Zero to Hero 🚀 - Como criar uma Aplicação Fullstack
Tags: node, redis, docker, react
Published: 2023-10-31 19:33:55

## 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.


![Show me the code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i63zi8fxtexjnd2th1bm.png)

###**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](https://gitlab.com/renandasilvasantos557/fullstackdockerapp/-/tree/master?ref_type=heads)

👨‍💻 Volte sempre...