Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

O Primeiro GET Endpoint

Com a configuração resolvida, vamos implementar as camadas de baixo para cima: banco de dados, repository, service, controller e route.

Conexão com o banco

Arquivo: backend/src/db.ts
import { Pool } from "pg";
import { loadConfig } from "./config";

const config = loadConfig();
export const pool = new Pool({ connectionString: config.databaseUrl });

Código 5-8: Pool de conexões com o PostgreSQL.

Usamos Pool em vez de Client aqui. Um Client abre uma única conexão com o banco — se duas requisições chegarem ao mesmo tempo, uma precisa esperar a outra terminar. O Pool mantém várias conexões abertas e distribui entre as requisições. Para o migrate.ts, que roda uma vez e sai, Client é adequado. Para um servidor que atende requisições continuamente, Pool.

Repository

Arquivo: backend/src/events/events.repository.ts
import { pool } from "../db";

export async function findAll() {
  const result = await pool.query(`
    SELECT
      e.id,
      e.title,
      e.description,
      e.location,
      e.date,
      e.capacity,
      e.created_at,
      u.name AS organizer_name
    FROM events e
    JOIN users u ON e.organizer_id = u.id
    ORDER BY e.date ASC
  `);
  return result.rows;
}

Código 5-9: Repository que busca todos os eventos no banco.

A query já faz o JOIN com users para trazer o nome do organizador — o frontend precisa exibir essa informação, e fazer duas consultas separadas seria desperdício. O ORDER BY e.date ASC retorna os eventos em ordem cronológica.

Service

Arquivo: backend/src/events/events.service.ts
import * as eventsRepository from "./events.repository";

export async function listAll() {
  return eventsRepository.findAll();
}

Código 5-10: Service que (por enquanto) apenas delega ao repository.

Nesse momento, o service parece inútil — ele apenas repassa a chamada para o repository. A camada existe porque é aqui que lógica de negócio vai morar quando aparecer. Filtrar eventos por data, verificar permissões de acesso — tudo isso entra no service sem mexer no repository nem no controller.

Controller

Arquivo: backend/src/events/events.controller.ts
import { Request, Response, NextFunction } from "express";
import * as eventsService from "./events.service";

export async function getAll(req: Request, res: Response, next: NextFunction) {
  try {
    const events = await eventsService.listAll();
    res.json(events);
  } catch (err) {
    next(err);
  }
}

Código 5-11: Controller que responde com a lista de eventos em JSON.

O controller chama o service, formata a resposta (o res.json serializa o array para JSON e define o header Content-Type: application/json automaticamente) e, se der erro, repassa para o middleware de tratamento de erros via next(err).

Route

Arquivo: backend/src/events/events.routes.ts
import { Router } from "express";
import * as eventsController from "./events.controller";

const router = Router();
router.get("/api/events", eventsController.getAll);

export default router;

Código 5-12: Rota que mapeia GET /api/events ao controller.

A rota é a camada mais fina: apenas conecta uma URL e um método HTTP a uma função do controller.

Montando tudo no app.ts

Arquivo: backend/src/app.ts
import "dotenv/config";
import express from "express";
import eventsRoutes from "./events/events.routes";
import { errorHandler } from "./errors/errorHandler";
import { loadConfig } from "./config";

const config = loadConfig();
const app = express();

app.use(eventsRoutes);
app.use(errorHandler);

app.listen(config.port, () => {
  console.log(`Servidor rodando em http://localhost:${config.port}`);
});

Código 5-13: Ponto de entrada do servidor.

O import "dotenv/config" na primeira linha carrega as variáveis do arquivo .env para process.env. Precisa estar antes de qualquer código que leia variáveis de ambiente — inclusive antes do loadConfig(). O dotenv procura o arquivo .env no diretório de onde o processo foi executado, por isso todos os comandos a seguir usam cd backend antes de rodar o servidor.

Testando o endpoint

Inicie o servidor e faça uma requisição com curl:

$ cd backend
$ npx tsx src/app.ts
Servidor rodando em http://localhost:3000

Em outro terminal:

$ curl -s http://localhost:3000/api/events | jq '.[0]'
{
  "id": 1,
  "title": "Hackathon UFS",
  "description": "Maratona de programação de 24h aberta a todos os cursos",
  "location": "Centro de Vivência - UFS",
  "date": "2026-09-15T11:00:00.000Z",
  "capacity": 50,
  "created_at": "2026-08-21T14:32:07.412Z",
  "organizer_name": "Ana Silva"
}

O primeiro evento do TamoJunto, servido como JSON. O frontend vai consumir exatamente essa resposta no próximo capítulo.

Para conferir que todos os 12 eventos estão lá:

$ curl -s http://localhost:3000/api/events | jq 'length'
12