Aprendendo NestJS Marius Espejo
npm i -g @nestjs/cli
OU
yarn global add @nestjs/cli
nest new nome-do-seu-projeto
cd nome-do-seu-projeto
yarn start:dev
Bulletproof node.js project architecture 🛡️
- Responsável por toda comunicação http
- Verbos HTTP: GET, PUT, POST, DELETE, PATCH, etc.
- Respostas com HTTP code: 404, 200, 201, 401, etc.
- Se comunica com a Service
- Não há regra de negócio aqui
- Regras de negócio
- Comunicação com Banco de Dados
No Controller pro exemplo, a rota é feita com base na annotation: @Controller()
Com o código abaixo:
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('app')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('hello')
getHello(): string {
return this.appService.getHello();
}
@Get(':id')
getHelloId(@Param('id') id: string): string {
return id;
}
}
Uma vez que definimos os Decorators como:
/app = @Controller('app')
/hello = @Get('hello')
O acesso será feito da seguinte maneira:
http://localhost:3000/app/hello
Assim como para realizar o acesso com parâmetro id:
http://localhost:3000/app/123123
Quando iniciamos o projeto utilizando o comando
nest new nome-do-seu-projeto
, mas a cli não se limita apenas a isso, para quem está habituado com outros frameworks como Laravel, a cli é uma ferramenta importante para auxiliar o desenvolver a criar códigos.
nest generate module users
Esse comando trará como resultado a criação do arquivo src/users/users.modules.ts
e a atualização do arquivo src/app.modules.ts
, já importando o novo módulo criado.
nest generate controller users
Esse comando executará a criação dos arquivos src/users/users.controller.spec.ts
e src/users/users.controller.ts
, também atualizará nosso módulo criado previamente src/users/users.module.ts
.
Os arquivos com .spec.
são arquivos para testes.
nest generate service users
Com resultado parecido com o comando para gerar Controllers, realizará a criação dos arquivos src/users/users.service.spec.ts
e src/users/users.service.ts
, também como a atualização do src/users/users.module.ts
.
src/users/users.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
private users: any = [{ id: 0, name: 'John' }, { id: 1, name: 'Mary' }];
findAll(): any {
return this.users;
}
findById(userId: number) {
return this.users.find(user => user.id === userId);
}
}
src/users/users.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) { }
@Get()
getUsers(): string {
return this.usersService.findAll();
}
@Get(':id')
getUserById(@Param('id') id: string): any {
return this.usersService.findById(Number(id));
}
}
src/users/dto/create-user.dto.ts
export class CreateUserDto {
name: string;
}
src/users/entities/user.entity.ts
export class User {
id: number;
name: string;
}
Após a criação de ambos arquivos, vamos adicionar em nossa Service e Controller como dependências. Os arquivos ficarão assim:
src/users/users.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) { }
@Get()
getUsers(): User[] {
return this.usersService.findAll();
}
@Get(':id')
getUserById(@Param('id') id: string): User {
return this.usersService.findById(Number(id));
}
@Post()
createUser(@Body() body: CreateUserDto): User {
return this.usersService.create(body);
}
}
src/users/user.service.ts
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
@Injectable()
export class UsersService {
private users: User[] = [{ id: 0, name: 'John' }, { id: 1, name: 'Mary' }];
findAll(): User[] {
return this.users;
}
findById(userId: number) {
return this.users.find(user => user.id === userId);
}
create(createUserDto: CreateUserDto): User {
const user = { id: this.getNextId(), ...createUserDto };
this.users.push(user);
return user;
}
getNextId(): number {
return this.users[this.users.length - 1].id + 1;
}
}
Aqui iniciará a parte de documentação do nosso projeto. Para instalar basta executar o comando a seguir:
yarn add @nestjs/swagger swagger-ui-express
Para configurarmos a documentação iremos alterar o arquivo:
src/main.ts
Ele ficará dessa maneira:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('NestJS API')
.setDescription('The API description')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('doc', app, document);
await app.listen(3000);
}
bootstrap();
Após a configuração basta acessar a url http://localhost:3000/doc
@ApitTags
= cria uma nova seção dentro da documentação
@ApiOkResponse
= enriquece as informações sobre o retorno do método
@ApiCreatedResponse
= o mesmo que o item acima, porém com status Code para Created.
@ApiProperty
= pode ser utilizado na tipagem, sendo dto ou entidade, também enriquece nossa documentação.
Exemplo de como fica o código após a utilização dessas annotations:
src/users/users.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
@ApiTags('users')
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) { }
@ApiOkResponse({ type: User, isArray: true })
@Get()
getUsers(): User[] {
return this.usersService.findAll();
}
@ApiOkResponse({ type: User, description: 'Returns an user' })
@Get(':id')
getUserById(@Param('id') id: string): User {
return this.usersService.findById(Number(id));
}
@ApiCreatedResponse({ type: User })
@Post()
createUser(@Body() body: CreateUserDto): User {
return this.usersService.create(body);
}
}
src/users/dto/create-user.dto.ts
import { ApiProperty } from '@nestjs/swagger';
export class CreateUserDto {
@ApiProperty()
name: string;
}
src/users/entities/user.entity.ts
import { ApiProperty } from "@nestjs/swagger";
export class User {
@ApiProperty()
id: number;
@ApiProperty()
name: string;
}
Atualize a página: http://localhost:3000/doc
e veja algumas diferenças.
Uma boa documentação da API é essencial, tanto para clientes externos quanto para equipe de desenvolvimento.
Para adicionar query params iremos alterar nosso controller e nossa service.
src/users/users.controller.ts
// ...
@ApiOkResponse({ type: User, isArray: true })
@ApiQuery({ name: 'name', required: false })
@Get()
getUsers(@Query('name') name: string): User[] {
return this.usersService.findAll(name);
}
// ...
src/users/users.service.ts
// ...
findAll(name?: string): User[] {
if (name) {
return this.users.filter(user => user.name.includes(name));
}
return this.users;
}
// ...
Uma vez que adicionamos a annotation @ApiQuery({ name: 'name', required: false })
nossa documentação já fica atualizada também.
Nesse caso iremos adicionar uma verificação simples ao buscar o usuário, incluindo sua documentação no Swagger.
src/users/users.controller.ts
// ...
@ApiOkResponse({ type: User, description: 'Returns an user' })
@ApiNotFoundResponse()
@Get(':id')
getUserById(@Param('id') id: string): User {
const user = this.usersService.findById(Number(id));
if (!user) {
throw new NotFoundException();
}
return user;
}
// ...
Vamos utilizar para transformar os dados que chegam na nossa API, para que não haja a necessidade de converter QueryParams toda vez que precisamos manipular Para isso precisamos instalar as seguintes dependências:
yarn add class-validator class-transformer
Documentação do class-validator
Iremos adicionar no arquivo src/main.ts
as dependências e iniciar sua configuração, o arquivo ficará da seguinte maneira:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
const config = new DocumentBuilder()
.setTitle('NestJS API')
.setDescription('The API description')
.setVersion('1.0')
.addTag('api')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('doc', app, document);
await app.listen(3000);
}
bootstrap();
Para adicionar as regras nesse caso iremos especificar em nosso DTO que o nome do usuário precisa ter no máximo 20 caracteres e apenas AlfaNuméricos.
src/users/dto/create-user.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsAlphanumeric, MaxLength } from 'class-validator';
export class CreateUserDto {
@ApiProperty()
@IsAlphanumeric()
@MaxLength(20)
name: string;
}
Uma vez que colocamos essas suas validações, precisamos alterar em nosso controller também o tipo de resposta caso o parâmetro name não esteja de acordo:
src/users/users.controller.ts
// ...
@ApiCreatedResponse({ type: User })
@ApiBadRequestResponse()
@Post()
createUser(@Body() body: CreateUserDto): User {
return this.usersService.create(body);
}
// ...
Já podemos notar que a ferramenta CLI do Nest é uma grande amiga no desenvolvimento. Imaginaremos um cenário de adicionar um CRUD para Lista de Tarefas, iremos chamar de TODOS.
Execute o comando abaixo:
nest g resource todos
Podemos notas que todos os arquivos já foram criados:
src/todos/todos.controller.spec.ts
src/todos/todos.controller.ts
src/todos/todos.module.ts
src/todos/todos.service.spec.ts
src/todos/todos.service.ts
src/todos/dto/create-todo.dto.ts
src/todos/dto/update-todo.dto.ts
src/app.module.ts -> esse foi o único que apenas foi alterado