Módulo 2 · Ciclo Comum · IN02 · Aula 6 de 11
Back-End II
Endpoints de leitura e escrita
Completando o CRUD via HTTP — req → controller → service → repository → DB e volta
📡 REST
✅ Validação (zod)
🔢 Status codes
🔁 Idempotência
⛔ Erros de domínio
🔐 Transações
⏱️ Daily — 15 Minutos
Como ficou o setup TS + Express da aula 5? Os primeiros GETs respondem?
15:00
✅ GETs já funcionando 🎯 Vou implementar POST/PUT/DELETE 🚧 Dúvidas em validação 📦 Estrutura MVC consolidada
📋 Agenda da Aula 6
Estrutura e objetivos da aula de hoje

🕐 Bloco 1 — Autoestudo (revisão e dúvidas)

Recap do material — REST, validação, transações, erros de domínio.

🕑 Bloco 2 — Instrução (Professor)

CRUD HTTP completo: validação com zod, regras de negócio no service, transação no repository e middleware de erros.

🕒 Bloco 3 — Almoço

Pausa para refeição e respiro antes do trabalho da tarde.

🎯 Bloco 4 — Desenvolvimento (Projeto)

Implementar POST/PUT/DELETE da entidade do projeto com validação e tratamento padronizado de erros.

🗺️ Roadmap do módulo
Onde estamos no Módulo 2 — Desenvolvimento Web
1
Aula 1 — HTTP, internet e DNS
Fundamentos de rede
2
Aula 2 — Banco de Dados I
Modelagem, DER, normalização
3
Aula 3 — Banco de Dados II
CRUD em SQL
4
Aula 4 — Banco de Dados III
JOINs, agregações, subqueries
5
Aula 5 — Back-End I
TS + Express + MVC 6 camadas
6
Aula 6 — Back-End II — você está aqui
Endpoints de leitura e escrita · CRUD via HTTP
7
Aula 7 — Front-End I
SSR com EJS no Express
8
Aula 8 — Front-End II
Forms, fetch e integração com a API
9
Aula 9 — Deploy & Observabilidade
Hospedagem, env, logs
10
Aulas 10-11 — Fechamento
Refino e apresentação
Recap — MVC com 6 camadas (TypeScript)
Arquitetura consolidada na aula 5 — hoje fazemos a coreografia ir e voltar
📐
models/ — tipos e interfaces
Contrato dos dados (User, Order, Product) · sem lógica
🎨
views/ — .ejs renderizados pelo back
SSR · entram em cena na aula 7
🎮
controllers/ — entrada e saída HTTP
Recebe req, valida payload, chama service, devolve res com status
⚙️
services/ — regras de negócio
Decide, orquestra, valida invariantes · não toca em req/res
🗄️
repositories/ — acesso ao banco (pg)
SQL puro com pool/client · transações vivem aqui
🧰
helpers/ — utilitários puros e testáveis
validateEmail, parsePositiveInt, formatadores · sem efeitos colaterais
REST refresher — verbos × CRUD × status
O contrato HTTP que vamos implementar nas próximas 2 horas
VerboRotaCRUDSucessoErros típicos
GET/usersRead (lista)200 OK500
GET/users/:idRead (item)200 OK404 Not Found
POST/usersCreate201 Created + Location400 · 409 · 422
PUT/users/:idReplace (idempotente)200 OK404 · 422
PATCH/users/:idUpdate parcial200 OK404 · 422
DELETE/users/:idDelete (idempotente)204 No Content404 (ou 204 se idempotente puro)

🔢 Status codes que vamos usar

200OK — leitura ou update bem sucedido 201Created — recurso criado (POST) 204No Content — DELETE ok, sem body 400Bad Request — JSON malformado 404Not Found — recurso inexistente 409Conflict — duplicidade (UNIQUE) 422Unprocessable — validação semântica 500Internal — algo quebrou no servidor

🛣️ Roteamento modular

src/routes/index.ts agrega; cada recurso tem seu próprio Router():

  • routes/userRoutes.ts
  • routes/orderRoutes.ts
  • routes/productRoutes.ts

Vantagem: isolamento, testabilidade e onboarding rápido em times grandes.

Validação de payload — zod recomendado
Não confie em req.body · valide antes de qualquer regra
controllers/UserController.ts
manual
Helpers + if/else
Mais código, mas zero dependência. Bom para projetos pequenos.
zod ⭐
Schema declarativo + tipos inferidos
A escolha desta aula. Erros estruturados, integração natural com TS.
class-validator
Decorators em DTOs
Forte em projetos NestJS. Verboso para Express puro.

💡 Por que zod?

z.infer<typeof schema> dá o tipo TS de graça — uma fonte da verdade.

Helpers de validação — puros e testáveis
Nem tudo precisa de zod · helpers pequenos vivem em helpers/
helpers/validation.ts

✅ Características de um bom helper

  • Puro — mesma entrada → mesma saída
  • Sem efeitos colaterais — não loga, não acessa banco
  • Tipado — assinatura clara em TS
  • Testável em isolamento — sem mocks

🧪 Exemplo de teste

expect(parsePositiveInt("42")).toBe(42);
expect(parsePositiveInt("-1")).toBeNull();

Service — onde a regra de negócio mora
O controller delega · o service decide · o repository persiste
services/OrderService.ts
Repository com transação — pg puro
Quando 2+ INSERTs precisam ser atômicos · BEGIN, COMMIT, ROLLBACK
repositories/OrderRepository.ts
Erros de domínio + middleware centralizado
Um lugar só converte exceção em resposta HTTP
errors/AppError.ts + middlewares/errorHandler.ts

⛔ Hierarquia de erros

  • AppError — base com status + message
  • NotFoundError → 404
  • ConflictError → 409 (UNIQUE violado)
  • ValidationError → 400

🛡️ Boas práticas

  • Não vazar err.stack nem detalhes do banco
  • Logar server-side com contexto (request id, user id)
  • Mensagem ao cliente: curta, sem dado sensível
asyncHandler — adeus try/catch repetido
Wrapper genérico que captura promises rejeitadas e chama next(err)
helpers/asyncHandler.ts

❌ Sem asyncHandler

router.post('/users', async (req, res, next) => {
  try { ... } catch (e) { next(e); }
});

O try/catch aparece em todo handler.

✅ Com asyncHandler

router.post('/users', asyncHandler(async (req, res) => {
  const u = await userService.create(req.body);
  res.status(201).location(`/users/${u.id}`).json(u);
}));

Erros vão direto pro middleware central.

End-to-end: POST /orders
A coreografia completa nas 6 camadas — req → controller → service → repository → DB → res
📨
1. HTTP request
POST /orders
body com user_id e items
🎮
2. Controller
zod parse
chama OrderService
⚙️
3. Service
Valida estoque
calcula total
abre transação
🗄️
4. Repository
BEGIN
INSERT order
INSERT itens
COMMIT
📬
5. Response
201 Created
Location: /orders/42
JSON do pedido

✅ Caminho feliz

Estoque ok → transação commit → 201 + Location header → cliente sabe onde buscar.

⛔ Caminho de erro

Estoque insuficiente → service lança ValidationError → middleware retorna 422 → nada foi gravado.

Idempotência — re-executar sem efeito colateral
PUT vs POST · por que DELETE deve ser idempotente

🟢 PUT — idempotente

PUT /users/42 com mesmo body 5 vezes deixa o sistema no mesmo estado final que executar 1 vez.

Cliente pode tentar de novo após timeout sem medo.

🟠 POST — NÃO idempotente

POST /orders 5 vezes cria 5 pedidos. Cliente que dá retry sem cuidado duplica dados.

Avançado: header Idempotency-Key resolve.

🔴 DELETE — idempotente

DELETE /users/42 5 vezes deixa o user deletado uma vez. Após a 1ª, retornar 204 ou 404 é uma escolha de projeto.

💡 Padrão de resposta — escolha UMA convenção

  • Envelope: { data: ..., meta: {...} } — bom para paginação
  • Direto: { id, name, ... } — mais limpo, REST clássico
  • Erros sempre no formato: { error: "mensagem curta" }
📝 PONDERADA · PROGRAMAÇÃO II
Testes de Integração — Caixa-Preta de um Fluxo
Banco real · integrações externas mockadas · prove que o sistema atende ao negócio

📋 Descrição

Você já tem endpoints implementados e regras de negócio mapeadas. Agora prove que o sistema atende ao negócio testando um fluxo inteiro como caixa-preta: a requisição entra no Controller, atravessa Service e Repository, e o efeito no banco é verificado.

Integrações externas (e-mail, storage, APIs de terceiros) são substituídas por mocks — o banco de dados não é mockado.

📦 Entrega mínima

  • Um fluxo documentado com justificativa de escolha
  • Suite Jest com os 4 casos obrigatórios
  • Banco real nos testes (não mockado); integrações externas com jest.mock()
  • Output de npm test com todos os casos passando
  • Matriz RF → RN → Teste preenchida

🧪 4 casos obrigatórios da suite

✅ OK Sucesso — fluxo executa de ponta a ponta e persiste ⛔ RN Regra de negócio violada — service rejeita 🚫 400 Payload inválido — validação no controller 💾 DB Verificação de persistência — SELECT real no banco

⚙️ Regras da suite

  • Mínimo 4 casos de teste para o fluxo escolhido
  • Banco limpo e reseedado antes de cada teste (beforeEach)
  • Integrações externas (e-mail, SMS, APIs externas) usam jest.mock()
  • O banco não usa mock — persistência é real (DB de teste ou em memória)
  • Cole o output de npm test apontando que todos passaram

🚀 Formato de entrega

🔀 MR Abra um Merge Request com as mudanças realizadas e a implementação dos testes. 🎓 ADALOVE No card da Adalove cole apenas o link do Merge Request. Pode estar open ou closed, aceito ou não — só não pode ser deletado (a correção precisa do link vivo).
RM-ODP — As 5 Visões do Sistema
ISO/IEC 10746 · Endpoints + validação + transações nas 5 visões
🏢 Enterprise
Regras de negócio do recurso
📋 Information
DTOs, schemas zod
⚙️ Computational
POST/PUT/DELETE
🔧 Engineering
Transações, middleware
💻 Technology
Express, pg, zod

Esta aula: Visão Computational — endpoints HTTP completos com validação no controller e transações no repository.

Esta Aula: Computational
RM-ODP · RF, RNF e Artefato — 8 Eixos no CRUD HTTP

📌 Requisito Funcional

Permitir criação, atualização e remoção do recurso central via HTTP, com validação de entrada e respostas consistentes.

📦 Artefato

  • 📡 Endpoints REST POST/PUT/DELETE
  • 🛡️ Schemas zod e middleware de erros
  • 🔁 Transações consistentes no repository

⚖️ RNF — 8 Eixos ISO/IEC 25010

CONF Confiabilidade✅ Transações + erros tipados
SEG Segurança✅ Validação no controller
MANT Manutenibilidade✅ Camadas isoladas
USAB Usabilidade API→ Erros padronizados
DES Desempenho→ Pool reusado
📡
CRUD via HTTP entregue!
Hoje você completa POST/PUT/PATCH/DELETE com validação, status codes corretos, transações no pg e middleware de erro. Na aula 7 a view EJS começa a renderizar essas respostas no servidor.
✅ Roteamento modular
✅ zod nos payloads
✅ Status codes corretos
✅ AppError + middleware
✅ asyncHandler
✅ Transações pg
✅ Idempotência
📦 Entrega da tarde (14h–16h): CRUD completo de 1 recurso + middleware de erro + 1 transação. MR feat(api): crud completo recurso X.
Módulo 2 · Ciclo Comum · Aula 6 de 11