Leodario.com

Leodario.com – Tudo sobre Tecnologia

Aula 51 – API JavaScript, Node.js e Express – Data Validation – Joi, Yup, express-validator

Imagem destacada da aula de API

Introdução (3 min)

Caros futuros mestres das APIs, sejam bem-vindos à Aula 51! Preparem-se para desvendar um dos pilares mais significativos no desenvolvimento de serviços web robustos: a validação de dados. Imaginem que sua API é um ponto de entrada para um sistema altamente complexo, como uma alfândega em um país rigoroso. Não se pode simplesmente permitir que qualquer pessoa entre com qualquer tipo de bagagem, não é mesmo? É crucial verificar a identidade, o propósito da visita e o conteúdo da bagagem para garantir a segurança e a ordem.

Essa é a essência da validação de dados em APIs. Ela é o porteiro digital que examina minuciosamente todas as informações que chegam à sua aplicação. Sem uma verificação adequada, sua API estaria vulnerável a dados maliciosos, inconsistentes ou simplesmente errados, o que pode levar a falhas críticas, brechas de segurança, corrupção de informações no banco de dados e uma experiência de usuário (UX) terrível. É um componente fundamental para a integridade, confiabilidade e segurança de qualquer sistema moderno.

Nesta aula, nosso objetivo é capacitá-los com ferramentas e conhecimentos para implementar uma validação de entrada de dados robusta no seu ecossistema Node.js/Express. Vamos explorar três bibliotecas de ponta – Joi, Yup e express-validator – cada uma com suas particularidades, permitindo que vocês escolham a mais adequada para cada cenário. Ao final, terão a capacidade de aplicar esquemas de validação complexos, tratar erros de forma elegante e construir APIs que resistem ao teste do tempo e dos dados mal-intencionados.

No contexto Node.js e Express, a validação de dados se integra perfeitamente como um middleware, um passo intermediário entre a chegada da requisição e o processamento final pelo seu controlador (handler). Isso possibilita interceptar e verificar os dados antes mesmo que eles cheguem à lógica de negócio, garantindo que apenas informações válidas avancem.

Conceito Fundamental (7 min)

A validação de dados é o processo de assegurar que a entrada fornecida por um usuário ou outro sistema externo atenda a um conjunto de regras predefinidas antes de ser processada ou armazenada. Em uma API RESTful, essa entrada geralmente se manifesta no corpo (body) da requisição, nos parâmetros (params) da URL ou nas consultas (query params). O objetivo primordial é manter a integridade e a segurança dos dados do seu sistema.

A terminologia correta da indústria nos aponta para conceitos como:

    • Schema (Esquema): Um modelo ou blueprint que define a estrutura, os tipos de dados e as restrições esperadas para um conjunto de dados. Por exemplo, um esquema de usuário pode especificar que o campo email deve ser uma string em formato de email válido e o campo senha deve ter no mínimo 8 caracteres.
    • Payload: Refere-se aos dados enviados no corpo de uma requisição HTTP, que são o principal alvo da validação.
    • Middleware: Funções que têm acesso ao objeto de requisição (req), ao objeto de resposta (res) e à próxima função middleware no ciclo de requisição-resposta de um aplicativo Express. É o local ideal para executar a validação.
    • Sanitização (Sanitization): O processo de limpar ou filtrar dados de entrada para remover caracteres indesejados ou potencialmente perigosos, como tags HTML (prevenção de XSS – Cross-Site Scripting) ou espaços em branco excessivos.
    • Escaping: A técnica de modificar caracteres especiais em uma string para que eles sejam interpretados literalmente e não como parte de um comando ou sintaxe, útil para prevenir ataques de injeção.

Casos de uso reais em produção são abundantes e demonstram a relevância da validação:

    • Registro de Usuário: Garantir que o email seja único e válido, a senha atenda aos requisitos de complexidade, o nome não seja vazio, e assim por diante.
    • Criação/Atualização de Produtos: Verificar se o preço é um número positivo, o nome do produto tem um comprimento razoável, a descrição não contém caracteres proibidos.
    • Filtros de Busca: Validar se os parâmetros de ordenação ou paginação são números inteiros esperados para evitar erros no banco de dados.
    • Upload de Arquivos: Embora complexo, a validação inicial pode verificar tipo de arquivo, tamanho máximo, etc.

A validação de dados se integra de forma sinérgica com diversas tecnologias:

    • Com a validação front-end, ela atua como uma segunda linha de defesa. Embora o front-end possa oferecer feedback imediato ao usuário, a validação no back-end é a única que pode ser considerada autoritativa e segura, pois o front-end pode ser facilmente manipulado.
    • Com bancos de dados, a validação de dados no back-end previne a inserção de informações inconsistentes ou inválidas, complementando as restrições de esquema que podem existir no próprio banco (como NOT NULL, UNIQUE, tipos de coluna).
    • Com testes automatizados, a validação robusta simplifica a escrita de testes, pois a lógica de negócio pode assumir que os dados de entrada já estão em um formato aceitável.

As vantagens de uma validação de dados rigorosa são notáveis:

    • Robustez: O sistema se torna mais resistente a entradas inesperadas, reduzindo a chance de crashes e comportamentos imprevisíveis.
    • Segurança: Minimiza o risco de ataques como injeção de SQL (SQL Injection), Cross-Site Scripting (XSS) e outros, ao impedir que dados maliciosos cheguem aos componentes sensíveis.
    • Integridade dos Dados: Garante que apenas dados válidos e bem-estruturados sejam armazenados, mantendo a consistência do seu banco de dados.
    • Melhor Experiência do Usuário (UX): Fornece mensagens de erro claras e informativas, orientando o usuário sobre como corrigir suas entradas.
    • Manutenibilidade do Código: Simplifica a lógica de negócio, pois ela não precisa se preocupar em lidar com dados inválidos, tornando o código mais limpo e fácil de manter.

Contudo, existem algumas desvantagens a considerar:

    • Overhead de Desempenho: Embora geralmente mínimo, o processo de validação adiciona uma pequena sobrecarga de processamento a cada requisição.
    • Curva de Aprendizado: Ferramentas mais poderosas podem exigir um tempo inicial para domínio da sintaxe e dos conceitos.
    • Verboso em Demasia: Se não for bem organizada, a lógica de validação pode tornar-se repetitiva e sobrecarregar os arquivos de rota.

Implementação Prática (10 min)

Vamos agora colocar a mão na massa e desenvolver um exemplo prático que demonstre a validação de dados utilizando as bibliotecas express-validator, Joi e Yup. Nosso cenário será um simples gerenciamento de usuários, com um endpoint para criar um novo usuário.

Primeiro, crie um novo diretório para o seu projeto e inicialize-o:

mkdir validacao-api
cd validacao-api
npm init -y

Agora, instale as dependências essenciais:

npm install express express-validator joi yup

Vamos criar a estrutura básica do nosso projeto:


validacao-api/
├── package.json
├── index.js
├── routes/
│   └── usuarioRoutes.js
├── middlewares/
│   └── validationMiddleware.js
└── schemas/
    └── usuarioSchema.js

Primeiro, configure o arquivo index.js, que será o ponto de entrada da nossa aplicação Express:

// index.js
const express = require('express'); // Importa o módulo 'express' para criar o servidor web
const usuarioRoutes = require('./routes/usuarioRoutes'); // Importa as rotas de usuário que vamos criar

const app = express(); // Inicializa o aplicativo Express const PORT = process.env.PORT || 3000; // Define a porta, usando a variável de ambiente ou 3000 por padrão

// Middleware para fazer o parsing do corpo da requisição em formato JSON app.use(express.json());

// Monta as rotas de usuário no caminho '/api/usuarios' app.use('/api/usuarios', usuarioRoutes);

// Rota de teste simples para verificar se a API está funcionando app.get('/', (req, res) => { res.send('API de validação de dados funcionando!'); });

// Middleware genérico para tratamento de erros (opcional, mas uma boa prática) app.use((err, req, res, next) => { console.error("Erro inesperado:", err.stack); // Registra o erro no console res.status(500).json({ status: 'erro', mensagem: 'Ocorreu um erro interno no servidor.' }); // Retorna um erro 500 });

// Inicia o servidor e escuta na porta especificada app.listen(PORT, () => { console.log(Servidor rodando na porta ${PORT}); console.log('Para testar, use ferramentas como Postman, Insomnia ou cURL.'); });

1. Validação com express-validator

Vamos começar com express-validator, que se integra de forma muito natural com o fluxo de middlewares do Express.

Crie o arquivo routes/usuarioRoutes.js:

// routes/usuarioRoutes.js
const express = require('express');
const { body, validationResult } = require('express-validator'); // Importa 'body' para validar o corpo da requisição e 'validationResult' para coletar os erros
const router = express.Router(); // Cria um novo objeto Router do Express

// Simulação de um banco de dados de usuários let usuarios = []; let nextId = 1;

/* Middleware para lidar com os resultados da validação do express-validator. Se houver erros, ele formata e envia uma resposta 400. / const handleValidationErrors = (req, res, next) => { const errors = validationResult(req); // Coleta todos os erros de validação da requisição if (!errors.isEmpty()) { // Se a lista de erros não estiver vazia // Loga os erros para depuração, é uma boa prática para identificar problemas console.error("Erros de validação (express-validator):", errors.array()); // Retorna uma resposta HTTP 400 (Bad Request) com os erros formatados return res.status(400).json({ status: 'erro', mensagem: 'Dados inválidos na requisição.', erros: errors.array() // Envia o array de erros para o cliente }); } next(); // Se não houver erros, passa para o próximo middleware ou para o handler da rota };

// Rota POST para criar um novo usuário com validação express-validator router.post( '/', // Caminho da rota [ // Middleware de validação para o campo 'nome' body('nome') .trim() // Remove espaços em branco do início e fim .notEmpty().withMessage('O nome é um campo obrigatório.') // Garante que não está vazio .isLength({ min: 3, max: 100 }).withMessage('O nome deve ter entre 3 e 100 caracteres.'), // Verifica o comprimento

// Middleware de validação para o campo 'email' body('email') .trim() .notEmpty().withMessage('O email é um campo obrigatório.') .isEmail().withMessage('Formato de email inválido.') // Verifica se é um email válido .normalizeEmail(), // Normaliza o email (ex: minusculo)

// Middleware de validação para o campo 'senha' body('senha') .notEmpty().withMessage('A senha é um campo obrigatório.') .isLength({ min: 6 }).withMessage('A senha deve ter no mínimo 6 caracteres.'), // Requisito de comprimento

handleValidationErrors // Nosso middleware customizado para lidar com os erros ], // Handler da rota (executado apenas se a validação passar) (req, res) => { const { nome, email, senha } = req.body; // Pega os dados validados do corpo da requisição

// Cria o novo usuário com um ID único const novoUsuario = { id: nextId++, nome, email, senha }; usuarios.push(novoUsuario); // Adiciona ao nosso "banco de dados"

// Retorna uma resposta HTTP 201 (Created) com o usuário criado res.status(201).json({ status: 'sucesso', mensagem: 'Usuário criado com sucesso!', usuario: { id: novoUsuario.id, nome: novoUsuario.nome, email: novoUsuario.email } // Não retorna a senha por segurança }); } );

// Rota GET para listar todos os usuários (para verificação) router.get('/', (req, res) => { res.status(200).json({ status: 'sucesso', usuarios: usuarios.map(u => ({ id: u.id, nome: u.nome, email: u.email })) }); });

module.exports = router; // Exporta o router para ser usado em index.js

Para testar (com cURL):

Cenário 1: Sucesso

curl -X POST -H "Content-Type: application/json" -d '{"nome": "Alice Silva", "email": "[email protected]", "senha": "senha123"}' http://localhost:3000/api/usuarios

Cenário 2: Erros de validação (email inválido, senha curta)

curl -X POST -H "Content-Type: application/json" -d '{"nome": "Bob", "email": "bob@", "senha": "123"}' http://localhost:3000/api/usuarios

2. Validação com Joi

Joi é uma biblioteca de validação de esquema muito poderosa e flexível. Para integrá-la ao Express, geralmente criamos um middleware genérico.

Crie o arquivo schemas/usuarioSchema.js para definir o esquema Joi:

// schemas/usuarioSchema.js
const Joi = require('joi'); // Importa a biblioteca Joi

// Define o esquema de validação para um usuário const criarUsuarioSchemaJoi = Joi.object({ // O campo 'nome' deve ser uma string, ser obrigatório e ter entre 3 e 100 caracteres nome: Joi.string().trim().min(3).max(100).required().messages({ 'string.base': 'O nome deve ser uma string.', 'string.empty': 'O nome é um campo obrigatório.', 'string.min': 'O nome deve ter no mínimo {#limit} caracteres.', 'string.max': 'O nome deve ter no máximo {#limit} caracteres.', 'any.required': 'O nome é um campo obrigatório.' }), // O campo 'email' deve ser uma string em formato de email válido e ser obrigatório email: Joi.string().trim().email().required().messages({ 'string.base': 'O email deve ser uma string.', 'string.empty': 'O email é um campo obrigatório.', 'string.email': 'O formato do email é inválido.', 'any.required': 'O email é um campo obrigatório.' }), // O campo 'senha' deve ser uma string, ser obrigatório e ter no mínimo 6 caracteres senha: Joi.string().min(6).required().messages({ 'string.base': 'A senha deve ser uma string.', 'string.empty': 'A senha é um campo obrigatório.', 'string.min': 'A senha deve ter no mínimo {#limit} caracteres.', 'any.required': 'A senha é um campo obrigatório.' }) });

module.exports = { criarUsuarioSchemaJoi // Exporta o esquema para ser usado em outros arquivos };

Crie o arquivo middlewares/validationMiddleware.js para o middleware genérico de validação:

// middlewares/validationMiddleware.js
// Este middleware genérico pode ser usado para Joi ou Yup

const validate = (schema) => async (req, res, next) => { try { // Valida o corpo da requisição contra o esquema fornecido // { abortEarly: false } garante que todos os erros sejam coletados, não apenas o primeiro const validatedBody = await schema.validateAsync(req.body, { abortEarly: false }); req.body = validatedBody; // Opcional: substitui o corpo da requisição pelo objeto validado/limpo next(); // Se a validação for bem-sucedida, passa para o próximo middleware/handler } catch (error) { // Loga o erro completo para depuração. Em produção, você pode querer um logger mais robusto. console.error("Erro de validação (Joi/Yup):", error.details);

// Formata os erros para um array mais amigável, similar ao express-validator const errosFormatados = error.details.map(detail => ({ campo: detail.context.key, // Nome do campo com erro mensagem: detail.message.replace(/['"]/g, ''), // Mensagem de erro, removendo aspas valor_recebido: detail.context.value // Valor que causou o erro }));

// Retorna uma resposta HTTP 400 (Bad Request) com os erros return res.status(400).json({ status: 'erro', mensagem: 'Dados inválidos na requisição.', erros: errosFormatados }); } };

module.exports = validate; // Exporta a função de middleware

Agora, modifique routes/usuarioRoutes.js para usar o Joi. Você pode comentar a rota anterior ou criar uma nova rota para Joi. Vou adicionar uma nova para demonstrar ambos.

// routes/usuarioRoutes.js (CONTINUAÇÃO)
// ... imports existentes ...
const validate = require('../middlewares/validationMiddleware'); // Importa o middleware genérico
const { criarUsuarioSchemaJoi } = require('../schemas/usuarioSchema'); // Importa o esquema Joi

// ... handleValidationErrors ...

// Rota POST para criar um novo usuário com validação Joi router.post( '/joi', // Novo caminho da rota para demonstrar Joi validate(criarUsuarioSchemaJoi), // Usa o middleware genérico com o esquema Joi // Handler da rota (executado apenas se a validação passar) (req, res) => { const { nome, email, senha } = req.body; // Pega os dados validados do corpo da requisição

// Cria o novo usuário com um ID único const novoUsuario = { id: nextId++, nome, email, senha }; usuarios.push(novoUsuario); // Adiciona ao nosso "banco de dados"

// Retorna uma resposta HTTP 201 (Created) com o usuário criado res.status(201).json({ status: 'sucesso', mensagem: 'Usuário criado com sucesso com Joi!', usuario: { id: novoUsuario.id, nome: novoUsuario.nome, email: novoUsuario.email } }); } ); // ... router.get ...

Para testar (com cURL):

Cenário 1: Sucesso (Joi)

curl -X POST -H "Content-Type: application/json" -d '{"nome": "Carlos Pereira", "email": "[email protected]", "senha": "senhasecreta"}' http://localhost:3000/api/usuarios/joi

Cenário 2: Erros de validação (Joi)

curl -X POST -H "Content-Type: application/json" -d '{"nome": "Ca", "email": "carlosexample.com", "senha": "abc"}' http://localhost:3000/api/usuarios/joi

3. Validação com Yup

Yup é outra biblioteca de validação baseada em esquemas, conhecida por sua sintaxe fluente e intuitiva. A integração com o Express é muito similar à do Joi, usando o mesmo middleware genérico.

Atualize schemas/usuarioSchema.js para incluir o esquema Yup:

// schemas/usuarioSchema.js (CONTINUAÇÃO)
// ... import Joi ...
const Yup = require('yup'); // Importa a biblioteca Yup

// Define o esquema de validação para um usuário usando Yup const criarUsuarioSchemaYup = Yup.object().shape({ // O campo 'nome' deve ser uma string, ser obrigatório e ter entre 3 e 100 caracteres nome: Yup.string() .trim() // Remove espaços em branco .min(3, 'O nome deve ter no mínimo 3 caracteres.') // Mensagem personalizada para min .max(100, 'O nome deve ter no máximo 100 caracteres.') // Mensagem personalizada para max .required('O nome é um campo obrigatório.'), // Mensagem personalizada para required

// O campo 'email' deve ser uma string em formato de email válido e ser obrigatório email: Yup.string() .trim() .email('Formato de email inválido.') // Mensagem personalizada para email .required('O email é um campo obrigatório.'),

// O campo 'senha' deve ser uma string, ser obrigatório e ter no mínimo 6 caracteres senha: Yup.string() .min(6, 'A senha deve ter no mínimo 6 caracteres.') .required('A senha é um campo obrigatório.') });

module.exports = { criarUsuarioSchemaJoi, criarUsuarioSchemaYup // Exporta o esquema Yup também };

Modifique routes/usuarioRoutes.js para incluir a rota com Yup:

// routes/usuarioRoutes.js (CONTINUAÇÃO)
// ... imports existentes ...
const { criarUsuarioSchemaYup } = require('../schemas/usuarioSchema'); // Importa o esquema Yup

// ... handleValidationErrors ...

// Rota POST para criar um novo usuário com validação Yup router.post( '/yup', // Novo caminho da rota para demonstrar Yup validate(criarUsuarioSchemaYup), // Usa o middleware genérico com o esquema Yup // Handler da rota (executado apenas se a validação passar) (req, res) => { const { nome, email, senha } = req.body; // Pega os dados validados do corpo da requisição

// Cria o novo usuário com um ID único const novoUsuario = { id: nextId++, nome, email, senha }; usuarios.push(novoUsuario); // Adiciona ao nosso "banco de dados"

// Retorna uma resposta HTTP 201 (Created) com o usuário criado res.status(201).json({ status: 'sucesso', mensagem: 'Usuário criado com sucesso com Yup!', usuario: { id: novoUsuario.id, nome: novoUsuario.nome, email: novoUsuario.email } }); } ); // ... router.get ...

Para testar (com cURL):

Cenário 1: Sucesso (Yup)

curl -X POST -H "Content-Type: application/json" -d '{"nome": "Diana Souza", "email": "[email protected]", "senha": "passwordforte"}' http://localhost:3000/api/usuarios/yup

Cenário 2: Erros de validação (Yup)

curl -X POST -H "Content-Type: application/json" -d '{"nome": "Di", "email": "dianamail.net", "senha": "abc"}' http://localhost:3000/api/usuarios/yup

Melhores Práticas Enterprise e Configurações HostGator Plano M

    • Separação de Preocupações: Como demonstrado, os esquemas de validação (schemas/usuarioSchema.js) e o middleware de validação (middlewares/validationMiddleware.js) são separados da lógica das rotas. Isso otimiza a organização e a reusabilidade.
    • Middleware de Validação Genérico: O validationMiddleware.js é um padrão enterprise. Ele permite que você utilize qualquer biblioteca de validação baseada em esquema (Joi, Yup) de forma intercambiável, apenas passando o esquema correto.
    • Tratamento de Erros Padronizado: As respostas de erro 400 Bad Request com um corpo JSON que contém status: 'erro' e um array erros são um padrão amplamente aceito. Isso facilita para os clientes da API entenderem e processarem os erros.
    • Sanitização:express-validator e Joi/Yup (com trim()) oferecem métodos para sanitizar os dados (como remover espaços em branco). Em cenários mais complexos, considere bibliotecas como validator.js para sanitizações mais profundas ou escapar HTML.
    • Logging Profissional: Embora tenhamos usado console.error para fins didáticos, em um ambiente de produção enterprise, seria vital integrar bibliotecas de logging como Winston ou Pino. Elas fornecem logs estruturados, níveis de severidade e a capacidade de direcionar logs para diferentes destinos (arquivos, serviços de nuvem).
    • Configurações HostGator Plano M:
      • O código acima é 100% compatível. O HostGator Plano M oferece suporte a aplicações Node.js.
      • Para deploy, certifique-se de que seu package.json esteja correto e liste todas as dependências. O HostGator executará npm install automaticamente.
      • Use process.env.PORT para definir a porta da sua aplicação. O ambiente de hospedagem injetará a porta correta.
      • A estrutura de diretórios apresentada é portátil e funcional em qualquer ambiente Node.js.

Exercício Hands-On (5 min)

Desafio Prático

Sua tarefa é desenvolver um novo endpoint na API para atualizar um produto existente.
Crie uma rota PUT /api/produtos/:id que receberá um ID do produto nos parâmetros da URL e os dados do produto (nome e preço) no corpo da requisição.

Requisitos de Validação:

    • O id do produto na URL deve ser um número inteiro e positivo.
    • O nome do produto no corpo da requisição deve ser uma string, não pode ser vazio e ter entre 3 e 100 caracteres.
    • O preco do produto no corpo da requisição deve ser um número e ser maior que zero.

Escolha uma das bibliotecas que vimos (Joi, Yup ou express-validator) para implementar a validação.

Solução Detalhada Passo a Passo

Vamos usar o Joi para a solução.

  • Crie um “banco de dados” de produtos na raiz do seu routes/usuarioRoutes.js (ou crie um novo routes/produtoRoutes.js e adicione em index.js). Por simplicidade, adicionaremos no usuarioRoutes.js para não alterar muito o index.js.
    // routes/usuarioRoutes.js (ADICIONE NO INÍCIO, ANTES DAS ROTAS)
    // ... imports existentes ...

// Simulação de banco de dados para usuários (já existia) let usuarios = []; let nextId = 1;

// NOVO: Simulação de banco de dados para produtos let produtos = [ { id: 101, nome: "Notebook Gamer", preco: 7500.00 }, { id: 102, nome: "Mouse Óptico", preco: 150.00 } ]; let nextProdutoId = 103;

// ... handleValidationErrors ...

  • Crie um esquema de validação para produtos em schemas/usuarioSchema.js (ou crie schemas/produtoSchema.js).
    // schemas/usuarioSchema.js (ADICIONE NO FINAL)
    // ... esquemas de usuário existentes ...

const atualizarProdutoSchemaJoi = Joi.object({ nome: Joi.string().trim().min(3).max(100).messages({ 'string.base': 'O nome do produto deve ser uma string.', 'string.empty': 'O nome do produto não pode ser vazio.', 'string.min': 'O nome do produto deve ter no mínimo {#limit} caracteres.', 'string.max': 'O nome do produto deve ter no máximo {#limit} caracteres.' }), preco: Joi.number().positive().messages({ 'number.base': 'O preço do produto deve ser um número.', 'number.positive': 'O preço do produto deve ser maior que zero.' }) }).min(1).messages({ 'object.min': 'Pelo menos um campo (nome ou preco) deve ser fornecido para atualização.' }); // Garante que pelo menos um campo seja enviado

module.exports = { criarUsuarioSchemaJoi, criarUsuarioSchemaYup, atualizarProdutoSchemaJoi // EXPORTE O NOVO ESQUEMA };

  • Adicione a rota PUT /api/produtos/:id em routes/usuarioRoutes.js. Precisaremos de um middleware para validar o id nos parâmetros e o middleware validate para o corpo da requisição.
    // routes/usuarioRoutes.js (ADICIONE ANTES DE 'module.exports = router;')

// Middleware para validar o ID do produto nos parâmetros da URL const validateProdutoId = (req, res, next) => { const id = parseInt(req.params.id, 10); // Converte o ID da URL para número inteiro if (isNaN(id) || id <= 0) { // Verifica se não é um número ou é menor/igual a zero console.error("Erro de validação: ID de produto inválido."); return res.status(400).json({ status: 'erro', mensagem: 'ID de produto inválido. Deve ser um número inteiro positivo.' }); } req.params.id = id; // Substitui o ID da string pelo número inteiro validado next(); // Continua para o próximo middleware/handler };

router.put( '/produtos/:id', // Caminho da rota com parâmetro de ID validateProdutoId, // Primeiro valida o ID da URL validate(atualizarProdutoSchemaJoi), // Depois valida o corpo da requisição com Joi (req, res) => { const productId = req.params.id; // Pega o ID validado const { nome, preco } = req.body; // Pega os dados validados do corpo

const produtoIndex = produtos.findIndex(p => p.id === productId); // Encontra o índice do produto if (produtoIndex === -1) { console.warn(Produto com ID ${productId} não encontrado para atualização.); return res.status(404).json({ status: 'erro', mensagem: Produto com ID ${productId} não encontrado. }); }

// Atualiza o produto com os novos dados (apenas os que foram fornecidos) if (nome) produtos[produtoIndex].nome = nome; if (preco) produtos[produtoIndex].preco = preco;

const produtoAtualizado = produtos[produtoIndex];

res.status(200).json({ status: 'sucesso', mensagem: 'Produto atualizado com sucesso!', produto: produtoAtualizado }); } );

  • Inicie o servidor (node index.js).

Como Testar e Validar o Resultado

Use cURL ou uma ferramenta como Postman/Insomnia.

Listar produtos existentes:

curl http://localhost:3000/api/usuarios/produtos

Saída esperada: Uma lista de produtos, incluindo o Notebook Gamer (ID 101) e o Mouse Óptico (ID 102).

Cenário 1: Atualização com Sucesso

curl -X PUT -H "Content-Type: application/json" -d '{"nome": "Notebook Pro", "preco": 8000.00}' http://localhost:3000/api/usuarios/produtos/101

Saída esperada: Um JSON com status: 'sucesso' e o produto atualizado.

Cenário 2: Erro de Validação (ID de produto inválido)

curl -X PUT -H "Content-Type: application/json" -d '{"nome": "Teclado Mecânico"}' http://localhost:3000/api/usuarios/produtos/abc

Saída esperada: Um JSON com status: 'erro' e mensagem “ID de produto inválido…”.

Cenário 3: Erro de Validação (nome muito curto, preço negativo)

curl -X PUT -H "Content-Type: application/json" -d '{"nome": "Te", "preco": -50}' http://localhost:3000/api/usuarios/produtos/102

Saída esperada: Um JSON com status: 'erro' e um array erros detalhando as falhas de validação para nome e preco.

Cenário 4: Erro de Validação (corpo vazio)

curl -X PUT -H "Content-Type: application/json" -d '{}' http://localhost:3000/api/usuarios/produtos/102

Saída esperada: Um JSON com status: 'erro' e mensagem Pelo menos um campo (nome ou preco) deve ser fornecido para atualização.

Troubleshooting dos Erros Mais Comuns

    • “Cannot read property ‘nome’ of undefined”: Geralmente indica que o express.json() middleware não está sendo usado, e req.body está vazio. Verifique app.use(express.json()); no index.js.
    • “Headers already sent”: Ocorre se você tentar enviar mais de uma resposta HTTP (ex: res.status().json() e depois next()). Certifique-se de usar return res.status().json() para encerrar a execução da função após enviar a resposta de erro.
    • Erros de sintaxe da biblioteca de validação (Joi/Yup/express-validator): Revise a documentação da biblioteca para a sintaxe correta de cada regra e mensagem.
    • Middleware não chamado: Se sua validação não está sendo executada, verifique a ordem dos middlewares na sua rota. O middleware de validação deve vir antes do handler da rota.
    • console.error vazio para erros: Assegure-se de que a formatação dos erros (error.details para Joi/Yup, errors.array() para express-validator) esteja correta para que o log mostre os detalhes.

Próximos Passos Sugeridos

    • Validação de Parâmetros de Consulta (req.query): Implemente validações para filtros de busca (ex: GET /api/produtos?minPreco=X&categoria=Y).
    • Validação de Upload de Arquivos: Explore bibliotecas como multer e como validar o tipo MIME, tamanho e dimensões de arquivos.
    • Validação de Autenticação/Autorização: Embora seja um tópico diferente, a validação de tokens JWT ou permissões de usuário frequentemente envolve a verificação de formatos e existência de dados no req.headers.
    • Testes Unitários e de Integração: Escreva testes automatizados para seus middlewares de validação, garantindo que eles se comportem como esperado para entradas válidas e inválidas.
    • Integração com ORMs/ODMs: Muitas ferramentas de banco de dados (como Mongoose para MongoDB, Sequelize para SQL) possuem suas próprias funcionalidades de validação. Entenda quando usá-las em conjunto com a validação de entrada da API.

🚀 Pronto para a próxima aula?

Continue sua jornada no desenvolvimento de APIs e domine Node.js & Express!

📚 Ver todas as aulas