Como desenvolvedor de back-end do Node.js, você concordará que, por padrão, o Node.js é muito básico e não faz suposições sobre o que você precisa ao criar um aplicativo. Como resultado, você é responsável por configurar tudo o que deseja usar em um aplicativo, incluindo lidar com roteamento, fazer chamadas de API, configurar TypeScript ou Web Sockets ou até mesmo coisas fundamentais como organização de código, estrutura de arquivos e convenções de nomenclatura. .
Gerenciar uma aplicação em larga escala pode ser uma tarefa difícil, especialmente se ela não foi projetada com uma estrutura clara e diretrizes rígidas de organização de código.
Nest.js tenta resolver alguns desses problemas criando uma abstração em torno do Node.js para que você, como desenvolvedor, possa se concentrar no problema do aplicativo, em vez de em outros pequenos detalhes de implementação.
Neste guia, você aprenderá os fundamentos básicos do Nest.js de cima a baixo, com o objetivo de deixá-lo atualizado para que possa criar aplicativos Node.js de nível empresarial com a ajuda do Nest.js rapidamente.
Tudo o que aprenderemos neste guia será incremental; cobrindo muito terreno sobre conceitos introdutórios. Para aproveitar ao máximo este guia, é útil codificar.
Vamos mergulhar de cabeça, pessoal!
Código fonte: Como de costume, você pode bifurcar e mexer no código-fonte hospedado em GitHub.
Observação: Usaremos o Postman para testar a API em nossa demonstração. Você pode baixá-lo no Página de download do carteiro. Como alternativa, você pode simplesmente usar o navegador, a linha de comando curl
ou qualquer outra ferramenta com a qual você esteja familiarizado.
O que é Nest.js
Pense no Nest.js como um superconjunto do Node.js que abstrai tarefas difíceis, ferramentas e código padrão, ao mesmo tempo que adiciona um kit de ferramentas completo para o desenvolvimento de seu aplicativo usando JavaScript e TypeScript modernos.
Nest.js fornece uma arquitetura de aplicativo pronta para uso que permite que desenvolvedores e equipes criem altamente escaláveis, testáveis, fracamente acoplados e de fácil manutenção, aproveitando opções e módulos proeminentes e prontamente disponíveis na comunidade, como aqueles disponíveis em Aplicativos Express.js. Você pode até trocar o Express (que ele usa internamente por padrão) pelo Fastify, mas isso significaria que você pode precisar usar diferentes bibliotecas compatíveis com o Fastify em seu aplicativo.
Ele combina os recursos de Programação Funcional, Programação Orientada a Objetos e Programação Reativa Funcional, e com mais de 52.4 mil estrelas e 6.2 mil garfos em GitHub e uma contagem semanal de downloads de até 1,784,004, a estrutura progressiva Node.js é uma referência popular para a criação de aplicativos de servidor eficientes, escaláveis e de nível empresarial.
Recursos do Nest.js
A seguir estão os motivos pelos quais o Nest.js cresceu e se tornou uma estrutura Node.js tão popular:
- Nest.js foi criado para ajudar os desenvolvedores a construir aplicativos monolíticos e também microsserviços.
- Embora seja poderoso, também é fácil de trabalhar para o desenvolvedor; fácil de usar, rápido de aprender e fácil de aplicar.
- Ele aproveita o TypeScript (um superconjunto de JavaScript) pronto para uso e abre espaço para os desenvolvedores escreverem código sustentável, livre de erros de tempo de execução.
- Possui uma interface de linha de comando que ajuda a aumentar a produtividade dos desenvolvedores e a facilidade de desenvolvimento.
- Ao construir com Nest.js, os processos de desenvolvimento são aprimorados e economiza tempo, esteja você inicializando um produto mínimo viável ou trabalhando em um aplicativo, porque o Nest vem com uma estrutura de pastas de projeto incrível por padrão.
- Ele suporta uma variedade de módulos específicos do Nest que ajudam na integração de conceitos e tecnologias comuns, incluindo TypeORM, GraphQL, registro, validação, Mongoose, WebSockets, cache, etc.
- Nest.js pode se orgulhar de possuir algumas das melhores documentações para qualquer estrutura existente. Sua documentação é completa, fácil de entender e útil para economizar tempo de depuração, pois é processada sem esforço quando há necessidade de solução para um problema.
- Nest.js se integra ao Jest, o que simplifica a gravação de testes de unidade em seus aplicativos.
- Ele foi desenvolvido para aplicativos empresariais de pequeno e grande porte.
Criando um projeto Nest.js
Para começar a usar Nest.js em sua máquina local, primeiro você precisa instalar a Nest Command Line Interface (CLI), que ajudaria a criar uma nova pasta de projeto Nest.js e preencher a pasta com arquivos principais e módulos necessários para um Aplicativo Nest.js.
Execute o seguinte comando para instalar a interface de linha de comando Nest.js:
$ npm i -g @nestjs/cli
// Or
$ yarn global add @nestjs/cli
// Or
$ pnpm add -g @nestjs/cli
Depois de instalar com êxito a CLI Nest.js globalmente em sua máquina local, você pode executar nest
na linha de comando para ver vários comandos que podemos usar:
$ nest
Resulta em:
Usage: nest [options]
Options:
-v, --version Output the current version.
-h, --help Output usage information.
Commands:
new|n [options] [name] Generate Nest application.
build [options] [app] Build Nest application.
start [options] [app] Run Nest application.
info|i Display Nest project details.
add [options] Adds support for an external library to your project.
generate|g [options] [name] [path] Generate a Nest element.
Schematics available on @nestjs/schematics collection:
┌───────────────┬─────────────┬──────────────────────────────────────────────┐
│ name │ alias │ description │
│ application │ application │ Generate a new application workspace │
│ class │ cl │ Generate a new class │
│ configuration │ config │ Generate a CLI configuration file │
│ controller │ co │ Generate a controller declaration │
│ decorator │ d │ Generate a custom decorator │
│ filter │ f │ Generate a filter declaration │
│ gateway │ ga │ Generate a gateway declaration │
│ guard │ gu │ Generate a guard declaration │
│ interceptor │ itc │ Generate an interceptor declaration │
│ interface │ itf │ Generate an interface │
│ middleware │ mi │ Generate a middleware declaration │
│ module │ mo │ Generate a module declaration │
│ pipe │ pi │ Generate a pipe declaration │
│ provider │ pr │ Generate a provider declaration │
│ resolver │ r │ Generate a GraphQL resolver declaration │
│ service │ s │ Generate a service declaration │
│ library │ lib │ Generate a new library within a monorepo │
│ sub-app │ app │ Generate a new application within a monorepo │
│ resource │ res │ Generate a new CRUD resource │
└───────────────┴─────────────┴──────────────────────────────────────────────┘
Aqui, você verá como usar os comandos e agora pode tocar no new|n [options] [name]
comando para criar seu primeiro projeto Nest.js:
$ nest new getting-started-with-nestjs
// Or
$ nest n getting-started-with-nestjs
A seguir, será perguntado qual gerenciador de pacotes você gostaria de usar:
? Which package manager would you ❤️ to use? (Use arrow keys)
npm
yarn
> pnpm
Fique à vontade para escolher o gerenciador de pacotes de sua preferência, irei com pnpm
. Isso ocorre porque ele é cerca de três vezes mais eficiente e rápido que o NPM e, com um sistema de cache rápido, o PNPM também é mais rápido que o Yarn.
Após escolher um gerenciador de pacotes, o processo de instalação continua, então o aplicativo Nest.js seria criado.
Agora você pode cd
no projeto recém-criado e abra-o com um editor de sua escolha:
$ cd getting-started-with-nestjs
Com o projeto agora criado, podemos executá-lo com qualquer um dos seguintes comandos:
$ npm run start
// Or
$ yarn start
// Or
$ pnpm run start
Se você der uma olhada no package.json
arquivo, você notará no segmento de script, o valor para pnpm run start
is nest start
:
"start": "nest start",
Isso significa que você também pode executar o aplicativo Nest.js executando:
$ nest start
Uma olhada na estrutura do projeto Nest.js
Vamos dar uma olhada em como um aplicativo Nest é estruturado:
/package.json
A package.json
file é o coração do Node.js e, por extensão, do projeto Nest.js. Ele contém todos os metadados sobre o projeto e define várias propriedades funcionais do projeto que são necessárias para instalar dependências de aplicativos ou executar scripts de projeto.
Já vimos a capacidade do start
script.
A start:dev
profile permite observar alterações na aplicação e recarregá-la automaticamente, sem a necessidade de parar a aplicação e reiniciá-la – e é destinado ao desenvolvimento. O start:prod
O script é útil quando você deseja testar se seu aplicativo está pronto para produção, bem como quando você o implanta na produção, junto com outros scripts para testar o aplicativo Nest.js.
@nestjs/platform-express
define express como o servidor HTTP padrão em um aplicativo Nest.
/tsconfig.json
A tsconfig.json
file é um arquivo escrito em JSON (JavaScript Object Notation) que define as opções relacionadas ao TypeScript necessárias para compilar o aplicativo Nest.
/nest-cli.json
Ele contém metadados necessários para criar, organizar ou implantar aplicativos Nest.
/test
Este diretório contém todos os arquivos necessários para executar os testes do Nest. Nest usa a estrutura Jest para testar com configuração Jest no jest-e2e.json
arquivo.
/src
A src
directory é a pasta pai do núcleo do projeto Nest. Ele contém o main.ts
file que é o arquivo onde o aplicativo Nest é iniciado. O trabalho do main.ts
arquivo é para carregar AppModule
que é importado de /src/app.module.ts
.
Posteriormente neste guia, aprenderemos sobre Módulos; um dos principais componentes de um aplicativo Nest.js.
A AppModule
é uma classe criada como um módulo, usando o @Module
decorador. No app.module.ts
arquivo, AppService
da ./app.service
e AppController
da ./app.controller
também são importados.
A AppController
também é uma classe criada usando o @Controller
decorador, enquanto o AppService
é uma classe criada usando o @Injectable
anotação.
O legal do Nest é que ele tem poucos decoradores que adicionam metadados a qualquer classe e esses metadados definem o propósito dessa classe, de forma que:
@Controller()
transforma uma classe em um controlador.@Module()
transforma uma classe em um módulo.@Injectable()
transforma uma classe em um provedor.
Também no src
diretório é o app.controller.spec.ts
arquivo, que é um arquivo de teste para controladores.
Podemos executar o aplicativo usando nest start
.
O aplicativo começa às http://localhost:3000
no seu navegador:
Podemos alterar o conteúdo que aparece em http://localhost:3000
, indo até o app.service.ts
arquivo, onde o provedor da rota de índice foi definido.
Os blocos de construção de um aplicativo Nest.js
Existem três componentes principais de um aplicativo Nest.js:
- Módulos
- controladores
- prestadores
Ao aprender sobre os blocos de construção de um aplicativo Nest, vamos primeiro limpar o projeto Nest, excluindo o app.controller.spec.ts
, ./app.service
, app.module.ts
e ./app.controller
arquivos; saindo apenas main.ts
, para emular um ciclo de vida de desenvolvimento do zero.
Neste ponto, quando removemos o importado AppModule
arquivo de main.ts
, somos informados de que um argumento para ‘módulo’ não foi fornecido.
Para demonstrar os blocos de construção de um aplicativo Nest, daremos uma olhada em uma implementação simples de perfil de usuário, construindo uma API REST para lidar com operações CRUD em um objeto.
Módulos
No src
pasta crie um novo app.module.ts
arquivo e, em seguida, crie um AppModule
classe, que exportamos.
A seguir, importe o AppModule
classe em main.ts
, e corra nest start
.
Navegar para http://localhost:3000
no seu navegador e você receberá um erro 404:
Isso ocorre porque ainda não definimos uma rota para o URL base do app Nest.
Back in app.module.ts
nós temos o AppModule
class que temos ainda não é um módulo Nest. Para torná-lo um módulo Nest, adicionamos o @Module()
decorador importado de @nestjs/common
então passamos um objeto vazio.
import { Module } from '@nestjs/common';
@Module({})
export class AppModule {}
Agora temos um módulo Nest.js!
Observação: Um módulo é uma classe anotada com um @Module()
decorador.
Cada aplicativo Nest possui um módulo raiz, que serve como ponto de entrada para resolver a estrutura e os relacionamentos de um aplicativo Nest.
É altamente recomendável usar vários módulos para organizar os componentes da sua aplicação.
A @Module()
decorador permite que os desenvolvedores definam metadados sobre uma classe no aplicativo Nest.
No caso de existirem vários módulos, como módulo de usuários, módulo de pedidos, módulo de chat, etc., o app.module.ts
deve ser usado para registrar todos os outros módulos do app Nest.
Criando Rotas; Controladores
Os controladores são necessários para criar rotas em aplicativos Nest. A finalidade de um controlador é receber solicitações específicas para um aplicativo Nest; controlar o ciclo de solicitação e resposta para várias rotas dentro do aplicativo.
Quando uma solicitação HTTP é feita do cliente para o aplicativo Nest, a rota que corresponde à rota em que a solicitação está sendo feita trata a solicitação e retorna a resposta apropriada.
Para criar um controlador em um app Nest, temos que usar o @Controller()
decorador.
No src
diretório, crie um novo arquivo app.contoller.ts
, e aí podemos definir um controlador Nest:
import { Controller } from '@nestjs/common';
@Controller({})
export class AppController {}
É isso! Temos um controlador muito bom, mas para criar uma nova rota, precisamos primeiro informar nosso aplicativo Nest sobre o controlador criado.
Para conseguir isso, certificamo-nos de importar AppController
em app.module.ts e defina informações sobre os controladores em @Module()
decorador – como uma matriz de controladores:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
@Module({
controllers: [AppController],
})
export class AppModule {}
Manipulando Solicitações GET
Então definimos um simples getUser()
percurso (com o @Get()
decorador usado para lidar com solicitações HTTP GET para um caminho especificado) para servir como rota base, podemos acessar o mesmo no navegador em https://localhost:3000
:
import { Controller, Get } from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
getUser() {
return 'I am a great person';
}
}
Isto resulta em:
Hmm, aqui estamos retornando apenas uma string, mas e se quiséssemos retornar um objeto? Em vez de uma string, podemos definir um objeto:
import { Controller, Get } from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
getUser() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
}
}
Navegar para http://localhost:3000
no seu navegador e você verá o objeto:
Longe da rota base, que tal criar uma rota semelhante a http://localhost:3000/user
para buscar todos os usuários?
Podemos criar um controlador para lidar com essa rota de duas maneiras.
Uma maneira seria definir um novo método, usando o @Get()
decorador/manipulador.
import { Controller, Get } from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
getUser() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
}
}
Nest.js fornece decoradores ou manipuladores para todos os vários métodos HTTP, incluindo @Get()
, @Post()
, @Put()
, @Delete()
, @Patch()
, @Options()
e @Head()
.
A @All()
decorator define um endpoint que lida com todos os vários métodos.
Tratamento de solicitações POST
Também podemos definir solicitações POST para armazenamento de dados no banco de dados, usando o @Post()
decorador:
import { Controller, Post } from '@nestjs/common';
@Controller({})
export class AppController {
@Post()
store() {
return 'Post request successful';
}
}
Em seguida, testamos a solicitação POST usando Postman e notamos que a string é retornada com sucesso conforme definido.
Você pode perguntar: e se eu também quiser fazer mais do que retornar dados? Talvez, para enviar dados.
Para isso, você precisa injetar os dados dentro do método route, conforme mostrado:
import { Controller, Post, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller({})
export class AppController {
@Post()
store(@Req() req: Request) {
return req.body;
}
}
Agora, ao testarmos a solicitação POST com o Postman, podemos visualizar os dados que estão sendo enviados. Neste caso, é apenas um objeto vazio:
Confira nosso guia prático e prático para aprender Git, com práticas recomendadas, padrões aceitos pelo setor e folha de dicas incluída. Pare de pesquisar comandos Git no Google e realmente aprender -lo!
Roteamento dinâmico com parâmetros de rota
Suponha que você queira aceitar dados dinâmicos como parte de uma solicitação. Primeiro, precisamos definir o token no caminho da rota, para anotar a posição dinâmica na rota/URL, depois usando o @Param()
decorator, o parâmetro route pode ser acessado assim:
import { Controller, Get, Param } from '@nestjs/common';
@Controller({})
export class AppController {
@Get('/:userId')
getUser(@Param() userId: number) {
return userId;
}
}
A userId
é retornado com sucesso:
Tratamento de solicitações assíncronas
Nest.js é capaz de lidar com solicitações assíncronas que retornam uma promessa usando várias abordagens:
import { Controller, Get} from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
async findAll(): Promise {
return [];
}
}
Na abordagem acima, a assincronicidade é tratada usando o async
palavra-chave. Outra abordagem é retornar fluxos observáveis RxJS:
import { Controller, Get} from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
findAll(): Observable {
return of([]);
}
}
Aqui, Nest.js assinará a fonte nos bastidores e, quando o stream for concluído, pegará o último valor emitido automaticamente.
Tratamento de redirecionamentos no Nest
A @Redirect()
decorador é usado para redirecionar uma resposta para um URL diferente. O @Redirect()
decorator aceita dois argumentos – o URL para o qual redirecionar e o código de status no redirecionamento, ambos opcionais:
import { Controller, Get} from '@nestjs/common';
@Controller({})
export class AppController {
@Get()
@Redirect('https://www.ucheazubuko.com', 302)
getSite() {
return { url: 'https://stackabuse.com' };
}
}
Código de status de retorno
Para retornar o código de status de qualquer solicitação tratada no servidor Nest.js, o @HttpCode(…)
passa facilmente.
No Nest, o código de status padrão para solicitações GET é 200, uma solicitação POST é 201, uma solicitação de erro é 304
O código de status para uma solicitação do servidor pode ser definido conforme mostrado abaixo:
import { Controller, Post, HttpCode } from '@nestjs/common';
@Controller({})
export class AppController {
@Post()
@HttpCode(204)
create() {
return 'This action adds a new user to the app.';
}
}
Tratamento de solicitações DELETE
Semelhante a fazer uma solicitação POST, uma solicitação de exclusão pode ser tratada da seguinte forma:
import { Controller, Delete, Param } from '@nestjs/common';
@Controller({})
export class AppController {
@Delete('/:userId')
delete(@Param() params: { userId: number }) {
return params;
}
}
Tratamento de solicitações UPDATE
Uma solicitação para atualizar dados específicos no servidor pode ser tratada usando o @Patch()
decorador:
import { Controller, Patch, Req} from '@nestjs/common';
import { Request } from 'express';
@Controller({})
export class AppController {
@Patch('/:userId')
update(@Req() req: Request) {
return req.body;
}
}
Agora que vimos várias maneiras de definir controladores típicos que frequentemente teríamos em um servidor robusto, é importante observar que o controlador deve ser enxuto, limpo e definido por caso de uso, de modo que se houver outro controlador para definir user
rotas, então um diretório separado deve ser criado e dedicado para lidar com as mesmas - longe do AppController
.
Então em user.controller.ts
, podemos configurar todos os manipuladores de rota para serem prefixados com /user/
escrevendo o código como mostrado abaixo:
import { Controller, Get } from '@nestjs/common';
@Controller('/user')
export class UserController {
@Get()
getUser() {
return 'I am from the user controller';
}
}
A seguir, cadastre-se UserController
nas matrizes dos controladores em app.modules.ts
:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { UserController } from './controllers/user/user.controller';
@Module({
controllers: [AppController, UserController],
})
export class AppModule {}
Quando navegamos para https:localhost:3000/user
, ele retorna com sucesso:
Para manter a pasta do projeto ainda mais organizada do que está agora, podemos definir um user.module.ts
arquivo onde definiremos o UserController
:
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
@Module({
controllers: [UserController],
})
export class UserModule {}
Em seguida, importe UserModule
para dentro app.module.ts
:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { UserModule } from './user/user.module';
@Module({
controllers: [AppController],
imports: [UserModule],
})
export class AppModule {}
Com isso poderemos ter o mesmo efeito de antes.
Observação: O Nest facilita a (g)geração de (módulos) e (controladores) usando o nest g mo
e nest g co
comandos. Módulos específicos, como o user
módulo e controladores também podem ser criados rapidamente usando o Nest CLI, executando os comandos: nest g mo user
– para criar um módulo de usuário, e nest g co user
– para criar um controlador de usuário.
prestadores
Toda a busca de dados de um banco de dados deve ser feita por provedores, e não por controladores, para criar uma camada de abstração entre o código voltado para o usuário e o código que interage com dados potencialmente confidenciais. Entre essas camadas – a validação pode ser configurada para garantir o manuseio adequado do banco de dados. Com o Nest CLI, podemos criar provedores gerando serviços:
$ nest g s user
Isso cria um UserService
onde definiríamos toda a lógica de negócios para o UserController
, De modo que UserController
lida apenas com solicitações e respostas. Em user.service.ts
, vemos que o @Injectable()
decorador é usado para definir a classe. No Nest, o uso do @Injectable()
decorador é transformar serviços, repositórios ou classes auxiliares em um provedor.
Os provedores são injetados em uma classe por meio de seu construtor. Vamos dar uma olhada em um exemplo.
Anteriormente, em user.controller.ts
, definimos a lógica de negócios para obter o objeto de usuário, mas agora devemos definir o mesmo no UserService
:
import { Controller, Injectable } from '@nestjs/common';
@Controller({})
export class AppController {
@Injectable()
get() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria'; };
}
}
Em seguida, no user.controller.ts
arquivo, vamos definir um construtor no UserController
aula. Neste construtor, fornecemos um privado userService
, que é um tipo de UserService
aula. É com esse private que podemos aproveitar a lógica de negócios que definimos anteriormente para buscar os usuários:
import { Controller, Get } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('/user')
export class UserController {
constructor(private userService: UserService) {}
@Get()
getUser() {
return this.userService.get();
}
}
Assim, o UserController
classe, agora depende do UserService
classe em um conceito conhecido como
Injeção de dependência.
Da mesma forma, a lógica em ambos user.controller.ts
e user.service.ts
os arquivos são atualizados de acordo:
import {
Controller,
Delete,
Get,
Param,
Patch,
Post,
Req,
} from '@nestjs/common';
import { Request } from 'express';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@Get()
getUsers() {
return this.userService.get();
}
@Get('/:userId')
getUser(@Param() param: { userId: number }) {
return this.userService.getUser(param);
}
@Post()
store(@Req() req: Request) {
return this.userService.create(req);
}
@Patch('/:userId')
update(@Req() req: Request, @Param() param: { userId: number }) {
return this.userService.update(req, param);
}
@Delete()
delete(@Param() param: { userId: number }) {
return this.userService.delete(param);
}
}
import { Injectable } from '@nestjs/common';
import { Request } from 'express';
@Injectable()
export class UserService {
get() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
}
getUser(param: { userId: number }) {
return param;
}
create(req: Request) {
return req.body;
}
update(req: Request, param: { userId: number }) {
return { body: req.body, param };
}
delete(param: { userId: number }) {
return param;
}
}
Agora, vamos verificar se os endpoints funcionam como deveriam, usando o Postman.
Desmistificando a injeção de dependência em Nest.js
Ao construir componentes menores de um aplicativo, como uma classe ou módulo, sua classe pode depender da funcionalidade de outra classe ou módulo, por exemplo, a necessidade de acessar um serviço HTTP fornecido por uma classe diferente para fazer chamadas de API, ou camadas de serviço que interagem com a camada de persistência.
As dependências podem ser fornecidas nos controladores por meio de Injeção de dependência.
A injeção de dependência é um conceito e padrão de programação que expressa como partes de um aplicativo são entregues a outras partes do aplicativo que as necessitam, de forma a fornecer alta coesão, mas acoplamento fraco.
O Nest oferece suporte à injeção de dependência e você pode usá-lo em seus aplicativos Nest para aprimorar a modularidade do seu projeto.
Uma ilustração prática é representada assim:
Suponha que a classe A use alguma funcionalidade da classe B. Então diz-se que a classe A depende da classe B. Assim, para usar a classe B na classe A, precisamos primeiro criar uma instância da classe B (ou seja, criar um Objeto de classe B):
const b = new B ()
.
Transferir a tarefa de criar uma instância de uma classe para outra classe e usar diretamente a dependência da classe fornecida (o componente injetor) é conhecido como injeção de dependência.
Conselho: A injeção de dependência, ou DI, é um dos conceitos fundamentais em frameworks como Spring Boot, Nest.js e Angular.js, se quiser ler mais sobre isso, você pode conferir o documentação oficial do Angular.
Normalmente, uma classe deve se concentrar apenas em cumprir suas funções, em vez de ser usada para criar vários objetos que ela possa exigir ou não.
Benefícios da injeção de dependência.
- Ajuda nos testes de unidade.
- Com a injeção de dependência, o código padrão é reduzido, pois a inicialização das dependências é feita pelo componente injetor.
- O processo de extensão de um aplicativo fica mais fácil.
- A injeção de dependência ajuda a permitir o acoplamento fraco.
Explorando cargas úteis de solicitação
Lembre-se de que em vários manipuladores de solicitação, como POST e PATCH, conseguimos acessar a solicitação enviada pelo servidor usando o método @Req()
decorador. No entanto, há mais nisso.
Em vez de recuperar todo o objeto de solicitação, podemos apenas acessar partes específicas do objeto de solicitação que precisamos.
Assim, o Nest fornece vários decoradores que podem ser usados com os manipuladores de rotas HTTP para acessar objetos Express do Fastify:
Decoradores de ninho | Objeto Fastify ou Express que é acessado |
`@Request(), @Req()` | `req` |
`@Resposta(), @Res()` | `re“s` |
`@Próximo()` | `próximo` |
`@Sessão()` | `req.sessão` |
`@Param(param?:string)` | `req.params` / `req.params[param]` |
`@Body(param?:string)` | `req.body` / `req.body[param]` |
`@Query(param?:string)` | `req.query` / `req.query[param]` |
`@Headers(param?:string)` | `req.headers` / `req.headers[param]` |
`@Ip()` | `req.ip` |
`@HostParam()` | `req.hosts` |
Um exemplo típico seria a substituição do @Req()
decorator que usamos anteriormente para ter acesso ao corpo do resultado, com o @Body()
que já pode nos dar acesso direto ao corpo de uma solicitação sem perfurar:
@Post()
store(@Body() body: any) {
return this.userService.create(body);
}
@Patch('/:userId')
update(@Body() body: any, @Param() param: { userId: number }) {
return this.userService.update(body, param);
}
create(body: any) {
return body;
}
update(body: any, param: { userId: number }) {
return { body: body, param };
}
Em alguns casos, talvez você queira recuperar apenas propriedades específicas de uma carga de solicitação. Nesse caso, você teria que definir um esquema de Data Transfer Object (DTO). O esquema de transferência de dados é um objeto que define uma cópia do objeto que está sendo recuperado, mas é usado principalmente para transferir os dados entre o objeto que deve ser salvo ou recuperado e a camada de persistência. Normalmente, como esse processo é mais vulnerável a ataques, o DTO não contém tantos pontos de dados confidenciais. Esta característica também permite recuperar apenas determinados campos de um objeto.
No Nest, é recomendado o uso de classes para definir um Objeto de Transferência de Dados, pois o valor das classes é preservado durante a compilação.
Supondo que o corpo da solicitação possua um token e você não queira recuperar ou atualizar tais dados, então um DTO pode ser definido conforme mostrado abaixo:
@Patch('/:userId')
update(
@Body() updateUserDto: { name: string; email: string },
@Param() param: { userId: number },
) {
return this.userService.update(updateUserDto, param);
}
update(
updateUserDto: { name: string; email: string },
param: { userId: number },
) {
return { body: updateUserDto, param };
}
No entanto, você notará que definimos o tipo para updateUserDto
duas vezes; em user.service.ts
e em user.controller.ts
, mas precisamos manter nossos códigos DRY (Don't Repeat Yourself) para não nos repetirmos na base de código.
Para isso, em uma nova pasta /user/dto
no /user
diretório, precisamos criar um arquivo /update-user.dto.ts
com o .dto.ts
extensão onde definimos e exportamos o UpdateUserDto
classe para uso no user.service.ts
e user.controller.ts
arquivos:
export class UpdateUserDto {
name: string;
email: string;
}
...
import { UpdateUserDto } from './dto/update-user.dto';
@Patch('/:userId')
update(
@Body() updateUserDto: UpdateUserDto,
@Param() param: { userId: number },
) {
return this.userService.update(updateUserDto, param);
}
...
import { UpdateUserDto } from './dto/update-user.dto';
update(updateUserDto: UpdateUserDto, param: { userId: number }) {
return { body: updateUserDto, param };
}
Pipe e validação
Suponha que seja necessário validar os dados obtidos quando uma solicitação é feita no servidor.
No Nest, podemos testar a exatidão de quaisquer dados que entram ou saem do aplicativo usando pipes instalando duas dependências – class-validator
e class-transformer
.
Um pipe é uma classe definida com o
@Injectable()
decorador (portanto, pipes são provedores), que implementa oPipeTransform
interface. Eles transformam os dados no formato desejado e avaliam os dados de forma que, se os dados forem considerados válidos, eles passam inalterados; caso contrário, uma exceção é lançada. Para usar um pipe, você precisa vincular uma instância da classe de pipe específica ao contexto apropriado.
A class-validator
pacote permite validar decoradores e não decoradores, utilizando validador.js internamente. Enquanto o class-transformer
O pacote torna possível transformar objetos em instâncias de uma classe, transformar classe em objeto e serializar ou desserializar objetos com base em determinados critérios.
Os oito tubos fornecidos pela Nest são:
ValidationPipe
ParseArrayPipe
ParseIntPipe
ParseUUIDPipe
ParseBoolPipe
DefaultValuePipe
ParseEnumPipe
ParseFloatPipe
Para demonstrar a validação no Nest neste guia, usaremos o recurso integrado ValidationPipe
que torna possível impor a validação em cargas úteis de solicitação e combina bem com o class-validator
pacote; regras específicas são declaradas com anotações simples nas declarações de objeto de transferência de dados/classe local em cada módulo.
Para começar a usar o integrado ValidationPipe
que é exportado de @nestjs/common
, vamos instalar o class-validator
e class-transformer
pacotes:
$ npm i --save class-validator class-transformer
# Or
$ yarn add class-validator class-transformer
# Or
$ pnpm install class-validator class-transformer
Em seguida, navegue para main.ts
onde vamos ligar ValidationPipe
no nível raiz do aplicativo para garantir que todos os endpoints do nosso aplicativo estejam protegidos contra a recuperação de dados inválidos:
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();
A seguir, nas declarações do Data Transfer Object de cada módulo, adicionamos algumas regras de validação declarando as verificações de dados apropriadas para cada dado individual. No nosso caso, declararíamos regras de validação apropriadas para name
e email
in UpdateUserDto
:
import { IsEmail, IsString } from 'class-validator';
export class UpdateUserDto {
@IsString()
name: string;
@IsEmail()
email: string;
}
A @IsString()
decorador verifica se um dado dado é uma string real, e o @IsEmail()
o validador verifica se um determinado dado é um email, caso contrário, ele retorna falso e lança uma exceção.
Agora, se tentarmos fazer uma PATCH
solicitação para um perfil de usuário e insira um número em vez de um e-mail válido, por exemplo, uma exceção será lançada:
Com eles, temos uma validação muito bacana em nosso app Nest.
Ao validar com ValidationPipe
, também é possível filtrar nossas propriedades que não queremos que nosso manipulador de método receba. Por exemplo, se nosso manipulador espera apenas name
e email
propriedades, mas uma solicitação também inclui um country
propriedade, podemos remover o country
propriedade do objeto resultante definindo whitelist
para true
quando nós instanciarmos ValidationPipe
:
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();
Vinculação de tubos no nível de parâmetro do método
Os tubos também podem ser definidos em params
, também. Para isso, vincularemos o pipe no nível de parâmetro do método.
Até agora, embora tenhamos definido o userId
para ser um número, você notará que se fizermos uma solicitação com o userId
como uma string, será bem-sucedido independentemente:
Para garantir que o valor de userId
deve ser sempre um número, declararemos que ele vincula o getUser()
manipulador de método com uma verificação de validação que garante o mesmo:
...
import { ParseIntPipe } from '@nestjs/common';
@Get('/:userId')
getUser(@Param('userId', ParseIntPipe) userId: number) {
return this.userService.getUser(userId);
}
getUser(userId: number) {
return { userId };
}
A ParseIntPipe
define o ParseInt Pipe integrado e garante que os dados contra os quais ele é executado devem ser um número inteiro.
Agora, quando fazemos um GET
solicitação para um inválido userId
da string “ab”, a validação falha e uma exceção é lançada com um 400
código de estado:
Mas com um valor numérico, a validação passa com sucesso:
Também podemos atualizar outros manipuladores de métodos de acordo para garantir a validação adequada:
import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Patch,
Post,
Req,
} from '@nestjs/common';
import { Request } from 'express';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@Get()
getUsers() {
return this.userService.get();
}
@Get('/:userId')
getUser(@Param('userId', ParseIntPipe) userId: number) {
return this.userService.getUser(userId);
}
@Post()
store(@Req() req: Request) {
return this.userService.create(req);
}
@Patch('/:userId')
update(
@Body() updateUserDto: { name: string; email: string },
@Param('userId', ParseIntPipe) userId: number,
) {
return this.userService.update(updateUserDto, userId);
}
@Delete()
delete(@Param('userId', ParseIntPipe) userId: number) {
return this.userService.delete(userId);
}
}
import { Injectable } from '@nestjs/common';
import { Request } from 'express';
import { UpdateUserDto } from './dto/user-update.dto';
@Injectable()
export class UserService {
get() {
return { name: 'Uchechukwu Azubuko', country: 'Nigeria' };
}
getUser(userId: number) {
return { userId };
}
create(req: Request) {
return req.body;
}
update(updateUserDto: UpdateUserDto, userId: number) {
return { body: updateUserDto, userId };
}
delete(userId: number) {
return { userId };
}
}
Agora, garantimos a técnica de melhores práticas para validar os dados que chegam ao nosso aplicativo, talvez de uma fonte externa, a qualquer momento.
Conclusão
Neste guia, você aprendeu sobre o que há de mais recente no bloco Node.js; Nest.js e tudo o que é necessário para ajudá-lo a começar se desejar criar um aplicativo com ele. Você aprendeu o que é o Nest, seus recursos, como criar um projeto Nest, como lidar com os dados recebidos em um aplicativo Nest e como validar os dados recebidos. Ao todo, você aprendeu sobre os blocos de construção de qualquer aplicativo Nest e o valor que cada componente traz para um aplicativo Nest.js.
A partir deste ponto, ainda há muito a aprender em relação à construção de um aplicativo de nível empresarial com o Nest, mas você conseguiu cobrir com sucesso os conceitos fundamentais que podem colocá-lo em funcionamento para tudo o que está por vir.
Fique atento a um novo guia no futuro, onde aprenderemos como construir uma API tranquila com Nest e MySQL.
Obrigado pela leitura!