· Diogo Felizardo · NestJS  · 5 min de leitura

Tratamento de Erros e Validação de Dados no NestJS

Aprenda a implementar um robusto sistema de tratamento de erros e validação de dados em suas aplicações NestJS para garantir a integridade e a confiabilidade.

Aprenda a implementar um robusto sistema de tratamento de erros e validação de dados em suas aplicações NestJS para garantir a integridade e a confiabilidade.

Sumário

  1. Introdução
  2. Validação de Dados
  3. Tratamento de Erros
  4. Exemplo Prático
  5. Testando os Erros
  6. Repositório no GitHub
  7. Conclusão
  8. Referências

Introdução

Em aplicações backend, garantir a integridade dos dados e o tratamento adequado de erros é fundamental para a robustez e a confiabilidade do sistema. O NestJS, um framework progressivo para Node.js, oferece ferramentas poderosas para implementar essas funcionalidades de maneira eficiente e escalável.

Neste post, abordaremos como realizar a validação de dados utilizando DTOs e Pipes do NestJS, além de implementar um sistema de tratamento de erros personalizado utilizando Filtros de Exceção.

Validação de Dados

A validação de dados assegura que as informações recebidas pelas rotas estejam no formato esperado, evitando inconsistências e possíveis vulnerabilidades.

Instalação das Dependências

Para realizar a validação, utilizaremos as bibliotecas class-validator e class-transformer. Instale-as executando:

npm install class-validator class-transformer

Criação de DTOs

DTOs (Data Transfer Objects) são classes que definem a estrutura dos dados que serão recebidos ou enviados pela aplicação.

Crie uma pasta chamada dto dentro do módulo desejado e adicione o seguinte arquivo:

// src/users/dto/create-user.dto.ts
import { IsString, IsEmail, IsNotEmpty, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @IsNotEmpty()
  nome: string;

  @IsEmail()
  @IsNotEmpty()
  email: string;

  @IsString()
  @MinLength(6)
  senha: string;
}

Uso de Pipes de Validação

Os Pipes do NestJS são responsáveis por transformar e validar os dados antes que eles cheguem aos manipuladores de rota.

No seu módulo principal ou no módulo específico, habilite a validação global:

// src/main.ts
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({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
  }));
  await app.listen(3000);
}
bootstrap();

Tratamento de Erros

Um tratamento de erros eficaz melhora a experiência do desenvolvedor e do usuário, fornecendo feedback claro e consistente.

Filtros de Exceção

Para centralizar o tratamento de erros, podemos criar Filtros de Exceção personalizados.

// src/common/filters/http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status = exception instanceof HttpException
      ? exception.getStatus()
      : HttpStatus.INTERNAL_SERVER_ERROR;

    const message = exception instanceof HttpException
      ? exception.getResponse()
      : 'Erro interno do servidor';

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message,
    });
  }
}

Registre o filtro globalmente:

// src/main.ts
import { AllExceptionsFilter } from './common/filters/http-exception.filter';
// ... outros imports

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new AllExceptionsFilter());
  // ... restante do bootstrap
}
bootstrap();

Exemplo Prático

Vamos criar um módulo de usuários que implementa validação de dados e tratamento de erros.

Estrutura do Projeto

src/
├── users/
│   ├── dto/
│   │   └── create-user.dto.ts
│   ├── users.controller.ts
│   ├── users.service.ts
│   └── users.module.ts
├── common/
│   └── filters/
│       └── http-exception.filter.ts
├── app.module.ts
└── main.ts

Código Fonte

// src/users/users.controller.ts
import { Controller, Post, Body, NotFoundException, BadRequestException, ConflictException } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) { }

  @Post()
  async create(@Body() createUserDto: CreateUserDto) {
    // Verifica se o email já está em uso
    const existingUser = await this.usersService.findByEmail(createUserDto.email);
    if (existingUser) {
      throw new ConflictException('Email já está em uso.');
    }

    const user = await this.usersService.create(createUserDto);
    if (!user) {
      throw new BadRequestException('Usuário não criado.');
    }
    return user;
  }
}
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UsersService {
  private users = [];

  create(createUserDto: CreateUserDto) {
    try {
      const user = { id: Date.now(), ...createUserDto };
      this.users.push(user);
      return user;
    } catch (error) {
      return null;
    }
  }

  findByEmail(email: string) {
    return this.users.find(user => user.email === email);
  }

  findAll() {
    return this.users;
  }
}
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}
// src/app.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';

@Module({
  imports: [UsersModule],
})
export class AppModule {}

Testando os Erros

Vamos simular algumas requisições para verificar como os diferentes erros são tratados.

1. Tentativa de Criar um Usuário com Email Existente

Requisição:

POST /users HTTP/1.1
Content-Type: application/json

{
  "nome": "João Silva",
  "email": "joao@example.com",
  "senha": "senha123"
}

Primeira Requisição:

Cria o usuário com sucesso.

Resposta:

{
  "id": 1616161616161,
  "nome": "João Silva",
  "email": "joao@example.com",
  "senha": "senha123"
}

Segunda Requisição com o Mesmo Email:

POST /users HTTP/1.1
Content-Type: application/json

{
  "nome": "Maria Souza",
  "email": "joao@example.com",
  "senha": "senha456"
}

Resposta:

{
	"statusCode": 409,
	"timestamp": "2024-09-30T13:32:06.715Z",
	"path": "/users",
	"message": {
		"message": "Email já está em uso.",
		"error": "Conflict",
		"statusCode": 409
	}
}

2. Tentativa de Criar um Usuário com Dados Inválidos

Requisição:

POST /users HTTP/1.1
Content-Type: application/json

{
  "nome": "",
  "email": "invalid-email",
  "senha": "123"
}

Resposta:

{
  "statusCode": 400,
  "timestamp": "2024-04-27T12:05:00.000Z",
  "path": "/users",
  "message": [
    "nome should not be empty",
    "email must be an email",
    "senha must be longer than or equal to 6 characters"
  ]
}

3. Tentativa de Acesso a um Recurso sem Autenticação

Requisição:

GET /admin/dashboard HTTP/1.1

Resposta:

{
  "statusCode": 401,
  "timestamp": "2024-04-27T12:10:00.000Z",
  "path": "/admin/dashboard",
  "message": "Credenciais inválidas."
}

4. Acesso Negado a um Recurso Restrito

Requisição:

GET /admin/dashboard HTTP/1.1
Authorization: Bearer <token-do-usuário-comum>

Resposta:

{
  "statusCode": 403,
  "timestamp": "2024-04-27T12:15:00.000Z",
  "path": "/admin/dashboard",
  "message": "Acesso negado."
}

5. Erro Interno no Servidor

Simulação:

Suponha que haja um erro não tratado dentro do serviço.

Requisição:

POST /users HTTP/1.1
Content-Type: application/json

{
  "nome": "Carlos Pereira",
  "email": "carlos@example.com",
  "senha": "senha789"
}

Resposta:

{
  "statusCode": 500,
  "timestamp": "2024-04-27T12:20:00.000Z",
  "path": "/users",
  "message": "Erro interno do servidor."
}

Conclusão

Implementar um sistema eficaz de tratamento de erros e validação de dados é essencial para o desenvolvimento de aplicações robustas e seguras. O NestJS facilita esse processo com suas ferramentas integradas, como DTOs, Pipes e Filtros de Exceção. Ao seguir as práticas abordadas neste post, você garantirá a integridade dos dados e fornecerá uma melhor experiência para os usuários e desenvolvedores que interagem com sua API.

Repositório no GitHub

Para acessar o código fonte deste projeto, visite o Acesse o Repositório no GitHub.

Referências

Olá Dev 👋🏻

Se você achou este post útil, considere dar uma estrela no repositório do GitHub ou compartilhar nas suas redes sociais favoritas 😍. Seu apoio faria toda a diferença!

Dúvidas? 🙋

Se tiver alguma pergunta sobre desenvolvimento backend, sinta-se à vontade para criar uma nova issue no GitHub usando o botão abaixo. Ficarei feliz em ajudar com qualquer assunto que você queira explorar!
Compartilhar conhecimento é a melhor forma de crescermos juntos 👨🏻‍💻.
Me faça uma pergunta
Back to Blog

Posts relacionados

Ver todos os posts »
Guia Completo dos Comandos CLI do NestJS

Guia Completo dos Comandos CLI do NestJS

Aprenda a utilizar a CLI do NestJS de forma eficiente com este guia completo. Descubra os principais comandos, entenda para que cada um serve e veja o que eles geram em seu projeto.