Este repositório tem o objetivo de fornecer algumas orientações sobre os recursos fundamentais do Framework NestJS
Fique a vontade para fazer o clone e aprender com a base de código, juntamente com o auxilio deste Readme
Para mais clareza e compreensão, recomendo a playlist do professor Jorge Aluizio, de onde foi tirada a base de código
-
Instalação CLI
npm i -g @nestjs/cli
-
Instalando versões específicas da CLI
npm install -g @nestjs/cli@7.6.0
-
Iniciar projeto
nest new <project-name>
Estrutura do projeto
nest-cli.json
-
Mantém meta dados para organizar, construir e implantar projetos estruturados com Nest
-
Normalmente não precisamos lidar com este arquivo diretamente, pois ele é gerenciado pelo próprio Nest
/src
- Pasta raiz onde ficará o código
/src/main.ts
-
Arquivo principal do projeto
-
Responsável por criar a aplicação a partir da instrução
NestFactory.create(AppModule);
-
Este método recebe como parâmetro o
AppModule
, que é o módulo principal da aplicação -
Todos os demais modules serão inseridos em
AppModule
. Normalmente esta inserção é feita automaticamente pelo próprio Nest
app.module.ts
-
Módulo principal da aplicação
-
Classe que recebe o decorator
@Module()
, responsável por passar todas as configurações necessárias para que a classe se comporte como um móduloimport { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [], controllers: [AppController], providers: [AppService], }) export class AppModule {}
-
Neste utiliza muito o recurso de decorators, de forma que podemos passar decorators para métodos, atributos, e tudo que precisarmos atribuir determinadas funcionalidades
-
Muitos recursos já são atribuidos pelo Nest com o uso da cli
-
Caso optemos por criar determinados recursos manualmente, sem o uso da cli, as configurações deverão ser passadas manualmente
app.controller.ts
-
Controller principal da aplicação
-
Os controllers são responsáveis por lidar com as requisições que chegam ma aplicação
-
Para que uma classe seja um controller, ela deve ser decorada pelo decorator
@Controller()
-
Nos controllers, declaramos as rotas através dos decorators de cada método:
@Get()
,@Post()
,@Put()
,@Patch()
,@Delete()
-
Para cada rota, deve ser informada uma função e seu retorno
-
As funções decoradas retornam serviços e seus métodos
-
Os serviços devem ser injetados por
injeção de dependencia
nos seus respectivos controllersimport { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } }
-
Podemos gerar um controller pela CLI
nest g controller <nome do controller>
app.service.ts
-
Serviço principal da aplicação
-
Para que um serviço possa ser injetado em um controller, isto é, para que uma classe possa ser um serviço, ele deve ser decorado com o decorator
@Injectable()
-
No Nest, os serviços são considerados
Providers
ouProvedores
, que podem injetar dependencias em outras classes -
Os serviços possuem lógica da camada de negócio, onde iremos efetivamente implementar o código que será o nucleo da aplicação
import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } }
-
Podemos gerar um serviço pela CLI
nest g service <nome do serviço>
Trabalhando com controllers
-
Criamos um controller
nest g controller courses
-
O Nest cria uma pasta em
src
com o arquivo do controller, um arquivo para teste e atualiza o arquivoapp.module.ts
-
Para que não seja criado o arquivo de testes, devemos passar o parametro
--no-spec
nest g controller courses --no-spec
-
Podemos criar a pasta em um diretorio diferente, passando na instrução o caminho desejado
nest g controller modules/courses
-
No controller, podemos definir os endpoints, passando os dados nos parametros das funções, de forma que o valor passado no controller, será a rota principal e os valores passados em cada método serão as rotas filhas
-
Representamos o endpoint
/courses/hello
da seguinte formaimport { Controller, Get } from '@nestjs/common'; @Controller('courses') export class CoursesController { constructor() {} @Get('hello') getUsers(): string { return 'Hello, course'; } }
-
Para que estejamos fazendo da forma correta, devemos
Parametros de rotas
-
Passamos o parametro no parametro do decorator da rota da forma tradicional
:parametro
-
Para que seja possível pegarmos o parâmetro para o uso no método, precisaremos do decorator
@Param()
, que decora um objeto, que será uma lista com todos os parametros que desejarmos obter.@Get(':idCourse') findOne(@Param() params): string { const { idCourse } = params; return `Curso número ${idCourse}`; }
Trabalhando com dados de Payload (corpo da requisição)
-
Para que seja possível pegarmos os dados do corpo da requisição, precisaremos utilizar o decorator
@Body()
, ue decora um objeto, que será uma lista com todos os dados que desejarmos obter.@Post() createCourse(@Body() body) { const { name, description, price } = body; return body; }
-
Também podemos fazer com que o body recebido no backend trabalhe apenas com valores determinados, não sendo obrigadtório trabalharmos com todos. Neste caso, passamos o atributo que desejamos trabalhar como parâmetro do decorator
@Post() createCourse(@Body('name') body) { return body; }
Trabalhando com HTTP Status Code
-
O Nest já envia automaticamente os status code padrões para as requisiçõe. 200 para Get, 201 para Post, etc.
-
Para manipular o status code, utilizamos o decorator
@HTTPCode
-
Basta decorar o método post com o decorator, passando como valor o status desejado
@Post() @HttpCode(204) createCourse(@Body('name') body) { return body; }
-
Caso o status seja o referente a
sem conteúdo
, como mostrado acima, mesmo que informemos oreturn
, nada será retornado para o front -
Podemos checar a lista de status http disponível no Nest através do enum
HttpStatus
. Basta chamar este enum e digitar um pontao, que será mostarada a lista com os status disponíveis@Post() @HttpCode(HttpStatus.NO_CONTENT) createCourse(@Body('name') body) { return body; }
-
O decorator
@HTTPCode
é extremamente útil quando o status de retorno é estático, porém para condições lógicas, como tratativas de erros, outros métodos devem ser utilizados, incluindo os métodos do próprioexpress
, que estão disponíveis no Nest, visto que o Nest utiliza o express internamente -
Para o melhor tratamento da resposta, podemos utilizar o decorator
@Res
. Assim, teremos disponívels as funcionalidades do express -
@Res
decora um parâmetro, que será o objeto response da aplicação@Get() listAllCourses(@Res() res): string { return res.status(200).json({ msg: 'Lista com todos os cursos' }); }
Lidando com os demais métodos HTTP
-
Patch
@Patch(':idCourse') updateSomeDataCourses(@Body() body, @Param() params) { const { name, description, price } = body; const { idCourse } = params; return body; }
-
Put
@Put(':idCourse') updateAllDataCourses(@Body() body, @Param() params) { const { name, description, price } = body; const { idCourse } = params; return body; }
-
Delete
@Delete(':idCourse') deleteCourse(@Param() params, @Res() res) { const { idCourse } = params; return res.json(params); }
Trabalhando com Services
-
Utilizados para isolar a lógica de negócio dos controllers
-
Facilmente reutilizados no código, bastando injetar na classe desejada
-
Criamos um service
nest g service courses --no-spec
-
O Nest cria uma pasta em
src
com o arquivo do service e atualiza o arquivoapp.module.ts
-
No controller desejado, injetamos a instancia do serviço no construtor (injeção de dependencias). Essa instancia segue o
padrão singleton
e recebe o decorator@Injectable()
import { Injectable } from '@nestjs/common'; @Injectable() export class CoursesService {}
-
Como normalmente não precisamos redefinir nada nos serviços, ao injetá-lo no controller, definimos como
readonly
@Controller('courses') export class CoursesController { constructor(private readonly CoursesService: CoursesService) {} }
-
Dessa forma, podemos utilizar todos os métodos criados no serviço
-
courses.service.ts
import { Injectable } from '@nestjs/common'; import { Course } from './entities/course.entity'; @Injectable() export class CoursesService { private courses: Course[] = [ { id: 1, name: 'Fundamentos do Nest', description: 'Fundamentos do Nest', tags: ['nest', 'node', 'typescript'], }, ]; findAll(): Course[] { return this.courses; } findOne(id: string): Course { const course = this.courses.find((course) => course.id == Number(id)); return course; } create({ name, description, tags }: Course): void { const lastCourse = this.courses[this.courses.length - 1]; const id = lastCourse.id + 1; this.courses.push({ id, name, description, tags }); } someDataUpdate(id: string, { name, description }: Course): Course { const course = this.courses.find((course) => course.id == Number(id)); course.name = name; course.description = description; return course; } delete(id: string) { const idexCourse = this.courses.findIndex( (course) => course.id == Number(id), ); this.courses.splice(idexCourse, 1); } }
-
courses.controller.ts
import { Body, Controller, Get, Param, Post, HttpCode, HttpStatus, Res, Patch, Put, Delete, } from '@nestjs/common'; import { CoursesService } from './courses.service'; import { Course } from './entities/course.entity'; @Controller('courses') export class CoursesController { constructor(private readonly CoursesService: CoursesService) {} @Get() listAllCourses(@Res() res): Course[] { return res.json(this.CoursesService.findAll()); } @Get(':idCourse') findOne(@Param() params): Course { const { idCourse } = params; const course = this.CoursesService.findOne(idCourse); console.log(course); return course; } @Post() @HttpCode(HttpStatus.NO_CONTENT) createCourse(@Body() body, @Res() res) { const { name, description, tags } = body; this.CoursesService.create({ name, description, tags }); return res.json(); } @Patch(':idCourse') updateSomeDataCourses(@Body() body, @Param() params, @Res() res) { const { name, description } = body; const { idCourse } = params; const course = this.CoursesService.someDataUpdate(idCourse, { name, description, }); return res.json(course); } @Delete(':idCourse') deleteCourse(@Param() params, @Res() res) { const { idCourse } = params; this.CoursesService.delete(idCourse); return res.json(); } }
Métodos para tratamento de erros
- O Nest fornece uma série de formas de tratamento de erros
Lista de exceptions para uso
-
BadRequestException
-
UnauthorizedException
-
NotFoundException
-
ForbiddenException
-
NotAcceptableException
-
RequestTimeoutException
-
ConflictException
-
GoneException
-
HttpVersionNotSupportedException
-
PayloadTooLargeException
-
UnsupportedMediaTypeException
-
UnprocessableEntityException
-
InternalServerErrorException
-
NotImplementedException
-
ImATeapotException
-
MethodNotAllowedException
-
BadGatewayException
-
ServiceUnavailableException
-
GatewayTimeoutException
-
PreconditionFailedException
HttpException
-
Recebe dois parâmetros:
response
, que é a mensagem que queremos informar, podendo ser uma string ou um objeto, e o segundo será ostatus
, que é o status de resposta da requisição -
Por padrão, o corpo da resposta JSON contém duas propriedades:
statusCode
, o qual o padrão é o código de status HTTP fornecido no argumento status, emessage
, que é uma breve descrição do erro HTTP com base no status -
Para substituir apenas a parte da mensagem do corpo da resposta JSON, forneça uma string no argumento response. Para substituir todo o corpo da resposta JSON, passe um objeto no argumento response. O Nest serializará o objeto e o retornará como o corpo da resposta JSON.
-
O segundo argumento do construtor -
status
- deve ser um código de status HTTP válido. A melhor prática é usar o HttpStatusenum importado de @nestjs/common. -
Há um terceiro argumento do construtor (opcional) -
options
- que pode ser usado para fornecer uma causa de erro. Esse objetocause
não é serializado no objeto de resposta, mas pode ser útil para fins de registro, fornecendo informações valiosas sobre o erro interno que causou o lançamento HttpException. -
HttpException
findOne(id: string): Course { const course = this.courses.find((course) => course.id == Number(id)); if (!course) { throw new HttpException('Curso não encontrado!', HttpStatus.NOT_FOUND); } return course; }
-
HttpException com cause
@Get() async findAll() { try { await this.service.findAll() } catch (error) { throw new HttpException({ status: HttpStatus.FORBIDDEN, error: 'This is a custom message', }, HttpStatus.FORBIDDEN, { cause: error }); } }
-
Podemos utilizar as demais exceptions da seguinte forma
if (!course) { throw new NotFoundException({ statusCode: HttpStatus.NOT_FOUND, error: 'Curso não encontrado!', }); }
-
O construtor padrão das exceptions é (consultar a classe caso necessário):
constructor(objectOrError?: string | object | any, description?: string);
Trabalhando com modules
-
Maneira eficaz de organizar os componentes, proporcionando manter uma arquitetura ideal que possui vários módulos, cada um encapsulando um conjunto de recursos
-
Criamos um module
nest g module courses --no-spec
-
O Nest cria uma pasta em
src
com o arquivo do module e atualiza o arquivoapp.module.ts
-
Com isso, não precisamos mais passar as dependencias para AppModule, podendo passar os services e controllers para CoursesModule e passando apenas CoursesModule para AppModule
-
app.module.ts
import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { CoursesModule } from './courses/courses.module'; @Module({ imports: [CoursesModule], controllers: [AppController], providers: [AppService], }) export class AppModule {}
-
courses.module.ts
import { Module } from '@nestjs/common'; import { CoursesController } from './courses.controller'; import { CoursesService } from './courses.service'; @Module({ imports: [], controllers: [CoursesController], providers: [CoursesService], }) export class CoursesModule {}
DTO
-
Data Transfer Object
-
Objeteto usado para encapsular dados a serem enviados de uma aplicação para outra
-
Nos ajudam a definir as interfaces ou quais informações são recebidas pela aplicação
-
Forma de lidar com informações que veem do frontend para o backend
-
Pode ser criado através de uma classe ou interface
-
Define os atributos ou campos que devem ser considerados para o nosso serviço lidar
nest g class courses/dto/create-courses.dto --no-spec
-
Com os DTOs, podemos separar os dados das entities do que esperamos receber numa requisição
-
Como os DTOs servem apenas para tiparmos o que esperamos receber, podemos definid suas propriedades como
readonly
-
CreateCoursesDto
export class CreateCoursesDto { readonly name: string; readonly description: string; readonly tags?: string[]; }
-
CoursesController
@Post() @HttpCode(HttpStatus.NO_CONTENT) createCourse(@Body() body: CreateCoursesDto, @Res() res) { const { name, description, tags } = body; this.CoursesService.create({ name, description, tags }); return res.json(); }
-
Assim, faremos um DTO para cada caso necessário
Validação de dados / Validation Pipe
-
Fornece uma maneira conveniente de aplicar regras de validação para todas as requisições que recebemos na nossa API
-
Para utilizarmos o recursos, precisaremos configurá-lo na nossa aplicação, chamando o método
useGlobalPipes()
no nosso arquivomain
-
Passamos como valor do método, a instanciação da classe
ValidationPipe()
-
Além disso, precisaremos realizar uma instalação extra de dois pacotes adicionais
npm i class-validator class-transformer
-
Após a instalação dos pacotes, utilizamos os validadores nos nossos DTOs
-
Realizamos essas verifgicações através de decorators
-
É importante salientar que o módulo
class-decorators
só pode ser aplicado a classes
import { IsString } from 'class-validator';
export class CreateCoursesDto {
@IsString()
readonly name: string;
@IsString()
readonly description: string;
@IsString({ each: true })
readonly tags: string[];
}
-
Por padrão, caso alguma das informaçõe snão estivar na requisição, o Neste retornará um objeto do sistema de validação, vindo do
class-validator
, com status 400 e detalhes sobre o erro -
Muitas vezes, algumas informações de determinados DTOs serão parecidas. Para que não haja a necessidade de refazer todo um código, podemos utilizar o módulo do Nest
mapped-types
npm i @nestjs/mapped-types
-
Após a instalação, podemos extender um DTO com um
PartialType()
e passar como parametro o DTO pai, o qual queremos pegar as informaçõesimport { PartialType } from '@nestjs/mapped-types'; import { CreateCoursesDto } from './create-courses.dto'; export class UpdateCoursesDto extends PartialType(CreateCoursesDto) {}
-
Dessa forma, informamos que os campos vindos da classe pai são opcionais ma classe estendida
Whitelist
-
Podemos filtrar propriedades que devem ser bloqueadas a partir da definição de uma lista, onde definimos quais propriedades são aceitaveis. Qualquer outra que seja enviada, será eliminada da requisição. Podemos fazer essas e outras definições atavés da passagem de configurações passadas no parâmetro da instanciação do ValidationPipe no arquivo 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 })); await app.listen(3000); } bootstrap();
-
Dessa forma, garantimos que apenas os dados listados nos DTOs serão utilizados, de forma que os demais dados que vierem do frontend serão retirados do objeto de requisição
ForbidNonWhitelisted
-
Apesar da whitelist removeer os itens inesperados, ela ainda permite que os itens cheguem ao nosso backend, aprenas removendo-os da requisição e utilizando os dados esperados, o que pode acabar ainda assim sendo perigoso
-
Podemos atiavar a opção forbidNonWhitelisted para que ao ser identificado um item inesperado, a requisição seja recusada, com um erro 400 (bad request)
app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }), );
-
Assim, garantimos que a nossa aplicação não receberá informações indevidas
transform
- Podemos utilizar este parâmetro para que o nosso backend determine automaticamente o tipo dos dados da requisição como sendo do tipo do DTO
Comandos CLI
-
Comandos
generate
podem ser abreviados parag
nest g module courses
-
Criar módulo
nest generate module <nome do modulo>
-
Gerar um controller
nest g controller <nome do controller>
-
Gerar um serviço
nest g service <nome do serviço>
-
Para gerar os arquivos sem o arquivo de teste, passar o parametro
--no-spec
nest g controller courses --no-spec
-
Criar a pasta em um diretorio diferente, passando na instrução o caminho desejado
nest g controller modules/courses
-
Para visualizar como ficaria a estrutura se fizermos um terderminado comando sem que seja executado, basta passar a flag
--dry-run
nest g controller modules/courses --dry-run