Renan Santos
Posted on October 31, 2023
Como criar sua primeira aplicação fullstack com Docker.
Organizar uma aplicação completa do zero, pode ser uma tarefa bastante complicada no começo, devido a alta quantidade de maneiras de resolver o mesmo problema. Muitos programadores ficam perdidos por onde começar, principalmente os iniciantes, focam em tecnologia, quando oque é uma das coisas mais legais, é colocar a mão na massa.
Porque utilizar Docker?
Com docker é possivel abstrair todo o ambiente e acoplar somente em um local, sendo facilmente utilizado
para coloca-lo em produção, ou colocar para funcionar em um projeto do amigo dev. Além disso não precisamos
instalar diversos pacotes dentro da nossa maquina, já que o próprio emula esse ambiente pra gente. Alguns benefícios:
- fácil instalação
- fácil deploy
- versionamento de build com docker hub
- pode ser integrado com pipelines
- evita instalar N Versões da aplicação na própria máquina
- fácil reciprocidade da comunidade
- receita de bolo
*Como organizar a aplicação: *
Primeiro de tudo iremos escrever nossa receita docker onde a mesma irá se encarregar de praticamente tudo, optei por escolher o banco de dados Postgres, devido ser bastante fácil de mexer, o código é aberto, e tem muito conteudo na web. O projeto está divido em 4 camadas. Sendo elas, client onde será usado um framework React (pode ser qualquer um, como vue, angular etc..), server com node + express, redis, e banco de dados. *Nginx para chavear as portas da aplicação e servir o frontend e o backend em portas dinamicas. *
Na raiz do projeto.
version: '3'
services:
postgres:
image: "postgres:latest"
environment:
- POSTGRES_PASSWORD=postgres_password
redis:
image: "redis:latest"
nginx:
restart: always
build:
dockerfile: Dockerfile.dev
context: ./nginx
ports:
- "3050:80"
depends_on:
- api
- client
api:
build:
dockerfile: Dockerfile.dev
context : ./server
volumes:
- /app/node_modules
- ./server:/app
environment:
- REDIS_HOST=redis
- REDIS_PORT=6379
- PGUSER=postgres
- PGHOST=postgres
- PGDATABASE=postgres
- PGPASSWORD=postgres_password
- PGPORT=5432
client:
build:
dockerfile: Dockerfile.dev
context: ./client
volumes:
- /app/node_modules
- ./client:/app
environment:
- WDS_SOCKET=0
Note que na receita Dockerfile acima estamos fazendo a referencia para seus devidos arquivos Dockerfile.dev
de cada serviço. Estou optando pelo arquivo .dev, pois assim apos o desenvolvedor buildar, podemos fazer versionamento e subir o build com uma esteira de deploy para produção.
Configuração do servidor:
📁 ./server/Dockerfile.dev
FROM node:14.14.0-alpine
WORKDIR '/app'
COPY package.json .
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
📁 ./server/index.js
const keys = require('./keys')
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const { Pool } = require('pg')
const redis = require('redis')
const app = express()
app.use(cors())
app.use(bodyParser.json())
const pgClient = new Pool({
user: keys.pgUser,
port: keys.pgPort,
host: keys.pgHost,
database: keys.pgDataBase,
password: keys.pgPassword,
})
pgClient.on("connect", (client) => {
client
.query("CREATE TABLE IF NOT EXISTS values (number INT)")
.catch((err) => console.error(err));
});
const redisClient = redis.createClient({
host: keys.redisHost,
port: keys.redisPort,
retry_strategy: () => 1000
})
const redisPublisher = redisClient.duplicate()
// Express route handlers
app.get('/', (req, res) => {
res.send('Hi')
})
app.get('/values/all', async (req, res) => {
const values = await pgClient.query('SELECT * FROM values')
res.send(values.rows)
})
app.get('/values/current', async (req, res) => {
redisClient.hgetall('values', (err, values) => {
res.send(values)
})
})
app.post('/values', async (req, res) => {
const index = req.body.index
if (parseInt(index) > 40) {
return res.status(422).send({ message: 'Index to high' })
}
redisClient.hset('values', index, 'Nothing yet!')
redisPublisher.publish('insert', index)
pgClient.query('INSERT INTO values(number) VALUES($1)', [index])
res.send({ working: true })
})
app.listen(5000, () => {
console.info('app on listening on port 5000!')
})
O script acima é responsável por iniciar o backend, como trata-se de somente um tutorial
optei por deixar todo o código em um arquivo para fácil entendimento, fique a vontade para quebra-lo camadas e arquivos separados, como service, e controllers (recomendado).
📁 ./server/keys.js
module.exports = {
redisHost: process.env.REDIS_HOST,
redisPort: process.env.REDIS_PORT,
pgUser: process.env.PGUSER,
pgHost: process.env.PGHOST,
pgDataBase: process.env.PGDATABASE,
pgPassword: process.env.PGPASSWORD,
pgPort: process.env.PGPORT
}
Apos a instalação de todos os pacotes, ainda não sera possivel visualizar a aplicação no browser, pois teriamos que fazer uma configuração do nosso proxy nginx, onde o mesmo fica responsável por expor as portas da aplicação
para o mundo externo.
📁 ./nginx/default.conf
upstream client {
server client:3000;
}
upstream api {
server api:5000;
}
server {
listen 80;
location / {
proxy_pass http://client;
}
location /api {
rewrite /api/(.*) /$1 break;
proxy_pass http://api;
}
location /ws {
proxy_pass http://client;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
}
A configuração acima, acredito e julgo que seja muito ideal pois o frontend não irá precisar bater um endpoint separada pois tudo que começa com [api] ja temos a referencia que é a nosso serviço, logo oque vier depois disso trata-se somente do backend. Assim facilitando caso optar por deploy da aplicação em ambiente de produção, poupando tempo em troca de ip ou URI.
Disclaimer, como trata-se de um tutorial deixarei a parte do frontend e escolha do programador, onde somente mostrarei apenas o arquivo de proxy que ficará dentro da pasta do client. Pode ser usado qualquer tipo de framework frontend, até mesmo js puro com webpack. Desde que seja adicionado o arquivo de configuração do nginx, também fazendo as devidas correções para ouvir o arquivo de html do projeto.
📁 ./client/nginx/default.conf
server {
listen 3000;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
Como rodar o projeto 🚀
O comando docker abaixo será responsável por subir todo o nosso projeto, sendo possivel acessá-lo
na porta 3050, para o frontend, e 5000 (/api/)para o backend.
docker compose -f docker-compose-dev.yml up
Link do repositório Gitlab
👨💻 Volte sempre...
Posted on October 31, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.