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


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.

100%
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).

100%
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.

JAVASCRIPT
// 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.

100%
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.

JAVASCRIPT
// 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 };
JAVASCRIPT
// 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.

JAVASCRIPT
// 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.

JAVASCRIPT
// 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
JAVASCRIPT
// 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

JAVASCRIPT
// === 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 ⭐⭐)

JAVASCRIPT
// 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 leitura
GET /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

JAVASCRIPT
// === 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 page ou cursor para a paginação? R: Use page para paginação superficial e cursor para 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


📝 Exercícios

  1. Exercício básico (⭐): Implemente rotas para os 6 métodos HTTP (CRUD de produtos).
  2. Problema básico (⭐): Implementar a validação de solicitações usando o Joi (obrigatório, comprimento, enumeração).
  3. Exercício avançado (⭐⭐): Implemente um formato unificado de resposta (sucesso/dados/meta/erro).
  4. Exercício avançado (⭐⭐): Implemente uma API completa para comentários (CRUD + Curtidas + Tratamento de erros).
  5. Desafio (⭐⭐⭐): Desenvolver uma API completa de comércio eletrônico (produtos + avaliações + pedidos + usuários + autenticação JWT).
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%