Tratamento de solicitações e respostas HTTP em Nest.js

Node.js 5 minutos
Felipe Heredia, depicted in the image

Em um ambiente de APIs REST é extremamente importante que a API lide de maneira eficaz com respostas e status HTTP. Para se fazer uma API consistente é necessário informações condizentes, onde o status é de erro e a resposta também. O Nest.js lida com requisições HTTP de maneira simples e eficaz, através dos chamados controladores (controllers).

Controladores são responsáveis por lidar com solicitações recebidas e retorno de respostas ao cliente.

O propósito de um controlador é receber solicitações específicas para a aplicação Nest. Frequentemente, controladores possuem mais de uma rota, e diferentes rotas podem executar diferentes ações.

Iremos entender como os controladores funcionam e como podemos utilizar o poder do Nest para responder requisições HTTP de maneira consistente e eficaz.

Recebendo informações

O Nest utiliza decoradores (decorators) para criar controladores. Abaixo segue um exemplo de um controller básico:

import { Controller, Get, Post, Body, Param } from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Get()
  getAllUsers() {
    return 'Retorna todos os usuários';
  }

  @Post()
  createUser(@Body('name') name: string) {
    return `Usuário ${name} criado com sucesso!`;
  }

  @Get(':id')
  getUserById(@Param('id') id: string) {
    return `Retorna o usuário com ID: ${id}`;
  }
}

São disponibilizados através do pacote @nestjs/common os decoradores para setar e receber as informações de requisições HTTP, como setar o tipo da rota (Get ou Post no exemplo), também receber parâmetros e um corpo na requisição.

Existem outros decoradores mais específicos, como Headers, Ip, Session entre outros, todos disponibilizados através do pacote common. Também é possível criar decoradores customizados.

@Post('create')
createUser(
  @Body('name') name: string,
  @Body('email') email: string,
  @Headers('authorization') authHeader: string,
) {
  return `Usuário ${name} criado com email ${email}. Token: ${authHeader}`;
}

Respondendo informações ao cliente

Como vimos acima através dos decoradores é bem simples recebermos informações como Body, Headers, agora como podemos responder um status HTTP de não autorizado ou não encontrado?

É possível no próprio controller receber o parâmetro Res através de um decorador, mas particularmente não é o caso em que eu prefiro e acho mais legível.

@Get(':id')
findUser(@Param('id') id: string, @Res() res: Response) {
  const user = this.userService.findById(id);
  if (!user) {
    return res.status(404).send('Usuário não encontrado');
  }
  return res.status(200).json(user);
}

Eu prefiro um caso onde o serviço (service) é responsável por fazer esse tratamento da resposta e lançar um erro, o Nest também nos auxilia com isso.

import { Controller, Get, Param } from '@nestjs/common';
import { UsersService } from './users.service';

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

  @Get(':id')
  findUser(@Param('id') id: string) {
    return this.userService.findById(id);
  }
}
import { Injectable, NotFoundException } from '@nestjs/common';

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

  async findById(id: string) {
    const user = this.users.find(user => user.id === id);

    if (!user) {
      throw new NotFoundException('Usuário não encontrado');
    }

    return user;
  }
}

Dessa forma, caso o usuário não seja encontrado, o próprio serviço já retornará para o controller informações como status 404 e a mensagem “Usuário não encontrado”, assim teremos um erro parecido com o seguinte abaixo:

{
  "statusCode": 404,
  "message": "Usuário não encontrado"
}

Recebendo informações mais precisas

Através do uso de DTOs e de Pipes é possível receber informações mais precisas das requisições e retornar erros antes mesmo de executar o código do seu controlador.

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

export class CreateUserDto {
  @IsNotEmpty({ message: 'O nome não pode estar vazio' })
  name: string;

  @IsEmail({}, { message: 'O e-mail precisa ser válido' })
  email: string;
}
import { Controller, Post, Body } from '@nestjs/common';
import { CreateUserDto } from './create-user.dto';

@Controller('users')
export class UsersController {
  @Post('create')
  createUser(@Body() { name, email }: CreateUserDto) {
    return `Usuário ${name} criado com o e-mail ${email}`;
  }
}

Dessa maneira, caso o campo name esteja vazio, já será retornado um erro com a mensagem setada no dto.

import { Controller, Get, Param, ParseUUIDPipe } from '@nestjs/common';

@Controller('users')
export class UsersController {
  @Get(':id')
  getUserById(@Param('id', ParseUUIDPipe) id: string) {
    return `Retorna o usuário com ID: ${id}`;
  }
}

Nesse caso acima, se não for possível transformar id em um UUID, será retornado um erro.

Conclusão

Como vimos, é possível lidar com requisições HTTP de maneira simples e eficaz com a utilização do Nest.

O Nest nos disponibiliza grandes poderes e formas diferentes de realizar a mesma tarefa, irá depender do desenvolvedor escolher a melhor maneira de aplicar cada conceito seguindo os padrões de cada projeto em que esteja inserido.

A documentação do Nest é muito completa, mais informações podem ser obtidas direto da fonte através da documentação.