A Arquitetura em Camadas
Hora de construir o backend que vai servir esses dados via HTTP. A tentação é colocar tudo num único arquivo — receber a requisição, consultar o banco, montar a resposta. Para 12 eventos e um endpoint, até funciona. Mas pense no que vem pela frente: autenticação, validação, tratamento de erro, múltiplos endpoints. Um arquivo monolítico cresce muito rápido.
A abordagem que vamos seguir é dividir o backend em camadas, cada uma com uma responsabilidade:
Route — Define as URLs e os métodos HTTP que a aplicação aceita.
GET /api/events deve chamar tal função. Só mapeamento.
Controller — Recebe a requisição HTTP, extrai os dados relevantes (query params, body, headers) e repassa para a camada de serviço. Depois, formata a resposta HTTP com o status code e o body adequados. O controller conhece HTTP; as camadas abaixo dele, não.
Service — Contém a lógica de negócio. Hoje a resposta é simples — “retorna todos os eventos” — mas essa é a camada que vai crescer quando o TamoJunto ganhar regras mais complexas.
Repository — Faz as consultas SQL no banco de dados. É a única camada que sabe que o dado mora no PostgreSQL. Se um dia migrarmos para outro banco, só o repository muda.
A requisição flui assim: Route → Controller → Service → Repository → banco de dados. A resposta faz o caminho inverso.
Para um endpoint que faz SELECT * FROM events, a lógica de cada camada cabe em
duas linhas. A separação não é sobre o tamanho do código hoje — é sobre onde
cada tipo de decisão fica quando o projeto crescer.
Estrutura de arquivos
Dentro de backend/src/, crie os seguintes arquivos:
backend/
├── migrations/
│ ├── 001_create_events.sql
│ └── 002_create_users_add_fk.sql
├── seed.sql
├── migrate.ts
├── package.json
├── tsconfig.json
├── .env
└── src/
├── app.ts
├── config.ts
├── db.ts
├── events/
│ ├── events.routes.ts
│ ├── events.controller.ts
│ ├── events.service.ts
│ └── events.repository.ts
└── errors/
└── errorHandler.ts
Os arquivos de eventos estão agrupados no diretório events/. Quando o
TamoJunto ganhar autenticação, teremos um diretório auth/ com a mesma
estrutura. Cada feature num lugar.
Por que um framework?
O Node.js já sabe atender HTTP sozinho. O módulo node:http que vem embutido
liga um servidor e entrega cada requisição para uma função. Um endpoint como o
nosso ficaria assim:
import http from "node:http";
const server = http.createServer((req, res) => {
if (req.method === "GET" && req.url === "/api/events") {
const events = buscarEventos();
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(events));
return;
}
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Not Found" }));
});
server.listen(3000);
Código 5-5: O mesmo endpoint escrito só com o módulo node:http.
Funciona. O problema aparece no segundo endpoint, e no terceiro. Cada rota vira
mais um if comparando req.method e req.url na mão. Ler o corpo de um POST
significa juntar pedaços do req conforme eles chegam pela rede. Um parâmetro
como /api/events/42 exige fatiar a string da URL você mesmo. Nada disso é
lógica do TamoJunto: é a mesma canalização que todo servidor HTTP reescreve.
Um framework é uma biblioteca que já resolve essa canalização e te dá uma estrutura pronta para pendurar o seu código. Você não chama o framework quando quer; ele chama o seu código quando uma requisição chega. O roteamento, a leitura do corpo, a extração de parâmetros da URL, o framework cuida disso, e você escreve só a parte que é do seu problema.
O Express é um framework HTTP minimalista para Node.js. O endpoint acima,
com Express, vira uma linha de roteamento e uma função que responde, sem nenhum
if de despacho. É o que vamos construir a seguir.
Instalando as dependências
Antes de escrever código, inicialize o projeto e instale os pacotes necessários:
$ cd backend
$ npm init -y
$ npm install express pg dotenv
$ npm install -D typescript @types/express @types/pg @types/node tsx
Além do express, o pg é o cliente PostgreSQL e o dotenv carrega variáveis
de ambiente de um arquivo .env. Os pacotes com @types/ são as definições de
tipo para TypeScript, e tsx é o executor que compila e roda TypeScript num
passo só.