Desenvolvimento Back-end Node

Validação e Serialização no NestJS: class-validator e class-transformer

Vamos abordar a utilização de duas biblitecas de serialização e validação fundamentais no NestJS: class-validator e class-transformer

há 1 ano 8 meses

Formação Desenvolvedor Node Full-Stack
Conheça a formação em detalhes

No artigo “Criando o primeiro CRUD com NestJS” desenvolvemos uma aplicação para se comunicar com um banco de dados de cadastro de usuários, porém algo muito importante é saber como podemos utilizar recursos de validação e serialização no NestJS.

Para isto no NestJS há duas bibliotecas que nos auxiliam nestas tarefas de forma eficiente e prática: O class-validator e class-transformer, onde podemos consultar mais informações na própria documentação do NestJS.

Vamos ver como utilizar essas bibliotecas? 😀

TypeScript - Fundamentos
Curso TypeScript - Fundamentos
Conhecer o curso

Instalação das bibliotecas class-validator e class-transformer

Primeiramente, vamos reutilizar o nosso projeto do artigo “Criando o primeiro CRUD com NestJS” como exemplo, você também pode obter o projeto neste repositório do github.

Ótimo, com o projeto em sua máquina, vamos instalar as bibliotecas, para isto basta utilizar o comando:

npm i class-validator class-transformer

Validação com o class-validator

Após a instalação vamos utilizar o class-validator para validar algumas informações no cadastro de usuários da nossa aplicação. O class-validator trás uma série de decorators que podemos utilizar, que podem ser consultados na documentação do class-validator.

Neste caso, vamos utilizar as validações nos arquivos DTO que são responsáveis pelos dados das requisições da aplicação, portanto no arquivo create-user.dto.ts temos duas propriedades: nome e idade. Perceba que não há nenhuma validação, portanto nossa aplicação irá aceitar cadastros que estejam vazios, ou sem limite de caracteres e etc. Vamos melhorar isso!

Na propriedade nome, a validação será a seguinte: não pode ser vazia, e precisa ter entre 3 e 10 caracteres, portanto, podemos utilizar o decorator @IsNotEmpty() e o dacorator @Length(), ficando da seguinte forma:

import { IsNotEmpty, Length } from 'class-validator';

export class CreateUserDto {
  @IsNotEmpty({ message: 'Nome não pode ser vazio' })
  @Length(3, 10, { message: 'Nome precisa ter entre 3 e 10 caracteres' })
  nome: string;

  @IsNotEmpty()
  idade: number;
}

Repare que importamos o IsNotEmpty e Lenght da biblioteca class-validator e utilizamos na propriedade nome. Por ser um decorator, é possível passar alguns parâmetros, no caso do @IsNotEmpty criamos uma mensagem customizada (a biblioteca possui mensagens padrões, mas em inglês) e no caso do @Lenght, o número mínimo e máximo de caracteres e também uma mensagem.

Agora precisamos informar para a aplicação que ela deve utilizar as configurações de validação, para isso é necessário passar a seguinte configuração no arquivo main.ts, conforme baixo:

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

Veja que configuramos a aplicação para utilizar as validações em nossa aplicação, ao tentar enviar uma requisição com o verbo HTTP POST para cadastro de um novo usuário com a propriedade nome com mais de 10 caracteres, obtemos o seguinte retorno:

Validação de limite de caracteres com class-validator

A aplicação já retorna um status code 400 Bad Request, com a mensagem que customizamos. Bem legal né? Vamos testar utilizando o decorator @IsNotEmpty para idade e passar uma idade nula:

Validação vazio com class-validator

Perceba que o retorno foi o mesmo Bad Request, agora na mensagem constando os dois campos que estão errados, porém veja que a mensagem do campo de idade está em inglês, pois não customizei a mensagem de propósito somente para exemplificar que o class-validator possui as mensagens padrões, que podem ser customizadas como fizemos na propriedade nome.

Ótimo! Mas e se for necessário criar uma validação customizada, é possível? A resposta é que sim! Podemos criar um novo decorator e também utilizar o @Validate(ValidaçãoCustomizada). Então como podemos fazer isso?

Validações customizadas

Primeiramente, vamos criar um arquivo na pasta users com o nome validator-idade.ts, será o arquivo que iremos configurar uma validação para verificar se a propriedade idade é maior que 18, ou seja, vamos simular um cadastro que o usuário deve ser maior de idade. O arquivo ficará da seguinte forma:

import {
  ValidatorConstraint,
  ValidatorConstraintInterface,
} from 'class-validator';

@ValidatorConstraint({ name: 'idadeValida', async: false })
export class IdadeValida implements ValidatorConstraintInterface {
  validate(idade: number) {
    return idade > 18 ? true : false;
  }

  defaultMessage() {
    return 'A idade precisa ser acima de 18 anos';
  }
}

Utilizaremos o decorator @ValidatorConstraint, que recebe como parâmetro o nome (name) e se é assíncrono (podemos ter validações mais avançadas que acessam banco de dados por exemplo, sendo importante ter essa possibilidade).

Logo em seguida vamos dar o nome da classe e implementar a interface que possui os métodos necessários para criar a validação, o principal deles é o método validate. Este método irá receber um parâmetro (que será a propriedade), com isso podemos fazer a validação e retornar true (se passou na validação) ou false em caso contrário.

Outro método que podemos utilizar é o defaultMessage, com ele podemos retornar uma mensagem caso não ocorra a validação com sucesso, lembre-se que essa mensagem pode ser alterada no próprio decorator utilizando a propriedade {message: ‘mensagem’}, como fizemos acima.

Agora vamos testar, o nosso DTO de usuário ficará da seguinte forma:

import { IsNotEmpty, Length, Validate } from 'class-validator';
import { IdadeValida } from '../validator-idade';

export class CreateUserDto {
  @IsNotEmpty({ message: 'Nome não pode ser vazio' })
  @Length(3, 10, { message: 'Nome precisa ter entre 3 e 10 caracteres' })
  nome: string;

  @IsNotEmpty()
  @Validate(IdadeValida)
  idade: number;
}

Utilizamos o decorator @Validate(), e como parâmetro o decorator recebe a classe da validação customizada, no caso a classe IdadeValida, agora ao cadastrar um usuário com idade menor que 18, recebemos o seguinte retorno:

Validação customizada com class-validator

Utilizando class-transformer

Express - Desenvolvendo aplicações web
Curso Express - Desenvolvendo aplicações web
Conhecer o curso

Prefeito, aprendemos a utilizar o class-validator para validar as informações, agora podemos utilizar o class-transformer para manipular essa informação, por exemplo, vamos imaginar um cenário que os nomes devem estar todos com letras minúsculas, para isso podemos usar o decorator @Transform() nos Dtos, conforme abaixo:

import { Transform } from 'class-transformer';
import { IsNotEmpty, Length, Validate } from 'class-validator';
import { IdadeValida } from '../validator-idade';

export class CreateUserDto {
  @IsNotEmpty({ message: 'Nome não pode ser vazio' })
  @Length(3, 10, { message: 'Nome precisa ter entre 3 e 10 caracteres' })
  @Transform(({ value }) => value.toLowerCase())
  nome: string;

  @IsNotEmpty()
  @Validate(IdadeValida)
  idade: number;
}

Veja que utilizamos o @Transform(), onde recebemos um parâmetro que será a propriedade nome e convertemos a string em letras minúsculas. Porém, além do decorator, é necessário habilitar a utilização deste recurso lá no app.useGlobalPipes dentro do arquivo main.ts, utilizando a propriedade { transform: true }, desta forma:

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({ transform: true }));
  await app.listen(3000);
}
bootstrap();

Ótimo, tudo configurado, vamos testar criando um usuário com o nome em letras maiúsculas e verificar o resultado:

Covertendo propriedade em letras minucsculas com class-transform

Veja que o usuário foi salvo com o nome em letras minúsculas conforme o esperado, bem interessante! Lembrando que é possível criar funções de acordo com a sua necessidade e tratar com o @Transform().

Recursos Expose e Exclude do class-transformer

O class-transformer também possui outros recursos interessantes como o @Expose() e @Exclude(), o primeiro basicamente podemos apontar para o NestJS como vamos trabalhar com a propriedade, já o segundo é usado para ocultarmos alguma propriedade. Vamos ver na prática, primeiramente vou alterar a propriedade nome para a requisição receber como nome_completo sem alterar o DTO ou a entidade, da seguinte maneira:

import { Expose, Transform } from 'class-transformer';
import { IsNotEmpty, Length, Validate } from 'class-validator';
import { IdadeValida } from '../validator-idade';

export class CreateUserDto {
  @IsNotEmpty({ message: 'Nome não pode ser vazio' })
  @Length(3, 10, { message: 'Nome precisa ter entre 3 e 10 caracteres' })
  @Transform(({ value }) => value.toLowerCase())
  @Expose({ name: 'nome_completo' })
  nome: string;

  @IsNotEmpty()
  @Validate(IdadeValida)
  idade: number;
}

Adicionamos o decorator @Expose(), apontando que vamos trabalhar com a propriedade nome como nome_completo, portanto ao receber a requisição, devemos passar com o nome do @Expose():

Utilizando recurso expose do class-transform

Neste caso, ao efetuar a requisição o NestJS considerou o DTO, e no retorno a entidade User.

Para que no response o class-transformer tenha efeito, precisamos apontar para o NestJS o uso dos interceptors, de forma resumida, é onde o nestJS manipula os dados entre as requisições, então devemos adicionar uma nova configuração no main.ts:

import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common';
import { NestFactory, Reflector } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  //uso global de interceptor, e a serialização dos dados nas requisições.
  app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
  app.useGlobalPipes(new ValidationPipe({ transform: true }));
  await app.listen(3000);
}
bootstrap();

Podemos então fazer as seguintes alterações no DTO, para que exiba a propriedade nome como nome_completo e utilizar o @Exclude() para não exibir a propriedade id:

import { Exclude, Expose, Transform } from 'class-transformer';
import { IsNotEmpty, Length, Validate } from 'class-validator';
import { IdadeValida } from '../validator-idade';

export class CreateUserDto {
  @IsNotEmpty({ message: 'Nome não pode ser vazio' })
  @Length(3, 10, { message: 'Nome precisa ter entre 3 e 10 caracteres' })
  @Transform(({ value }) => value.toLowerCase())
  @Expose({ name: 'nome_completo' })
  nome: string;

  @IsNotEmpty()
  @Validate(IdadeValida)
  idade: number;

  @Exclude()
  id: number;
}

Ao criarmos um novo usuário, vamos obter o retorno da seguinte forma:

Utilizando recurso exclude do class-transformer

Ótimo, agora o retorno está como nome_completo e não está exibindo o id como o esperado.

Nest.js - Fundamentos
Curso Nest.js - Fundamentos
Conhecer o curso

Conclusão

Neste artigo abordamos de forma resumida alguns recursos das bibliotecas class-validator e class-transformer, que vão te ajudar a ganhar tempo e utilizar boas práticas ao desenvolver suas aplicações. Vale ressaltar que elas possuem vários outros recursos interessantes que podem ser consultados pelas suas respectivas documentações.

Por fim, caso queira aprender mais sobre NestJS saiba que aqui na TreinaWeb temos o curso Nest.js - Fundamentos que possui 02h07 de vídeos e um total de 18 exercícios. Conheça também nossos outros cursos de TypeScript.

Veja quais são os tópicos abordados durante o curso de Nest.js - Fundamentos:

  • Conhecendo a estrutura;
  • Utilizando Nest CLI
  • Entendendo Rotas, Controllers e Views;;
  • Conexão com banco de dados;
  • Usando TypeORM;
  • Template Engine.

Autor(a) do artigo

Wesley Gado
Wesley Gado

Formado em Análise e Desenvolvimento de Sistemas pelo Instituto Federal de São Paulo, atuou em projetos como desenvolvedor Front-End. Nas horas vagas grava Podcast e arrisca uns três acordes no violão.

Todos os artigos

Artigos relacionados Ver todos