Validação só no navegador não protege: repita a regra no servidor Node
Veja como contornar um plugin de validação com um curl, e como montar no Node uma validação que confere tipo, formato e tamanho, recalcula preço e devolve erro por campo.
O formulário de assinatura da loja não deixa enviar e-mail sem arroba, nome vazio nem plano inexistente. O plugin pinta o campo de vermelho, a equipe testa, aprova e relaxa. Três semanas depois, o banco tem 400 cadastros com e-mail x, nome de 5 mil caracteres e assinaturas do plano premium por R$ 0,00. Ninguém passou pelo formulário: um robô chamou a rota direto. A validação no navegador é conforto para quem preenche. A regra que protege o sistema mora no servidor.
A resposta curta: toda regra que o plugin aplica (obrigatório, formato, tamanho, valores permitidos) precisa existir também no servidor, que confere até o tipo do dado recebido e devolve o erro por campo. Valores que a pessoa não escolhe, como o preço, o servidor recalcula. O plugin, se continuar, usa as mesmas mensagens.
Por que o plugin não é uma cerca
Um plugin de validação, seja jQuery Validate, os atributos nativos required e type="email" ou uma biblioteca de formulário moderna, roda no navegador da pessoa. Tudo que roda ali está sob controle de quem usa o navegador. Basta desligar o JavaScript, apagar o atributo nas ferramentas de desenvolvedor ou, mais comum, nem abrir a página e mandar a requisição direto.
A rota de cadastro é pública. Qualquer pessoa vê no painel de rede do navegador o endereço e o formato do corpo, e repete com um comando:
curl -i -X POST https://loja.exemplo/api/assinaturas \
-H "Content-Type: application/json" \
-d '{"nome":"","email":"x","plano":"premium","preco":0}'
Se a resposta for 201 Created, a validação está só na tela. Esse teste leva trinta segundos e é o melhor diagnóstico de qualquer formulário. Faça antes de ler o resto.
O que o servidor tem de conferir, além do que o plugin confere
O servidor não recebe "o formulário"; recebe um corpo qualquer. Isso cria verificações que o plugin nunca precisou fazer:
- Tipo: num corpo JSON,
emailpode chegar como número, array ou objeto. Chamar.trim()num array derruba a rota com erro 500. - Campos extras: o corpo pode trazer
preco,descontoouadmin. Se o código grava o corpo inteiro, grava tudo isso. - Valores derivados: o preço é consequência do plano. Ele nunca vem da requisição.
- Campos ausentes: campo
disabledno HTML não é enviado no POST. O servidor não pode assumir que ele veio com o valor que a tela mostrava.
Como montar a validação no Node, passo a passo
- Escreva as regras num módulo próprio. Um arquivo só com a função de validação, sem depender de Express nem do DOM, para poder ser usado no servidor e, se quiser, no navegador.
- Confira o tipo antes do formato. Se não é string, já é erro; só depois aplique tamanho e formato.
- Devolva um objeto de erros por campo. A tela precisa saber qual campo marcar, e a API precisa dizer o mesmo.
- Monte o registro só com os campos conhecidos. Nunca espalhe o corpo inteiro no objeto que vai para o banco.
- Recalcule o que é derivado. Preço, total e desconto vêm de tabela do servidor.
- Responda 422 com os erros. Assim o front-end mostra a mensagem certa no campo certo.
// regras.js
const PLANOS = { basico: 1990, premium: 4990 }; // centavos
const FORMATO_EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function texto(valor) {
return typeof valor === 'string' ? valor.trim() : null;
}
export function validarAssinatura(corpo) {
const dados = corpo ?? {};
const erros = {};
const nome = texto(dados.nome);
if (!nome) erros.nome = 'Informe o seu nome.';
else if (nome.length > 120) erros.nome = 'Use no máximo 120 caracteres.';
const email = texto(dados.email);
if (!email) erros.email = 'Informe o seu e-mail.';
else if (email.length > 254 || !FORMATO_EMAIL.test(email)) {
erros.email = 'Confira o e-mail: falta algo como nome@dominio.com.';
}
const plano = texto(dados.plano);
if (!plano || !Object.hasOwn(PLANOS, plano)) erros.plano = 'Escolha um dos planos da lista.';
if (Object.keys(erros).length > 0) return { erros };
return {
assinatura: { nome, email: email.toLowerCase(), plano, precoCentavos: PLANOS[plano] }
};
}
// servidor.js
import express from 'express';
import { validarAssinatura } from './regras.js';
const app = express();
app.use(express.json({ limit: '10kb' }));
app.post('/api/assinaturas', async (req, res) => {
const resultado = validarAssinatura(req.body);
if (resultado.erros) {
return res.status(422).json({ erros: resultado.erros });
}
// grava resultado.assinatura no banco, com consulta parametrizada
res.status(201).json({ plano: resultado.assinatura.plano });
});
app.listen(3000);
Repare em três detalhes. O corpo ?? {} protege contra requisição sem corpo ou com outro tipo de conteúdo, em que req.body pode não existir. O Object.hasOwn impede que alguém mande "plano": "constructor" e pegue uma propriedade herdada do objeto. E o limit do parser de JSON recusa corpos enormes antes de o seu código rodar.
A regex de e-mail é deliberadamente simples. Ela pega o erro de digitação óbvio; a confirmação real de que o e-mail existe é o link de confirmação. Para gravar, use consulta com parâmetro, como no guia de Node.js e MySQL com parâmetro.
As mesmas mensagens na tela e na API
Se o plugin diz "e-mail inválido" e a API diz "invalid format", a pessoa aprende duas verdades. Use as mensagens do objeto erros para preencher os campos quando a resposta for 422. Assim, mesmo quem tem o JavaScript bloqueado ou chegou por outro cliente vê o mesmo texto. O guia sobre a mesma mensagem de erro na tela e na API mostra como mapear isso no front-end. Se a sua pilha é PHP, o equivalente está em validação jQuery repetida no PHP.
Erros comuns
- Confiar em
requiredemaxlengthdo HTML. Somem com um clique nas ferramentas do navegador e não existem para quem chama a rota direto. - Aceitar o preço que veio na requisição. Alguém assina o premium por zero, e você só descobre no fechamento do mês.
- Gravar o corpo inteiro. Campos extras como
admin: trueentram no banco se o modelo aceitar. - Usar
disabledpara "proteger" um valor. Campo desabilitado nem vai no POST;readonlyvai, mas é editável por quem quiser. - Bloquear colar no campo. Não impede robô nenhum e atrapalha quem usa gerenciador de senha ou tecnologia assistiva.
- Responder 500 para dado inválido. Um
.trim()num array derruba a rota e esconde que o problema era do cliente.
Checklist
- O teste com
curle dados inválidos recebe 422, não 201. - Cada regra do plugin tem uma equivalente no servidor.
- O servidor confere o tipo de cada campo antes do formato.
- O registro gravado é montado só com campos conhecidos.
- Preço e outros valores derivados são calculados no servidor.
- A resposta de erro traz uma mensagem por campo, igual à da tela.
- O corpo da requisição tem limite de tamanho.
Abra o painel de rede do navegador, copie a requisição do seu formulário principal como curl e troque um campo por lixo: o servidor aceita o que o formulário recusa?
Referência: http://www.devmedia.com.br/jquery-validate-validando-formularios/26490