Validar o input/entrada de dados em aplicações é uma das poucas verdades absolutas no mundo da programação. Você simplesmente não pode, jamais, confiar que os dados enviados para sua aplicação processar estarão no formato e tipo corretos que você precisa.
Mesmo que você adicione validação na sua interface ou front-end, dificilmente você conseguirá garantir que, em algum momento, algum usuário descuidado ou malicioso acabe lhe enviando dados fora do padrão que você espera e isso pode ser desastroso.
Quando você pensa em desenvolvimento web então, nem se fala. Existem muitas formas de burlar validações colocadas no front-end, seja através de maneiras simples como desativando javascript ou inspecionando e alterando o HTML da página e muito mais, a formas mais complexas como capturando requisições HTTP do front para o backend e alterando as mesmas (Man in the Middle Attack).
No caso do Node.js, mais especificamente do web framework Express, o mais usado nesta plataforma, os dados recebidos são provenientes dos objetos body, param e query, que aceitam apenas texto de qualquer formato. Se você está fazendo web APIs RESTful, esta validação que vou ensinar neste tutorial será a sua primeira e muitas vezes única defesa contra o descuido e as más intenções de quem for utilizar as suas APIs.
Embora você possa fazer todas suas validações manualmente, usando ‘ifs’ e outras práticas básicas de programação, isso geralmente leva a muito código repetitivo, complexo de testar unitariamente e arquiteturas pouco elegantes. Usaremos neste tutorial o pacote @hapi/joi, o mais popular atualmente para validação de dados e espero gerar alguns insights de como você pode levar validação a sério na sua aplicação sem torná-la um espaguete Javascript.
Então vamos lá!
Atenção: Este tutorial requer conhecimento prévio de Node.js, que podem ser obtidos em outros artigos, em meus livros ou meu curso online, onde inclusive possui uma aula em vídeo ensinando a fazer a mesma coisa.
#1 – Estruturando o Projeto
Vamos começar de maneira bem simples, criando um novo projeto Node.js para mostrar o básico do joi e mais tarde espero poder trazer conceitos mais avançados em artigos futuros.
Crie uma pasta chamada node-validation no seu computador e navegue até ela via terminal para criar um projeto dentro dela, usando o npm init. Crie também uma index.js aí.
Adicione no projeto a extensão que vamos usar, com o comando abaixo:
1 2 3 |
npm i joi |
O Joi usa uma linguagem descritiva para criar schemas de validação (ao contrário de estruturas imperativas como ifs) e é um desses schemas que vamos criar no início do seu index.js, usando o código abaixo de exemplo, que será explicado a seguir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//index.js const Joi = require('joi'); const userSchema = Joi.object({ username: Joi.string() .alphanum() .min(3) .max(30) .required(), password: Joi.string() .pattern(/^[a-zA-Z0-9]{3,30}$/), birth_year: Joi.number() .integer() .min(1900) .max(2001), email: Joi.string() .email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } }) }) |
Na primeira linha, apenas carregamos uma constante ‘Joi’ que será utilizada para criar nossos schemas de validação (através de Joi.object) e o primeiro deles será o userSchema, que possuirá todas as regras de validação de usuários cadastrados no sistema.
A primeira regra é relacionada ao username, que será uma string (Joi.string), alfanumérica (alphanum), com no mínimo 3 no máximo 30 caracteres (min e max) e que é uma propriedade obrigatória (required).
A segunda regra é no password, que também deve ser uma string mas deve seguir o padrão (pattern) declarado sob a forma de uma expressão regular que aceita de 3 a 30 letras maiúsculas, minúsculas e números.
Já a terceira regra, birth_year define que esse dado deve ser um número (Joi.number), inteiro (integer) e com o seu valor entre 1900 e 2001 (min e max).
A última regra, email, foi colocada com o intuito de mostrar o poder desta biblioteca que possui muitas funções utilitárias para facilitar a sua vida, como as já mostradas anteriormente (string, number, required, etc) e aqui temos a função email que recebe configurações por parâmetro como minDomainSegments (para definir o número de partes mínima no domínio do e-mail) e tlds (Top Level Domains – para permitir ou bloquear extensões específicas de domínio).
Agora, para usar este schema de validação é muito simples, basta chamar o objeto userSchema passando por parâmetro na function validate o objeto a ser validado, sendo importante que os nomes das propriedades coincidam.
1 2 3 4 5 6 7 |
const val1 = userSchema.validate({ username: 'abc', birth_year: 1994 }); console.log("passou na validação: " + !val1.error) const val2 = userSchema.validate({}); console.log("passou na validação: " + !val2.error) |
No primeiro teste, o val1 virá com o erro vazio, pois o objeto passado por parâmetro atende aos requisitos do userSchema. Já no segundo teste, val2 terá um erro pois não foi passado o username, que é obrigatório segundo as regras que criamos (required).
Rode este projeto simples no terminal e você verá essas validações em ação.
Antes de prosseguir, sugiro que “desafie” as validações do Joi com outros testes que cubram mais cenários possíveis, só para ver que de fato ele funciona como deveria.
#2 – Reorganizando o Projeto
Agora que entendemos o básico, é importante que a gente separe a lógica de validação da lógica de aplicação.
Para fazer isso, crie outro arquivo na sua aplicação que chamaremos de validations.js, com o seguinte conteúdo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
//validations.js const Joi = require('joi'); const userSchema = Joi.object({ username: Joi.string() .alphanum() .min(3) .max(30) .required(), password: Joi.string() .pattern(/^[a-zA-Z0-9]{3,30}$/), birth_year: Joi.number() .integer() .min(1900) .max(2001), email: Joi.string() .email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } }) }) module.exports = { userSchema } |
Note que eu extraí a lógica de validação do index.js para o validations.js e apenas adicionei um module.exports no final, para expor o schema para fora deste módulo JS.
Agora o nosso index.js ficará bem menor, uma vez que a aplicação não faz nada exceto chamar as validações pré-configuradas.
1 2 3 4 5 6 7 8 9 10 |
//index.js const validations = require("./validations"); const val1 = validations.userSchema.validate({ username: 'abc', birth_year: 1994 }); console.log("Passou na validação: " + !val1.error); const val2 = validations.userSchema.validate({}); console.log("Passou na validação: " + !val2.error); |
Rode novamente esta aplicação no terminal e ela deve continuar funciona como anteriormente, mas agora com uma organização mais elegante!
E por hoje é só pessoal. No artigo seguinte, vamos explorar formas mais elaboradas de validação, incluindo usando middlewares do Express em aplicações web, clique aqui para ler.
Um abraço e sucesso!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.