MongoDB: API RESTful na prática
Última atualização: 2026-08-26
As APIs RESTful são o padrão para serviços da web — dominá-las permite que você crie uma arquitetura de API bem definida e fácil de manter.
Conceitos Fundamentais e Equívocos Comuns sobre REST: A essência do REST (Representational State Transfer) é a “orientação a recursos” — as URLs representam recursos, e os métodos HTTP representam operações. Equívocos comuns — 1. REST ≠ CRUD: o REST não se limita a Criar, Ler, Atualizar e Excluir; ele também pode expressar ações de negócios (por exemplo, POST /orders/{id}/cancel). O ponto-chave é que as URLs são baseadas em substantivos; 2. REST ≠ sem estado: a ausência de estado significa que cada solicitação contém todas as informações necessárias (sem depender de sessões do lado do servidor), mas os dados de negócios são, obviamente, com estado; 3. REST ≠ deve usar JSON: o REST não restringe o formato; o JSON é simplesmente a escolha mais popular; 4. REST ≠ perfeito: o REST não oferece bom suporte a notificações push em tempo real, consultas complexas ou operações em lote; gRPC e GraphQL são mais adequados para esses cenários. Compreender as limitações do REST é mais importante do que memorizar suas regras.
Modelo de Maturidade de APIs RESTful: O Modelo de Maturidade de Richardson classifica as APIs REST em quatro níveis — Nível 0 (Túnel HTTP): utiliza apenas POST, com todas as operações realizadas por meio de uma única URL (por exemplo, SOAP); Nível 1 (Recursos): introduz o conceito de recursos; recursos diferentes utilizam URLs diferentes, mas apenas GET e POST são utilizados; Nível 2 (Verbos HTTP): uso correto de GET, POST, PUT e DELETE juntamente com códigos de status — esse é o nível alcançado pela maioria dos projetos; Nível 3 (Hipermídia/HATEOAS): as respostas incluem links para recursos relacionados (por exemplo, uma resposta de pedido contém um link de cancelamento), possibilitando a autodescoberta. O maior salto ocorre do Nível 1 para o Nível 2 (que padroniza a semântica das operações), enquanto o Nível 3 raramente é utilizado na prática (ele aumenta a complexidade, mas oferece benefícios limitados para o front-end). O objetivo deste curso é o Nível 2 — construir APIs claras por meio da aplicação correta da semântica HTTP.
1. O que você vai aprender
- Princípios de projeto de APIs RESTful
- Endpoints CRUD completos (GET/POST/PUT/DELETE)
- Validação de solicitações (joi / express-validator)
- Paginação, ordenação, filtragem
- Especificação dos códigos de status HTTP
- Formato padronizado de resposta
2. Princípios de projeto RESTful
Modelo de Maturidade REST: Leonard Richardson definiu quatro níveis de maturidade para APIs REST — Nível 0: URL única + POST (por exemplo, SOAP); Nível 1: Introdução do conceito de recursos (URLs diferentes representam recursos diferentes); Nível 2: Métodos HTTP semânticos (GET/POST/PUT/DELETE expressam operações); Nível 3: HATEOAS (a resposta inclui um link para a próxima ação). A maioria das APIs em produção permanece no Nível 2; embora o Nível 3 seja o que melhor se alinha aos princípios REST, sua implementação é onerosa.
Projeto de segurança da API: A segurança de uma API RESTful requer várias camadas de defesa — 1. Camada de transporte: exigir o uso de HTTPS para evitar ataques do tipo “man-in-the-middle”; 2. Camada de autenticação: usar tokens JWT Bearer para verificar a identidade do usuário; 3. Camada de autorização: controle de acesso baseado em funções (RBAC) (cliente/administrador/moderador); 4. Camada de entrada: validação joi + limite de tamanho do corpo da solicitação (express.json({limit:'1mb'})); 5. Camada de limitação de taxa: express-rate-limit para impedir ataques de força bruta; 6. Camada de origem cruzada: lista de permissões CORS para restringir domínios de origem.
| Camada de segurança | Objetivo de defesa | Método de implementação |
|---|---|---|
| HTTPS | Interceptação/Adulteração | Certificados TLS |
| JWT | Falsificação de identidade | Token de portador |
| RBAC | Operações não autorizadas | Matriz de funções e permissões |
| joi | Injeção/Dados corrompidos | Validação do esquema |
| limitação de taxa | DDoS | Limitação de taxa por IP |
| CORS | Abuso entre origens | Domínios na lista de permissões |
O que é REST? REST (Representational State Transfer) é um estilo arquitetônico cujo princípio fundamental é que tudo é um recurso, identificado por uma URL e operado por meio de métodos HTTP. O REST não é um protocolo, mas um conjunto de restrições — uma API que segue essas restrições é chamada de API RESTful. Em sua tese de doutorado de 2000, Roy Fielding definiu seis restrições: cliente-servidor, ausência de estado, armazenamento em cache, interface uniforme, sistema em camadas e código sob demanda.
Princípios fundamentais do projeto RESTful:
| Princípio | Descrição | Exemplo |
|---|---|---|
| Use substantivos para recursos | URLs representam recursos, não ações | /products ✅ /getProducts ❌ |
| Substantivos no plural | Plural de substantivos coletivos | /products ✅ /product ❌ |
| Semântica dos métodos HTTP | GET (Leitura) / POST (Gravação) / PUT (Substituição) / PATCH (Atualização) / DELETE (Exclusão) | DELETE /products/:id |
| Recursos aninhados | Como expressar relações hierárquicas por meio de caminhos | /products/:id/reviews |
| Idempotência | Várias chamadas para GET/PUT/DELETE produzem o mesmo resultado | Chamadas repetidas de DELETE não resultam em erro |
| Sem estado | Cada solicitação contém todas as informações necessárias | O token JWT é incluído em todas as solicitações |
REST x RPC x GraphQL:
| Dimensão | REST | RPC | GraphQL |
|---|---|---|---|
| Conceitos básicos | Recursos + Semântica HTTP | Chamadas de ação | Consultas sob demanda |
| Estilo de URL | Substantivo | Verbo | Ponto de extremidade único |
| Recuperação de dados | Estrutura fixa | Estrutura fixa | Definida pelo cliente |
| Extração excessiva | Comum | Comum | Evitar |
| Curva de aprendizado | Baixa | Baixa | Média |
| Compatível com cache | Cache HTTP nativo | Deve ser implementado manualmente | Complexo |
| Casos de uso | API CRUD | Serviços internos | Front-end complexo |
Restrições e Liberdade do REST: As seis restrições do REST — cliente-servidor, ausência de estado, armazenamento em cache, interface uniforme, sistema em camadas e código sob demanda — não são requisitos obrigatórios. O REST de Nível 2 (recursos + semântica dos métodos HTTP) já atende a 90% das necessidades das APIs. Buscar excessivamente a pureza RESTful (como o HATEOAS de Nível 3) acaba, na verdade, aumentando os custos de desenvolvimento. Em projetos do mundo real, existem apenas três princípios fundamentais: 1. URLs são substantivos (recursos) e métodos HTTP são verbos (operações); 2. Expresse os resultados usando códigos de status; não os repita no corpo da resposta; 3. Autenticação sem estado (o JWT é transmitido em cada solicitação e não depende de sessões).
Quando não usar REST: O REST não é adequado para todos os cenários — 1. Comunicação em tempo real (os WebSockets são mais adequados; o modelo de solicitação-resposta do REST não suporta push do lado do servidor); 2. Operações em lote (as operações por recurso do REST são ineficientes; endpoints do tipo RPC, como /batch, são mais práticos para importações/exportações em lote); 3. Consultas complexas (o GraphQL oferece maior flexibilidade para filtragem com múltiplas condições combinadas; os parâmetros de consulta da URL do REST têm expressividade limitada); 4. Envio de arquivos (multipart/form-data não é um método padrão de interação REST, mas é aceitável na prática). A chave para escolher a abordagem certa é o pragmatismo, não o dogmatismo.
graph LR
Client[Client] -->|GET| R1[GET /products<br/>List]
Client -->|GET| R2[GET /products/:id<br/>Details]
Client -->|POST| R3[POST /products<br/>Create]
Client -->|PUT| R4[PUT /products/:id<br/>Complete Update]
Client -->|PATCH| R5[PATCH /products/:id<br/>Partial Update]
Client -->|DELETE| R6[DELETE /products/:id<br/>Delete]
style R1 fill:#d4edda
style R3 fill:#cce5ff
style R6 fill:#f8d7da
Práticas de design RESTful da Alice: Quando Alice projetou a API de produtos para o ShopHub, ela tratou as URLs como caminhos de recursos: /api/products representa uma coleção de produtos, /api/products/PHONE-001 representa um produto específico e /api/products/PHONE-001/reviews representa as avaliações desse produto — as próprias URLs funcionam como recursos autodocumentados.
Importância prática da idempotência: A idempotência é a base da confiabilidade das APIs — operações idempotentes podem ser repetidas com segurança, sem causar efeitos colaterais. Cenários práticos: 1. Repetições em caso de tempo limite de rede — Após um tempo limite em uma solicitação PUT/PATCH/DELETE, o front-end pode repetir a solicitação automaticamente (com o mesmo resultado da primeira tentativa); Solicitações POST não podem ser repetidas automaticamente (pois isso pode criar dados duplicados); 2. Condições precárias de rede em dispositivos móveis — Se a rede se desconectar depois que um usuário clicar em “Excluir”, repetir a solicitação após a conexão ser restaurada não excluirá outro registro; 3. Repetição de chamadas entre microsserviços — Quando chamadas entre serviços falham e são repetidas, operações idempotentes não produzem efeitos colaterais. Operações não idempotentes (como a criação via POST) exigem uma chave de idempotência para garantir a idempotência — o cliente gera um ID único, e o servidor verifica se ele já foi processado.
Guia para a escolha de códigos de status HTTP: Os códigos de status para APIs RESTful devem refletir com precisão o resultado — 2xx Sucesso (200 OK, 201 Criado, 204 Sem conteúdo), 4xx Erros do cliente (400 Solicitação inválida, 401 Não autorizado, 403 Proibido, 404 Não encontrado, 409 Conflito) e 5xx Erros do servidor (500 Erro interno do servidor, 502 Erro de gateway, 503 Serviço indisponível). Erros comuns: 1. Retornar 200 + {error: "...}} para todos os erros (viola a semântica HTTP; o front-end não consegue determinar o resultado com base no código de status); 2. Confundir 401 e 403 (401 indica “não autenticado” e requer login, enquanto 403 indica “autenticado, mas sem permissão”); 3. Usar 400 como um código genérico para todos os erros 4xx (o front-end não consegue distinguir entre “falha na validação” e “recurso inexistente”). Códigos de status precisos tornam o código do front-end mais conciso — os interceptadores do axios tratam as solicitações com base nas ramificações dos códigos de status.
Diretrizes para lidar com erros 5xx: Um erro 5xx indica um problema no lado do servidor — 1. 500 Erro interno do servidor: Uma exceção não interceptada (como um TypeError ou perda de conexão com o banco de dados) deve ser registrada com o rastreamento completo da pilha, e uma mensagem de erro genérica deve ser retornada ao cliente (sem expor o rastreamento da pilha); 2. 502 Bad Gateway: O proxy reverso (Nginx) não consegue se conectar ao aplicativo Node.js (trava do aplicativo ou falha na inicialização); acione um alerta; 3. 503 Serviço indisponível: O aplicativo está sobrecarregado ou em manutenção; retorne um cabeçalho Retry-After para informar ao cliente quando tentar novamente; 4. Tratamento unificado de erros 5xx: O middleware de erros do Express captura esses erros de maneira uniforme, os registra, envia alertas e retorna uma mensagem genérica. A taxa de erros 5xx em produção deve ser < 0,1%; ultrapassar esse limite aciona um alerta no PagerDuty.
| Método HTTP | Ação | Idempotente |
|---|---|---|
| OBTER | Ler | ✅ |
| POST | Criar | ❌ |
| PUT | Atualização completa | ✅ |
| PATCH | Atualização parcial | ✅ |
| EXCLUIR | Excluir | ✅ |
| Padrão de URL | Significado |
|---|---|
GET /api/products |
Lista |
GET /api/products/:id |
Detalhes |
POST /api/products |
Criar |
PUT /api/products/:id |
Atualização completa |
PATCH /api/products/:id |
Atualização parcial |
DELETE /api/products/:id |
Excluir |
3. Especificação dos códigos de status HTTP
Por que os códigos de status são importantes? Os códigos de status HTTP funcionam como “semáforos” para as APIs — os clientes os utilizam para determinar o resultado de uma solicitação sem precisar analisar o corpo da resposta. O código 2xx indica sucesso, o 4xx indica um erro do cliente e o 5xx indica um erro do servidor. O uso indevido do código de status 200 (como retornar um código de status 200 junto com uma mensagem de erro, mesmo quando ocorre um erro) prejudica a semântica do HTTP e impede que os clientes processem a resposta corretamente.
Os 6 códigos de status mais comuns: 90% das respostas de API em ambientes de produção exigem apenas 6 códigos de status — 200 OK (sucesso + dados retornados), 201 Criado (criação bem-sucedida), 204 Sem conteúdo (exclusão ou atualização bem-sucedida, sem dados retornados), 400 Solicitação inválida (falha na validação), 404 Não encontrado (recurso não existe) e 500 Erro interno do servidor (erro do servidor). Outros códigos de status (401/403/409/422) são usados em cenários específicos. Princípio: use os seis códigos básicos sempre que possível, em vez dos menos comuns; a consistência é mais importante do que a exaustividade.
Seleção precisa dos códigos de status 4xx: os códigos de status 4xx indicam erros do cliente; a seleção precisa ajuda os desenvolvedores de front-end a identificar o problema — 400 (formato de solicitação inválido/falha na autenticação; o front-end deve modificar a entrada e tentar novamente), 401 (não autenticado; o front-end deve redirecionar para a página de login), 403 (autenticado, mas sem permissão; o front-end deve exibir uma página “Sem permissão”), 404 (recurso inexistente; o front-end deve exibir “Não encontrado”), 409 (conflito/duplicação; o front-end deve exibir “Já existe”), 422 (erro semântico; validação aprovada, mas regras de negócio não atendidas). Erros comuns: usar o código 400 para abranger todos os erros 4xx (o front-end não consegue distinguir entre “não está logado” e “erro de parâmetro”) ou usar o código 404 em vez do 403 (ocultando a existência de um recurso e, ao mesmo tempo, criando uma controvérsia de segurança — um 404 indica “não existe” quando, na verdade, significa “acesso negado”).
Diretrizes para lidar com erros 5xx: Um erro 5xx indica um erro do lado do servidor — o cliente não pode resolvê-lo e deve tentar novamente ou aguardar. Princípios fundamentais: 1. Nunca exponha detalhes internos do erro (erros de banco de dados, caminhos de arquivos e rastreamentos de pilha são inúteis para os usuários e representam riscos à segurança); 2. Retorne um ID de solicitação (UUID) para que os usuários possam fornecê-lo ao relatar problemas, e os desenvolvedores possam usá-lo para pesquisar e identificar o problema nos logs; 3. Use o código 500 para erros desconhecidos, 502 para erros de gateway upstream (por exemplo, perda de conexão com o MongoDB) e 503 para sobrecarga do serviço (use o cabeçalho de resposta Retry-After para informar ao cliente quando tentar novamente); 4. Acione alertas (uma taxa de erros 5xx > 1% aciona um alerta no PagerDuty).
graph TD
Start[Request Result] --> Success{Success?}
Success -->|Yes| Code2xx[2xx Status Code]
Success -->|No| WhoFault{Whose Fault Is It?}
Code2xx --> HasBody{Has a return value?}
HasBody -->|Yes| OK[200 OK]
HasBody -->|Create a New Resource| Created[201 Created]
HasBody -->|No content| NoContent[204 No Content]
WhoFault -->|Client| ClientErr{Client Error?}
WhoFault -->|Server| ServerError[500 Internal Server Error]
ClientErr -->|Yes| Client4xx[4xx Client Error]
Client4xx --> What4xx{What's the problem?}
What4xx -->|Parameter error| BR[400 Bad Request]
What4xx -->|Not verified| UA[401 Unauthorized]
What4xx -->|No permission| FB[403 Forbidden]
What4xx -->|Does not exist| NF[404 Not Found]
What4xx -->|Resource Conflicts| CF[409 Conflict]
style OK fill:#d4edda
style Created fill:#d4edda
style BR fill:#f8d7da
style UA fill:#fff3cd
| Código de status | Significado | Cenário |
|---|---|---|
| 200 | OK | Sucesso (GET/PUT/PATCH) |
| 201 | Criado | Criado com sucesso (POST) |
| 204 | Sem conteúdo | Sucesso: Sem conteúdo (DELETE) |
| 400 | Solicitação inválida | Parâmetros de solicitação inválidos |
| 401 | Não autorizado | Não autenticado |
| 403 | Acesso proibido | Sem permissão |
| 404 | Não encontrado | O recurso não existe |
| 409 | Conflito | Conflito de recursos (por exemplo, duplicação) |
| 500 | Erro do servidor | Erro do servidor |
4. Formato padronizado de resposta
Por que padronizar o formato de resposta? Sem um formato padronizado, um endpoint poderia retornar { product }, outro { data: product } e, em caso de erro, { message: "error" } — o que exigiria que o front-end criasse uma lógica de análise diferente para cada endpoint. Com um formato padronizado, o front-end precisa apenas de um conjunto de lógica: if (response.success) indica sucesso, e else aciona a leitura de response.error.
Projeto da estrutura de resposta padrão:
| Campo | Tipo | Em caso de sucesso | Em caso de erro |
|---|---|---|---|
| sucesso | booleano | verdadeiro | falso |
| dados | qualquer | dados comerciais | não existe |
| meta | objeto | informações de paginação | não existe |
| erro | objeto | não existe | detalhes do erro |
Princípios de concepção de códigos de erro: Os códigos de erro (error.code) devem seguir o formato de letras maiúsculas e sublinhado (por exemplo, VALIDATION_ERROR), não devem revelar detalhes técnicos (por exemplo, não retorne MongooseError) e devem fornecer informações úteis (por exemplo, DUPLICATE_KEY + o nome do campo repetido).
HATEOAS e autodescoberta de API: O Nível 3 do Modelo de Maturidade REST é o HATEOAS (Hypermedia as the Engine of Application State) — as respostas contêm não apenas dados, mas também links para operações relacionadas. Por exemplo, uma resposta com detalhes de um pedido pode incluir links: {pay: '/orders/123/pay', cancel: '/orders/123/cancel'}. O HATEOAS torna a API autodescritiva — os clientes não precisam codificar URLs de forma estática, mas podem descobrir dinamicamente as operações disponíveis a partir da resposta. Embora a maioria dos projetos precise atingir apenas o Nível 2, o HATEOAS é adequado para plataformas de API abertas (como o Stripe e a API do GitHub).
Projeto dos metadados de paginação: Os metadados de paginação (meta) para a API de lista devem incluir informações suficientes para que o front-end exiba os controles de paginação — total (número total de registros), página (página atual), limite (número de registros por página) e totalPages (número total de páginas = ceil(total/limite)). Campos opcionais adicionais: hasNext (se há uma próxima página) e hasPrev (se há uma página anterior). O front-end usa esses campos para determinar a lógica de exibição da paginação: a barra de paginação é exibida somente se totalPages > 1, e o botão “Próxima página” fica oculto se currentPage == totalPages.
Métricas quantitativas para o desempenho de APIs: O desempenho das APIs RESTful precisa ser quantificado — 1. Tempo de resposta P50/P95/P99 (tempos de resposta para 50%, 95% e 99% das solicitações; uma meta comum é P95 < 200 ms); 2. Taxa de processamento (QPS; número de solicitações processadas por segundo; uma configuração com uma única instância do Express + MongoDB normalmente lida com 500–2.000 QPS); 3. Taxa de erros (porcentagem de erros 5xx em relação ao total de solicitações; < 0,1% é considerado um padrão saudável). Ferramentas de monitoramento: Prometheus + Grafana para coleta e exibição de métricas; Alertmanager para alertas. Prioridade na otimização de desempenho: primeiro, otimize as APIs mais lentas (aquelas com o P95 mais alto); depois, otimize as APIs mais frequentemente chamadas (listando as interfaces com o maior QPS).
Estratégias de armazenamento em cache de APIs: APIs com alto volume de leitura e baixo volume de gravação (como listas de produtos e detalhes de artigos) são adequadas para armazenamento em cache — 1. Armazenamento em cache HTTP (Cache-Control: max-age=300; os navegadores utilizam o cache diretamente por 5 minutos sem enviar uma solicitação); 2. Armazenamento em cache por CDN (armazenamento em cache de borda do Cloudflare/CloudFront, permitindo que usuários em todo o mundo acessem o conteúdo a partir do local mais próximo); 3. Armazenamento em cache por aplicativo (o Redis armazena em cache dados acessados com frequência com um TTL de 5 a 10 minutos); 4. Armazenamento em cache por banco de dados (armazenamento em cache do WiredTiger do MongoDB, gerenciado automaticamente). Estratégias de expiração do cache: 1. Expiração automática ao atingir o TTL; 2. Expiração proativa na gravação (exclusão da chave de cache correspondente após a atualização de um artigo); 3. Controle por número de versão (as chaves de cache incluem um número de versão, que é incrementado nas atualizações).
Proteção contra penetração de cache e avalanches de cache: Os sistemas de cache apresentam dois modos clássicos de falha — 1. Penetração de cache: consulta de dados que não existem (por exemplo, productId=999999); o cache não contém dados → consulta ao banco de dados → também não há dados encontrados lá → nenhum dado é armazenado em cache → o banco de dados é consultado novamente na próxima vez. Prevenção: Armazene em cache até mesmo resultados que retornem null (TTL de 60 segundos) ou use um filtro de Bloom para filtrar IDs inexistentes; 2. Avalanche de cache: Um grande número de itens em cache expira simultaneamente (por exemplo, expiração em lote às 3h da manhã), fazendo com que todas as solicitações sobrecarreguem o banco de dados. Prevenção: Adicione um deslocamento aleatório ao TTL (300 ± 30 segundos) para distribuir os horários de expiração; 3. Ruptura de cache: Um grande número de solicitações sobrecarrega o banco de dados no momento em que os dados mais acessados expiram. Mitigação: Use bloqueios distribuídos para dados mais acessados (permitindo apenas uma solicitação por vez para reconstruir o cache) ou implemente caches sem expiração com atualizações assíncronas. As estratégias de mitigação para essas três falhas diferem entre si e devem ser implementadas separadamente.
// Successful Response
{
"success": true,
"data": { ... },
"meta": { "page": 1, "limit": 20, "total": 100 }
}
// Error Response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": { "email": "Required" }
}
}
// Middleware:Standard Response
const sendSuccess = (res, data, meta = null) => {
res.json({ success: true, data, meta });
};
const sendError = (res, code, message, status = 400, details = null) => {
res.status(status).json({
success: false,
error: { code, message, details }
});
};
5. Solicitar um período de teste (joi)
Por que a validação de solicitações é necessária? Nunca confie nas entradas do cliente — strings vazias, textos excessivamente longos, caracteres inválidos e campos obrigatórios em branco podem levar a: ① gravação de dados incorretos no banco de dados; ② erros de consulta; ③ vulnerabilidades de segurança (ataques de injeção). A camada de validação funciona como a primeira linha de defesa da API, interceptando solicitações inválidas antes que os dados cheguem ao controlador.
Onde a camada de validação se encaixa no fluxo de solicitações: O middleware de validação deve ser colocado imediatamente antes do Controlador — após a autenticação/autorização (para verificar a identidade antes de validar a entrada) e antes da lógica de negócios (para impedir que dados incorretos cheguem ao Controlador). Assim que a validação for aprovada, os dados limpos são atribuídos a req.validated, e o Controlador utiliza apenas os dados de validated em vez dos originais de req.body — isso garante que o Controlador sempre receba dados válidos.
Comparação detalhada entre joi e express-validator: o joi é uma biblioteca de validação autônoma (independente do Express), enquanto o express-validator é um middleware projetado especificamente para o Express. Vantagens do joi: 1. Os esquemas podem ser exportados e reutilizados (o front-end e o back-end compartilham o mesmo conjunto de regras de validação); 2. Maior capacidade de expressão para validações complexas (validação entre campos, validação condicional); 3. Independente de framework (também funciona com Koa e Hapi). Vantagens do express-validator: 1. Middleware nativo do Express com integração sem configuração; 2. Baseado no validator.js, oferecendo um rico conjunto de regras de validação; 3. Sintaxe encadeada intuitiva. O joi é recomendado para novos projetos (devido à sua alta reutilização), mas não há necessidade de migrar projetos que já utilizam o express-validator.
sequenceDiagram
participant Client
participant Route as Express Route
participant Validate as joi Verification
participant Controller
participant DB as MongoDB
Client->>Route: POST /api/products
Route->>Validate: validate(req.body)
alt Verification Failed
Validate-->>Client: 400 VALIDATION_ERROR
else Verification Passed
Validate->>Controller: req.validated = value
Controller->>DB: Product.create(validated)
DB-->>Client: 201 Created
end
Estratégia de defesa em camadas para validação de entradas: A validação de entradas da API deve ser implementada em várias camadas — 1. Camada de roteamento (Joi/express-validator): Primeira linha de defesa, valida formato, tipo e intervalo; retorna um código de status 400, além de informações detalhadas sobre o erro; 2. Camada de controlador: valida regras de negócios (por exemplo, “A categoria deve existir”, “O SKU não pode ser duplicado”), o que requer uma consulta ao banco de dados; 3. Camada de modelo (esquema Mongoose): a última linha de defesa, garantindo que os dados gravados no MongoDB sigam rigorosamente as restrições estruturais. Cada uma dessas três camadas de validação tem uma finalidade distinta — a camada de roteamento filtra 90% das entradas inválidas (falha rápida), a camada do controlador lida com a lógica de negócios (exigindo consultas ao banco de dados) e a camada do modelo atua como uma rede de segurança (impedindo que as duas primeiras camadas sejam contornadas).
Comparação detalhada entre Joi e express-validator: Estas são as duas bibliotecas de validação de entrada mais utilizadas no Node.js — Joi: uma biblioteca de validação independente e independente de framework, com definições de esquema elegantes (API encadeada) e mensagens de erro detalhadas, mas que requer integração manual no Express (por meio de middleware); express-validator: baseado no validator.js, profundamente integrado ao Express (req.check() pode ser usado diretamente nas rotas), mas sua definição de esquema é menos intuitiva do que a do Joi (usa métodos check() encadeados em vez de definições baseadas em objetos). Critérios de seleção: 1. Usar o Joi para novos projetos (os esquemas são reutilizáveis, testáveis e podem gerar documentação Swagger); 2. Continuar usando o express-validator para projetos existentes (o custo da migração não compensa); 3. O Joi é mais conveniente para validações complexas (dependências condicionais, validação entre campos).
Padrões de Design para Middleware de Validação: Encapsular a lógica de validação como middleware é uma prática recomendada no Express — 1. O middleware de validação aceita um esquema (validate(createProductSchema)), valida req.body e chama next() se for bem-sucedido; caso contrário, retorna um código de status 400 juntamente com os detalhes do erro; 2. O middleware anexa os valores validados a req.validated (em vez de continuar usando req.body), impedindo que controladores subsequentes processem dados não validados; 3. Os esquemas de validação são separados por operações CRUD (por exemplo, createSchema possui campos obrigatórios, enquanto updateSchema tem todos os campos opcionais) e não são misturados. Esse padrão garante que os controladores não precisem se preocupar com a lógica de validação — eles processam apenas dados validados.
// validators/productValidator.js
const Joi = require('joi');
const createProductSchema = Joi.object({
sku: Joi.string().required().pattern(/^[A-Z0-9-]+$/),
title: Joi.string().required().min(1).max(200),
price: Joi.number().required().min(0),
category: Joi.string().required().valid('Electronics', 'Books', 'Clothing'),
stock: Joi.number().integer().min(0).default(0)
});
const updateProductSchema = Joi.object({
title: Joi.string().min(1).max(200),
price: Joi.number().min(0),
category: Joi.string().valid('Electronics', 'Books', 'Clothing'),
stock: Joi.number().integer().min(0)
}).min(1);
const validate = (schema) => (req, res, next) => {
const { error } = schema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
error: { code: 'VALIDATION_ERROR', details: error.details }
});
}
next();
};
module.exports = { createProductSchema, updateProductSchema, validate };
// routes/products.js
const { createProductSchema, updateProductSchema, validate } = require('../validators/productValidator');
router.post('/', validate(createProductSchema), ctrl.createProduct);
router.put('/:sku', validate(updateProductSchema), ctrl.updateProduct);
6. Endpoints CRUD completos
Padrão de projeto de endpoints CRUD: Cada recurso segue um padrão unificado de cinco endpoints — lista (GET /), detalhe (GET /:id), criação (POST /), atualização (PUT /:id) e exclusão (DELETE /:id). Pontos-chave de decisão: ① A lista deve usar paginação ou um cursor? ② As atualizações devem usar PUT ou PATCH? ③ A exclusão deve ser definitiva ou temporária?
Matriz de Permissões dos Endpoints CRUD: Cada endpoint possui requisitos de permissão diferentes — 1. GET / (Lista): Público ou requer autenticação (dependendo da lógica de negócios); as listas de produtos de comércio eletrônico são públicas, enquanto as listas de pedidos exigem autenticação; 2. GET /:id (Detalhes): Mesma política da lista, mas pode exigir filtragem de permissão (os usuários só podem visualizar os detalhes de seus próprios pedidos); 3. POST / (Criar): Autenticação necessária; alguns recursos exigem autorização (por exemplo, apenas administradores podem criar produtos); 4. PUT /:id (Atualizar): Autenticação necessária + proprietário do recurso ou administrador (os usuários só podem editar seus próprios comentários); 5. DELETE /:id (Excluir): Autenticação necessária + proprietário do recurso ou administrador. As verificações de permissão devem ser realizadas no middleware (autenticação + autorização); o Controlador lida apenas com a lógica de negócios.
Decisões sobre o projeto de endpoints CRUD:
| Ponto de decisão | Opção A | Opção B | Recomendação |
|---|---|---|---|
| Método de paginação | página + limite (deslocamento) | cursor | Use “página” para paginação superficial e “cursor” para paginação profunda |
| Método de atualização | PUT (substituição completa) | PATCH (atualização parcial) | PATCH (mais seguro; não limpa os campos não especificados) |
| Método de exclusão | Exclusão física (remover) | Exclusão temporária (sinalizador isDeleted) | Exclusão temporária (recuperável, conformidade de dados) |
| Formato de ID | ObjectId | SKU/Slug personalizado | SKU para usuários, ObjectId para uso interno |
| Classificação de listas | Campo único | Vários campos (combinação) | Vários campos (classificação + ordem) |
Otimização de desempenho do Bob: No ShopHub, Bob percebeu que as consultas de lista estavam executando tanto find quanto countDocuments simultaneamente. Ele utilizou Promise.all para executá-las em paralelo — reduzindo o tempo de consulta de 200 ms + 200 ms = 400 ms para max(200 ms, 180 ms) = 200 ms.
// controllers/productController.js
const Product = require('../models/Product');
// GET /api/products
exports.list = async (req, res) => {
const { page = 1, limit = 20, sort = 'createdAt', order = 'desc', category, search } = req.query;
const query = { isActive: true };
if (category) query.category = category;
if (search) query.title = new RegExp(search, 'i');
const [products, total] = await Promise.all([
Product.find(query)
.select('sku title price thumbnail rating')
.sort({ [sort]: order === 'desc' ? -1 : 1 })
.limit(limit * 1)
.skip((page - 1) * limit)
.lean(),
Product.countDocuments(query)
]);
res.json({
success: true,
data: products,
meta: { page: +page, limit: +limit, total, pages: Math.ceil(total / limit) }
});
};
// GET /api/products/:sku
exports.get = async (req, res) => {
const product = await Product.findOne({ sku: req.params.sku }).lean();
if (!product) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND' } });
res.json({ success: true, data: product });
};
// POST /api/products
exports.create = async (req, res) => {
const product = await Product.create(req.body);
res.status(201).json({ success: true, data: product });
};
// PUT /api/products/:sku
exports.update = async (req, res) => {
const product = await Product.findOneAndUpdate(
{ sku: req.params.sku },
req.body,
{ new: true, runValidators: true }
);
if (!product) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND' } });
res.json({ success: true, data: product });
};
// DELETE /api/products/:sku
exports.remove = async (req, res) => {
const product = await Product.findOneAndDelete({ sku: req.params.sku });
if (!product) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND' } });
res.status(204).send();
};
7. Tratamento de erros
Estratégia de tratamento de erros em camadas na API: O tratamento de erros não se resume simplesmente a “adicionar um bloco try-catch”, mas sim a um sistema de defesa em camadas — a camada de validação intercepta entradas inválidas (4xx), a camada de lógica de negócios intercepta erros lógicos (por exemplo, produto inexistente → 404), a camada de banco de dados intercepta erros de sistema (por exemplo, conexão perdida → 5xx) e a camada mais externa captura todos os erros inesperados.
Classificação incorreta e manuseio:
| Origem do erro | Exemplo | Código de status | Solução |
|---|---|---|---|
| Camada de validação | Falha na validação do joi | 400 | Retornar detalhes do erro no nível do campo |
| Camada de Negócios | O produto não existe | 404 | Retornar "O recurso não existe" |
| Camada de banco de dados | Conflito de chave única | 409 | Retornar o campo em conflito |
| Camada de autenticação | Token inválido | 401 | Retornado sem autenticação |
| Nível de permissão | Função insuficiente | 403 | Sem permissão |
| Nível do sistema | Exceção desconhecida | 500 | Retorna um erro genérico (detalhes não divulgados) |
Princípios de design para middleware de tratamento de erros: O middleware de tratamento de erros do Express é uma função com quatro argumentos (err, req, res, next) e deve ser colocado após todas as rotas. Pontos-chave do projeto: 1. Prioridade na classificação de erros — verifique na ordem: ValidationError → MongoError → JsonWebTokenError → 500 (error genérico), pois tipos específicos de erro podem fornecer códigos de status mais precisos; 2. Não exponha o rastreamento da pilha em produção — err.stack é retornado apenas no ambiente de desenvolvimento; em produção, uma mensagem genérica é retornada; 3. Níveis de log — erros 4xx são registrados como warn (erros do cliente), e erros 5xx são registrados como error (erros do servidor); 4. Contexto da solicitação — os logs de erros devem incluir requestId, userId e path para facilitar o diagnóstico de problemas.
Medidas de segurança para respostas de erro: As respostas de erro da API são uma das principais fontes de vazamento de informações — 1. Não retorne a mensagem bruta para erros de banco de dados (que podem conter nomes de coleções ou instruções de consulta); 2. Retorne rastreamentos de pilha apenas no ambiente de desenvolvimento; 3. O Mongoose ValidationError pode ser retornado diretamente (erros no nível do campo são seguros), mas MongoError deve ser filtrado (conflitos de chave única podem ser retornados; os demais devem ser mascarados); 4. Padronize a formatação de erros como {success: false, error: {code, message, details?}} para que o front-end não precise analisar a estrutura da resposta. A essência desse princípio: nenhum erro 5xx deve expor detalhes internos de implementação ao cliente.
// middlewares/errorHandler.js
const errorHandler = (err, req, res, next) => {
console.error(err);
if (err.name === 'ValidationError') {
return res.status(400).json({
success: false,
error: { code: 'VALIDATION_ERROR', message: err.message, details: err.errors }
});
}
if (err.code === 11000) {
return res.status(409).json({
success: false,
error: { code: 'DUPLICATE_KEY', message: 'Duplicate key', details: err.keyValue }
});
}
res.status(500).json({
success: false,
error: { code: 'INTERNAL_ERROR', message: 'Internal server error' }
});
};
8. Prática: a API de comentários
Projeto de roteamento de recursos aninhados: Os comentários estão subordinados aos produtos; portanto, a URL é projetada como /products/:productId/reviews em vez de /reviews?productId=xxx — a primeira é semanticamente mais clara e mais compatível com as convenções REST. No entanto, operações diretas nos comentários (atualizar/excluir) utilizam /reviews/:reviewId, uma vez que o contexto do produto não é necessário nesses casos.
Projeto da API do sistema de comentários:
| Endpoint | Método | Autenticação | Descrição |
|---|---|---|---|
| /products/:productId/reviews | GET | Não | Ver lista de avaliações |
| /products/:productId/reviews | POST | Sim | Publicar uma avaliação |
| /reviews/:reviewId | PUT | Sim (Autor) | Editar avaliação |
| /reviews/:reviewId | EXCLUIR | Sim (Autor) | Excluir comentário |
| /reviews/:reviewId/like | POST | Sim | Curtir/Deixar de curtir |
// routes/reviews.js
router.get('/products/:productId/reviews', ctrl.listReviews);
router.post('/products/:productId/reviews', authenticate, ctrl.createReview);
router.put('/reviews/:reviewId', authenticate, ctrl.updateReview);
router.delete('/reviews/:reviewId', authenticate, ctrl.deleteReview);
router.post('/reviews/:reviewId/like', authenticate, ctrl.likeReview);
Princípios de projeto para pontos de extremidade de API: A tabela acima ilustra o projeto dos pontos de extremidade de API para um sistema de avaliações de comércio eletrônico — 1. Use substantivos no plural para nomes de recursos (/produtos, e não /produto); 2. Use recursos aninhados para expressar relações hierárquicas (/produtos/:idDoProduto/avaliações indica “avaliações para um produto específico”); 3. Identifique claramente os requisitos de autenticação — as operações de leitura são públicas (GET), enquanto as operações de gravação exigem autenticação (POST/PUT/DELETE); 4. Use subcaminhos de verbos para pontos de extremidade baseados em ações (/reviews/:reviewId/like em vez do coletivo /likes); 5. Use um prefixo de versão consistente (/api/v1/..., omitido na tabela).
Três estratégias para o controle de versões de APIs: 1. Prefixo de URL (/api/v1/products): a abordagem mais intuitiva e comumente utilizada; as mudanças de versão ficam claras, mas as URLs ficam mais longas; 2. Cabeçalho da solicitação (Header: Api-Version: 1): a URL permanece inalterada, mas os clientes precisam definir o cabeçalho da solicitação, o que dificulta a depuração; 3. Negociação de conteúdo (Accept: application/vnd.api+json; version=1): A mais RESTful, mas também a mais complexa; raramente utilizada em projetos reais. Estratégia recomendada: 1 — fácil de usar para desenvolvedores, fácil de testar e com suporte nativo pelo roteamento do Nginx. Ao atualizar versões: mantenha a v1 inalterada, crie um novo arquivo de roteamento para a v2, migre os clientes gradualmente e retire a v1 de operação após definir uma data de fim de vida útil.
▶ Exemplo 1: CRUD de produto + paginação + validação
// === Complete Product CRUD API(Includes verification+Pagination+Error Handling)===
const express = require('express');
const Joi = require('joi');
const mongoose = require('mongoose');
const app = express();
app.use(express.json());
// Schema
const ProductSchema = new mongoose.Schema({
sku: { type: String, required: true, unique: true },
title: { type: String, required: true },
price: { type: Number, required: true, min: 0 },
category: { type: String, required: true, enum: ['Electronics', 'Books', 'Clothing'] },
stock: { type: Number, default: 0, min: 0 }
}, { timestamps: true });
const Product = mongoose.model('Product', ProductSchema);
// Joi Verification
const createSchema = Joi.object({
sku: Joi.string().required().pattern(/^[A-Z0-9-]+$/),
title: Joi.string().required().min(1).max(200),
price: Joi.number().required().min(0),
category: Joi.string().required().valid('Electronics', 'Books', 'Clothing'),
stock: Joi.number().integer().min(0).default(0)
});
const validate = (schema) => (req, res, next) => {
const { error, value } = schema.validate(req.body, { abortEarly: false });
if (error) return res.status(400).json({ success: false, error: { code: 'VALIDATION_ERROR', details: error.details } });
req.validated = value;
next();
};
// CRUD
app.get('/api/products', async (req, res) => {
const { page = 1, limit = 20, category } = req.query;
const query = {};
if (category) query.category = category;
const [products, total] = await Promise.all([
Product.find(query).select('sku title price').skip((page-1)*limit).limit(+limit).lean(),
Product.countDocuments(query)
]);
res.json({ success: true, data: products, meta: { page: +page, limit: +limit, total, pages: Math.ceil(total/limit) } });
});
app.post('/api/products', validate(createSchema), async (req, res) => {
const product = await Product.create(req.validated);
res.status(201).json({ success: true, data: product });
});
app.get('/api/products/:sku', async (req, res) => {
const product = await Product.findOne({ sku: req.params.sku }).lean();
if (!product) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND' } });
res.json({ success: true, data: product });
});
app.use((err, req, res, next) => {
if (err.code === 11000) return res.status(409).json({ success: false, error: { code: 'DUPLICATE_KEY' } });
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR' } });
});
mongoose.connect('mongodb://localhost:27017/shopdb').then(() => app.listen(3000));
Resultado: Uma API CRUD completa para produtos, incluindo validação Joi, consultas paginadas, tratamento de erros e um formato de resposta padronizado.
▶ Exemplo: Completo CRUD API com Pagination e Filtering (Difficulty ⭐⭐)
// Scene: ShopHub product API with search, filtering, pagination, and sorting
const express = require('express');
const router = express.Router();
const Product = require('../models/product');
// GET /api/products - List with pagination, filtering, sorting
router.get('/', async (req, res, next) => {
try {
const { page = 1, limit = 10, sort = '-createdAt', category, minPrice, maxPrice, search } = req.query;
// Build filter query
const filter = {};
if (category) filter.category = category;
if (minPrice || maxPrice) {
filter.price = {};
if (minPrice) filter.price.$gte = Number(minPrice);
if (maxPrice) filter.price.$lte = Number(maxPrice);
}
if (search) {
filter.$text = { $search: search };
}
// Execute query with pagination
const [products, total] = await Promise.all([
Product.find(filter)
.sort(sort)
.skip((page - 1) * limit)
.limit(Number(limit))
.select('sku title price category stock')
.lean(),
Product.countDocuments(filter)
]);
// Response with pagination metadata
res.json({
success: true,
data: products,
pagination: {
total,
page: Number(page),
limit: Number(limit),
pages: Math.ceil(total / limit)
}
});
} catch (err) {
next(err);
}
});
// GET /api/products/:id - Get single product
router.get('/:id', async (req, res, next) => {
try {
const product = await Product.findById(req.params.id).lean();
if (!product) {
return res.status(404).json({ success: false, error: 'Product not found' });
}
res.json({ success: true, data: product });
} catch (err) {
next(err);
}
});
module.exports = router;
Saída:
TEXT 📖 Somente leituraGET /api/products?category=Electronics&minPrice=100&maxPrice=1000&sort=-price { "success": true, "data": [ { "sku": "PHONE-001", "title": "Smartphone X", "price": 999, "category": "Electronics", "stock": 50 }, { "sku": "PHONE-002", "title": "Smartphone Y", "price": 299, "category": "Electronics", "stock": 120 } ], "pagination": { "total": 2, "page": 1, "limit": 10, "pages": 1 } }
▶ Exemplo 2: API RESTful completa para comentários + validação com joi
// === 1. validators/reviewValidator.js - joi Verification ===
const Joi = require('joi');
const createReviewSchema = Joi.object({
productId: Joi.string().required().pattern(/^[0-9a-fA-F]{24}$/), // ObjectId
content: Joi.string().required().min(10).max(1000),
rating: Joi.number().integer().required().min(1).max(5),
parentId: Joi.string().pattern(/^[0-9a-fA-F]{24}$/).allow(null)
});
const updateReviewSchema = Joi.object({
content: Joi.string().min(10).max(1000),
rating: Joi.number().integer().min(1).max(5)
}).min(1); // At least one field
const validate = (schema) => (req, res, next) => {
const { error, value } = schema.validate(req.body, { abortEarly: false });
if (error) {
return res.status(400).json({
success: false,
error: {
code: 'VALIDATION_ERROR',
details: error.details.map(d => ({ field: d.path.join('.'), message: d.message }))
}
});
}
req.validated = value; // Validated data
next();
};
module.exports = { createReviewSchema, updateReviewSchema, validate };
// === 2. controllers/reviewController.js ===
const Review = require('../models/Review');
const Product = require('../models/Product');
exports.list = async (req, res, next) => {
try {
const { productId } = req.params;
const { page = 1, limit = 20, sort = '-createdAt' } = req.query;
const reviews = await Review.find({ productId, parentId: null })
.populate('userId', 'username avatar')
.populate({
path: 'replies',
populate: { path: 'userId', select: 'username avatar' }
})
.sort(sort)
.skip((page - 1) * limit)
.limit(+limit)
.lean();
const total = await Review.countDocuments({ productId, parentId: null });
res.json({
success: true,
data: reviews,
meta: { page: +page, limit: +limit, total, pages: Math.ceil(total / limit) }
});
} catch (err) { next(err); }
};
exports.create = async (req, res, next) => {
try {
// 1. Verify Product Availability
const product = await Product.findById(req.validated.productId).lean();
if (!product) {
return res.status(404).json({
success: false,
error: { code: 'PRODUCT_NOT_FOUND' }
});
}
// 2. Verify that the parent comment exists(If this is a reply)
if (req.validated.parentId) {
const parent = await Review.findById(req.validated.parentId);
if (!parent) {
return res.status(404).json({
success: false,
error: { code: 'PARENT_REVIEW_NOT_FOUND' }
});
}
}
// 3. Create a Review
const review = await Review.create({
...req.validated,
userId: req.user._id
});
// 4. Update Product Ratings
await updateProductRating(req.validated.productId);
await review.populate('userId', 'username avatar');
res.status(201).json({ success: true, data: review });
} catch (err) { next(err); }
};
exports.update = async (req, res, next) => {
try {
const review = await Review.findOneAndUpdate(
{ _id: req.params.reviewId, userId: req.user._id }, // Only the author can edit this.
{ $set: { ...req.validated, isEdited: true } },
{ new: true, runValidators: true }
).lean();
if (!review) {
return res.status(404).json({
success: false,
error: { code: 'NOT_FOUND_OR_NO_PERMISSION' }
});
}
res.json({ success: true, data: review });
} catch (err) { next(err); }
};
exports.remove = async (req, res, next) => {
try {
const review = await Review.findOneAndDelete({
_id: req.params.reviewId,
userId: req.user._id
});
if (!review) {
return res.status(404).json({
success: false,
error: { code: 'NOT_FOUND_OR_NO_PERMISSION' }
});
}
await updateProductRating(review.productId);
res.status(204).send();
} catch (err) { next(err); }
};
// === 3. routes/reviews.js ===
const router = require('express').Router();
const ctrl = require('../controllers/reviewController');
const { authenticate } = require('../middlewares/auth');
const { createReviewSchema, updateReviewSchema, validate } = require('../validators/reviewValidator');
router.get('/products/:productId/reviews', ctrl.list);
router.post('/products/:productId/reviews',
authenticate, validate(createReviewSchema), ctrl.create);
router.put('/reviews/:reviewId',
authenticate, validate(updateReviewSchema), ctrl.update);
router.delete('/reviews/:reviewId',
authenticate, ctrl.remove);
module.exports = router;
// === 4. Test API ===
// curl http://localhost:3000/api/products/507f1f77bcf86cd799439021/reviews
// curl -X POST http://localhost:3000/api/products/507f1f77bcf86cd799439021/reviews \
// -H "Authorization: Bearer <token>" \
// -H "Content-Type: application/json" \
// -d '{"content":"Great product!","rating":5}'
Resultado: Uma API RESTful completa para comentários que oferece suporte à listagem, criação, atualização e exclusão; a validação Joi garante a validade dos dados; e a autenticação protege as permissões dos autores.
Padrões de projeto para a camada de controladores: O código acima ilustra os padrões de projeto padrão para a camada de controladores — 1. Cada função exportada corresponde a um endpoint de rota (exports.list → GET, exports.create → POST); 2. Todas as operações assíncronas são envolvidas em um bloco try-catch, e qualquer exceção é tratada de maneira uniforme, passando o erro para o middleware de tratamento de erros por meio de next(err); 3. Retorno antecipado em caso de falha na validação (404/403) para impedir a entrada na lógica de negócios; 4. Adicione filtragem de permissão às condições de consulta (userId: req.user._id garante que os usuários só possam modificar seus próprios comentários); 5. Uso de lean() para reduzir a sobrecarga de documentos do Mongoose (cenários somente leitura não exigem o rastreamento de alterações do Mongoose). Esses padrões mantêm o Controlador simples — validação → consulta → resposta —, com cada função variando de 10 a 20 linhas.
Padrões de combinação para rotas e middleware: As definições de rotas ilustram estratégias de combinação de middleware — 1. Middleware comum (router.use(authenticate)): Todas as rotas exigem autenticação; 2. Middleware no nível da rota (validate(createReviewSchema)): Apenas rotas específicas exigem validação; 3. Ordem de execução do middleware: authenticate → validate → controlador, organizada por dependência (a validação depende do resultado da autenticação req.user); 4. Middleware condicional: algumas rotas não exigem autenticação (por exemplo, GET list), portanto, são colocadas antes de authenticate ou utilizam middleware de autenticação opcional. Esse padrão de composição torna a pilha de middleware para cada rota clara e legível.
❓ Perguntas Frequentes
P: Como faço para escolher entre PUT e PATCH? R: O PUT substitui o recurso inteiro (os campos não especificados são perdidos), enquanto o PATCH realiza uma atualização parcial. Recomenda-se usar o PATCH.
P: Devo usar
pageoucursorpara a paginação? R: Usepagepara paginação superficial ecursorpara paginação profunda (para garantir um desempenho consistente).
P: O que é melhor, o joi ou o express-validator? R: O joi é mais poderoso (no estilo Schema), enquanto o express-validator é mais fácil de integrar.
📖 Resumo
- A semântica dos seis métodos HTTP RESTful
- Códigos de status HTTP: 200/201/204/400/401/403/404/409/500
- Formato padronizado de resposta: sucesso + dados + metadados + erro
- joi Pedido de verificação
- Endpoints CRUD completos (GET/POST/PUT/PATCH/DELETE)
- Middleware para tratamento de erros
📝 Exercícios
- Exercício básico (⭐): Implemente rotas para os 6 métodos HTTP (CRUD de produtos).
- Problema básico (⭐): Implementar a validação de solicitações usando o Joi (obrigatório, comprimento, enumeração).
- Exercício avançado (⭐⭐): Implemente um formato unificado de resposta (sucesso/dados/meta/erro).
- Exercício avançado (⭐⭐): Implemente uma API completa para comentários (CRUD + Curtidas + Tratamento de erros).
- Desafio (⭐⭐⭐): Desenvolver uma API completa de comércio eletrônico (produtos + avaliações + pedidos + usuários + autenticação JWT).