Pedro's dev blog

Nest JS:Visão Geral e Uso parte 1

NEST.png
Published on
/44 mins read/---

Introdução

O Nest (NestJS) é um framework para construir aplicações Node.js server-side eficientes e escaláveis. Ele utiliza JavaScript progressivo, é construído com e suporta totalmente TypeScript (mas ainda permite que os desenvolvedores codifiquem em JavaScript puro) e combina elementos de OOP (Programação Orientada a Objetos), FP (Programação Funcional) e FRP (Programação Reativa Funcional).

Debaixo dos panos, o Nest utiliza frameworks robustos de Servidor HTTP como o Express (o padrão) e opcionalmente pode ser configurado para usar o Fastify também!

O Nest fornece um nível de abstração acima desses frameworks Node.js comuns (Express/Fastify), mas também expõe suas APIs diretamente ao desenvolvedor. Isso dá aos desenvolvedores a liberdade de usar módulos de terceiros disponíveis para a plataforma subjacente.

Filosofia

Nos últimos anos, graças ao Node.js, o JavaScript se tornou a “língua franca” da web para aplicações front-end e back-end. Isso deu origem a projetos incríveis como :

que melhoram a produtividade dos desenvolvedores e possibilitam a criação de aplicações front-end rápidas, testáveis e extensíveis. No entanto, embora existam muitas bibliotecas, auxiliares e ferramentas excelentes para Node (e JavaScript do lado do servidor), nenhuma delas resolve efetivamente o principal problema de arquitetura.

O Nest fornece uma arquitetura de aplicação pronta para uso que permite que desenvolvedores e equipes criem aplicações altamente testáveis, escaláveis, fracamente acopladas e de fácil manutenção. A arquitetura é fortemente inspirada no Angular.

Instalação

Para começar, você pode gerar o projeto com o Nest CLI ou clonar um projeto inicial (ambos produzirão o mesmo resultado).

Para gerar o projeto com o Nest CLI, execute os seguintes comandos. Isso criará um novo diretório de projeto e preencherá o diretório com os arquivos Nest principais iniciais e módulos de suporte, criando uma estrutura de base convencional para seu projeto. Criar um novo projeto com o Nest CLI é recomendado para usuários iniciantes. Continuaremos com essa abordagem em Primeiros Passos.

Terminal
$ npm i -g @nestjs/cli
$ nest new nome-do-projeto

DICA Para criar um novo projeto TypeScript com um conjunto de recursos mais rigoroso, passe a flag --strict para o comando nest new.

Alternativas

Alternativamente, para instalar o projeto inicial do TypeScript com Git:

Terminal
$ git clone https://github.com/nestjs/typescript-starter.git projeto
$ cd projeto
$ npm install
$ npm run start

DICA Se você deseja clonar o repositório sem o histórico do git, você pode usar o degit.

Abra seu navegador e navegue até http://localhost:3000/.

Para instalar a versão JavaScript do projeto inicial, use javascript-starter.git na sequência de comandos acima.

Primeiros passos

Nesta série,Será os fundamentos do Nest. Para se familiarizar com os blocos de construção essenciais das aplicações Nest, construiremos uma aplicação CRUD básica com recursos que abrangem muita coisa em um nível introdutório.

Linguagem

Nest é compatível com TypeScript e JavaScript puro. O Nest aproveita os recursos mais recentes da linguagem, então, para usá-lo com JavaScript puro, precisamos de um compilador Babel.

Usaremos principalmente TypeScript nos exemplos , mas você sempre pode alternar os trechos de código para a sintaxe JavaScript puro.

Pré-requisitos

Certifique-se de que o Node.js (versão >= 20) esteja instalado no seu sistema operacional.

Configuração

Configurar um novo projeto é bastante simples com o Nest CLI. Com o npm instalado, você pode criar um novo projeto Nest com os seguintes comandos no terminal do seu sistema operacional:

Terminal
$ npm i -g @nestjs/cli
$ nest new nome-do-projeto

DICA Para criar um novo projeto com o conjunto de recursos mais rigoroso do TypeScript, passe a flag --strict para o comando nest new.

O diretório nome-do-projeto será criado, os módulos node e alguns outros arquivos boilerplate serão instalados e um diretório src/ será criado e preenchido com vários arquivos principais.

src
app.controller.spec.ts
app.controller.ts
app.module.ts
app.service.ts
main.ts

Aqui está uma breve visão geral desses arquivos principais:

  • app.controller.ts: Um controller básico com uma única rota.
  • app.controller.spec.ts: Os testes unitários para o controller.
  • app.module.ts: O módulo raiz da aplicação.
  • app.service.ts: Um serviço básico com um único método.
  • main.ts: O arquivo de entrada da aplicação que usa a função principal NestFactory para criar uma instância da aplicação Nest.

O main.ts inclui uma função async, que inicializará nossa aplicação:

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

Para criar uma instância da aplicação Nest, usamos a classe principal NestFactory. NestFactory expõe alguns métodos estáticos que permitem criar uma instância da aplicação. O método create() retorna um objeto de aplicação, que atende à interface INestApplication. Este objeto fornece um conjunto de métodos que são descritos nos próximos capítulos. No exemplo main.ts acima, simplesmente iniciamos nosso listener HTTP, que permite que a aplicação aguarde as requisições HTTP de entrada.

Observe que um projeto gerado com o Nest CLI cria uma estrutura de projeto inicial que incentiva os desenvolvedores a seguir a convenção de manter cada módulo em seu próprio diretório dedicado.

DICA Por padrão, se ocorrer algum erro ao criar a aplicação, seu aplicativo sairá com o código 1. Se você quiser que ele lance um erro em vez disso, desabilite a opção abortOnError (por exemplo, NestFactory.create(AppModule, { abortOnError: false })).

Plataforma

O Nest tem como objetivo ser um framework agnóstico de plataforma. A independência da plataforma torna possível criar partes lógicas reutilizáveis que os desenvolvedores podem aproveitar em vários tipos diferentes de aplicações. Tecnicamente, o Nest é capaz de trabalhar com qualquer framework HTTP Node, uma vez que um adaptador seja criado. Existem duas plataformas HTTP suportadas fora da caixa: express e fastify. Você pode escolher aquele que melhor se adapta às suas necessidades.

  • platform-express: Express é um framework web minimalista bem conhecido para node. É uma biblioteca testada em batalha, pronta para produção, com muitos recursos implementados pela comunidade. O pacote @nestjs/platform-express é usado por padrão. Muitos usuários são bem servidos com o Express e não precisam tomar nenhuma ação para habilitá-lo.
  • platform-fastify: Fastify é um framework de alto desempenho e baixa sobrecarga altamente focado em fornecer máxima eficiência e velocidade.

Independentemente da plataforma utilizada, ela expõe sua própria interface de aplicação. Estas são vistas respectivamente como NestExpressApplication e NestFastifyApplication.

Quando você passa um tipo para o método NestFactory.create(), como no exemplo abaixo, o objeto app terá métodos disponíveis exclusivamente para essa plataforma específica. Observe, no entanto, que você não precisa especificar um tipo, a menos que realmente queira acessar a API da plataforma subjacente.

const app = await NestFactory.create<NestExpressApplication>(AppModule);

Executando a aplicação

Depois que o processo de instalação for concluído, você pode executar o seguinte comando no prompt de comando do seu sistema operacional para iniciar a aplicação ouvindo as requisições HTTP de entrada:

$ npm run start

DICA Para acelerar o processo de desenvolvimento (construções 20 vezes mais rápidas), você pode usar o construtor SWC passando a flag -b swc para o script de início, da seguinte forma npm run start -- -b swc.

Este comando inicia o aplicativo com o servidor HTTP ouvindo na porta definida no arquivo src/main.ts. Depois que a aplicação estiver em execução, abra seu navegador e navegue até http://localhost:3000/. Você deve ver a mensagem Hello World!.

Para observar as alterações em seus arquivos, você pode executar o seguinte comando para iniciar a aplicação:

$ npm run start:dev

Este comando observará seus arquivos, recompilando e recarregando automaticamente o servidor.

Linting e formatação

O CLI fornece o melhor esforço para gerar um fluxo de trabalho de desenvolvimento confiável em escala. Assim, um projeto Nest gerado vem com um linter de código e um formatador pré-instalados (respectivamente, eslint e prettier).

DICA Não tem certeza sobre o papel de formatadores versus linters? Aprenda a diferença aqui.

Para garantir a máxima estabilidade e extensibilidade, usamos os pacotes de cli base do eslint e prettier. Esta configuração permite uma integração de IDE organizada com extensões oficiais por design.

Para ambientes headless onde uma IDE não é relevante (Integração Contínua, Hooks Git, etc.) um projeto Nest vem com scripts npm prontos para uso.

# Lint e auto-correção com eslint
$ npm run lint
 
# Formatar com prettier
$ npm run format

Controllers

Os controllers são responsáveis por lidar com as requisições de entrada e enviar respostas de volta para o cliente.

O objetivo de um controller é lidar com requisições específicas para a aplicação. O mecanismo de roteamento determina qual controller lidará com cada requisição. Frequentemente, um controller tem várias rotas, e cada rota pode executar uma ação diferente.

Para criar um controller básico, usamos classes e decorators. Os decorators vinculam classes com os metadados necessários, permitindo que o Nest crie um mapa de roteamento que conecta as requisições aos seus controllers correspondentes.

DICA Para criar rapidamente um controller CRUD com validação integrada, você pode usar o gerador CRUD do CLI: nest g resource [name].

Roteamento

No exemplo a seguir, usaremos o decorator @Controller(), que é necessário para definir um controller básico. Especificaremos um prefixo de caminho de rota opcional de cats. Usar um prefixo de caminho no decorator @Controller() nos ajuda a agrupar rotas relacionadas e reduz o código repetitivo. Por exemplo, se quisermos agrupar rotas que gerenciam interações com uma entidade cat sob o caminho /cats, podemos especificar o prefixo do caminho cats no decorator @Controller(). Dessa forma, não precisamos repetir essa parte do caminho para cada rota no arquivo.

// cats.controller.ts
import { Controller, Get } from '@nestjs/common';
 
@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}

DICA Para criar um controller usando o CLI, basta executar o comando $ nest g controller [name].

O decorator do método de requisição HTTP @Get() colocado antes do método findAll() diz ao Nest para criar um handler para um endpoint específico para requisições HTTP. Este endpoint é definido pelo método de requisição HTTP (GET neste caso) e o caminho da rota. Então, qual é o caminho da rota? O caminho da rota para um handler é determinado pela combinação do prefixo (opcional) declarado para o controller com qualquer caminho especificado no decorator do método. Como definimos um prefixo (cats) para cada rota e não adicionamos nenhum caminho específico no decorator do método, o Nest mapeará as requisições GET /cats para este handler.

Como mencionado, o caminho da rota inclui tanto o prefixo do caminho do controller opcional quanto qualquer string de caminho especificada no decorator do método. Por exemplo, se o prefixo do controller for cats e o decorator do método for @Get('breed'), a rota resultante será GET /cats/breed.

Em nosso exemplo acima, quando uma requisição GET é feita para este endpoint, o Nest roteia a requisição para o método findAll() definido pelo usuário. Observe que o nome do método que escolhemos aqui é totalmente arbitrário. Embora devamos declarar um método para vincular a rota, o Nest não atribui nenhum significado específico ao nome do método.

Este método retornará um código de status 200 junto com a resposta associada, que neste caso é apenas uma string. Por que isso acontece? Para explicar, primeiro precisamos apresentar o conceito de que o Nest usa duas opções diferentes para manipular as respostas:

  • Padrão (recomendado): Usando este método integrado, quando um handler de requisição retorna um objeto ou array JavaScript, ele será automaticamente serializado para JSON. Quando ele retorna um tipo primitivo JavaScript (por exemplo, string, número, booleano), no entanto, o Nest enviará apenas o valor sem tentar serializá-lo. Isso torna o tratamento da resposta simples: basta retornar o valor, e o Nest cuidará do resto.

    Além disso, o código de status da resposta é sempre 200 por padrão, exceto para requisições POST que usam 201. Podemos facilmente alterar esse comportamento adicionando o decorator @HttpCode(...) em um nível de handler (consulte Códigos de status).

  • Específico da biblioteca: Podemos usar o objeto de resposta específico da biblioteca (por exemplo, Express), que pode ser injetado usando o decorator @Res() na assinatura do handler do método (por exemplo, findAll(@Res() response)). Com essa abordagem, você tem a capacidade de usar os métodos nativos de manipulação de resposta expostos por esse objeto. Por exemplo, com o Express, você pode construir respostas usando código como response.status(200).send().

AVISO O Nest detecta quando o handler está usando @Res() ou @Next(), indicando que você escolheu a opção específica da biblioteca. Se ambas as abordagens forem usadas ao mesmo tempo, a abordagem Padrão é automaticamente desabilitada para esta rota única e não funcionará mais como esperado. Para usar ambas as abordagens ao mesmo tempo (por exemplo, injetando o objeto de resposta apenas para definir cookies/cabeçalhos, mas ainda deixar o restante para o framework), você deve definir a opção passthrough como true no decorator @Res({ passthrough: true }).

Explore seu gráfico com NestJS Devtools

  • Visualizador de gráfico
  • Navegador de rotas
  • Playground interativo
  • Integração CI/CD

INSCREVA-SE

Objeto de requisição

Os handlers frequentemente precisam acessar os detalhes da requisição do cliente. O Nest fornece acesso ao objeto de requisição da plataforma subjacente (Express por padrão). Você pode acessar o objeto de requisição instruindo o Nest a injetá-lo usando o decorator @Req() na assinatura do handler.

// cats.controller.ts
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';
 
@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Req() request: Request): string {
    return 'This action returns all cats';
  }
}

DICA Para aproveitar as tipagens do express (como no exemplo request: Request acima), certifique-se de instalar o pacote @types/express.

O objeto de requisição representa a requisição HTTP e contém propriedades para a string de consulta, parâmetros, cabeçalhos HTTP e corpo (leia mais aqui). Na maioria dos casos, você não precisa acessar manualmente essas propriedades. Em vez disso, você pode usar decorators dedicados como @Body() ou @Query(), que estão disponíveis fora da caixa. Abaixo está uma lista dos decorators fornecidos e os objetos específicos da plataforma correspondentes que eles representam.

  • @Request(), @Req(): req

  • @Response(), @Res()*: res

  • @Next(): next

  • @Session(): req.session

  • @Param(key?: string): req.params / req.params[key]

  • @Body(key?: string): req.body / req.body[key]

  • @Query(key?: string): req.query / req.query[key]

  • @Headers(name?: string): req.headers / req.headers[name]

  • @Ip(): req.ip

  • @HostParam(): req.hosts

  • Para compatibilidade com tipagens em plataformas HTTP subjacentes (por exemplo, Express e Fastify), o Nest fornece os decorators @Res() e @Response(). @Res() é simplesmente um alias para @Response(). Ambos expõem diretamente a interface do objeto de resposta da plataforma nativa subjacente. Ao usá-los, você também deve importar as tipagens para a biblioteca subjacente (por exemplo, @types/express) para aproveitar ao máximo. Observe que, ao injetar @Res() ou @Response() em um handler de método, você coloca o Nest no modo Específico da biblioteca para esse handler e se torna responsável por gerenciar a resposta. Ao fazê-lo, você deve emitir algum tipo de resposta fazendo uma chamada no objeto de resposta (por exemplo, res.json(...) ou res.send(...)), ou o servidor HTTP travará.

DICA Para aprender como criar seus próprios decorators personalizados, visite este capítulo.

Recursos

Anteriormente, definimos um endpoint para buscar o recurso cats (rota GET). Normalmente, também queremos fornecer um endpoint que crie novos registros. Para isso, vamos criar o handler POST:

// cats.controller.ts
import { Controller, Get, Post } from '@nestjs/common';
 
@Controller('cats')
export class CatsController {
  @Post()
  create(): string {
    return 'This action adds a new cat';
  }
 
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}

É simples assim. O Nest fornece decorators para todos os métodos HTTP padrão: @Get(), @Post(), @Put(), @Delete(), @Patch(), @Options() e @Head(). Além disso, @All() define um endpoint que lida com todos eles.

Wildcards de rota

Rotas baseadas em padrão também são suportadas no NestJS. Por exemplo, o asterisco (*) pode ser usado como um curinga para corresponder a qualquer combinação de caracteres em uma rota no final de um caminho. No exemplo a seguir, o método findAll() será executado para qualquer rota que comece com abcd/, independentemente do número de caracteres que se seguem.

@Get('abcd/*')
findAll() {
  return 'This route uses a wildcard';
}

O caminho da rota 'abcd/*' corresponderá a abcd/, abcd/123, abcd/abc e assim por diante. O hífen (-) e o ponto (.) são interpretados literalmente por caminhos baseados em string.

Essa abordagem funciona tanto no Express quanto no Fastify. No entanto, com a última versão do Express (v5), o sistema de roteamento tornou-se mais restrito. No Express puro, você deve usar um curinga nomeado para fazer a rota funcionar - por exemplo, abcd/*splat, onde splat é simplesmente o nome do parâmetro curinga e não tem nenhum significado especial. Você pode nomeá-lo como quiser. Dito isso, como o Nest fornece uma camada de compatibilidade para o Express, você ainda pode usar o asterisco (*) como um curinga.

Quando se trata de asteriscos usados no meio de uma rota, o Express requer curingas nomeados (por exemplo, ab{*splat}cd), enquanto o Fastify não os suporta de forma alguma.

Código de status

Como mencionado, o código de status padrão para respostas é sempre 200, exceto para requisições POST, que são padronizadas para 201. Você pode facilmente alterar esse comportamento usando o decorator @HttpCode(...) no nível do handler.

@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}

DICA Importe HttpCode do pacote @nestjs/common.

Frequentemente, seu código de status não é estático, mas depende de vários fatores. Nesse caso, você pode usar um objeto de resposta específico da biblioteca (injetar usando @Res()) (ou, em caso de erro, lançar uma exceção).

Cabeçalhos de resposta

Para especificar um cabeçalho de resposta personalizado, você pode usar o decorator @Header() ou um objeto de resposta específico da biblioteca (e chamar res.header() diretamente).

@Post()
@Header('Cache-Control', 'no-store')
create() {
  return 'This action adds a new cat';
}

DICA Importe Header do pacote @nestjs/common.

Redirecionamento

Para redirecionar uma resposta para uma URL específica, você pode usar o decorator @Redirect() ou um objeto de resposta específico da biblioteca (e chamar res.redirect() diretamente).

@Redirect() aceita dois argumentos, url e statusCode, ambos opcionais. O valor padrão de statusCode é 302 (Found) se omitido.

@Get()
@Redirect('https://nestjs.com', 301)

DICA Às vezes, você pode querer determinar o código de status HTTP ou a URL de redirecionamento dinamicamente. Faça isso retornando um objeto seguindo a interface HttpRedirectResponse (de @nestjs/common).

Os valores retornados substituirão quaisquer argumentos passados para o decorator @Redirect(). Por exemplo:

@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version) {
  if (version && version === '5') {
    return { url: 'https://docs.nestjs.com/v5/' };
  }
}

Parâmetros de rota

Rotas com caminhos estáticos não funcionarão quando você precisar aceitar dados dinâmicos como parte da requisição (por exemplo, GET /cats/1 para obter o cat com id 1). Para definir rotas com parâmetros, você pode adicionar tokens de parâmetro de rota no caminho da rota para capturar os valores dinâmicos da URL. O token de parâmetro de rota no exemplo do decorator @Get() abaixo ilustra essa abordagem. Esses parâmetros de rota podem então ser acessados usando o decorator @Param(), que deve ser adicionado à assinatura do método.

DICA As rotas com parâmetros devem ser declaradas após quaisquer caminhos estáticos. Isso impede que os caminhos parametrizados interceptem o tráfego destinado aos caminhos estáticos.

@Get(':id')
findOne(@Param() params: any): string {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}

O decorator @Param() é usado para decorar um parâmetro de método (no exemplo acima, params), tornando os parâmetros da rota acessíveis como propriedades desse parâmetro de método decorado dentro do método. Como mostrado no código, você pode acessar o parâmetro id referenciando params.id. Alternativamente, você pode passar um token de parâmetro específico para o decorator e referenciar diretamente o parâmetro da rota pelo nome dentro do corpo do método.

DICA Importe Param do pacote @nestjs/common.

@Get(':id')
findOne(@Param('id') id: string): string {
  return `This action returns a #${id} cat`;
}

Roteamento de subdomínio

O decorator @Controller pode aceitar uma opção host para exigir que o host HTTP das requisições de entrada corresponda a algum valor específico.

@Controller({ host: 'admin.example.com' })
export class AdminController {
  @Get()
  index(): string {
    return 'Admin page';
  }
}

AVISO Como o Fastify não suporta roteadores aninhados, se você estiver usando o roteamento de subdomínio, é recomendável usar o adaptador Express padrão em vez disso.

Semelhante a um caminho de rota, a opção hosts pode usar tokens para capturar o valor dinâmico naquela posição no nome do host. O token de parâmetro host no exemplo do decorator @Controller() abaixo demonstra esse uso. Os parâmetros host declarados dessa forma podem ser acessados usando o decorator @HostParam(), que deve ser adicionado à assinatura do método.

@Controller({ host: ':account.example.com' })
export class AccountController {
  @Get()
  getInfo(@HostParam('account') account: string) {
    return account;
  }
}

Compartilhamento de estado

Para desenvolvedores que vêm de outras linguagens de programação, pode ser surpreendente saber que no Nest, quase tudo é compartilhado entre as requisições de entrada. Isso inclui recursos como o pool de conexão de banco de dados, serviços singleton com estado global e muito mais. É importante entender que o Node.js não usa o Modelo Sem Estado Multi-Threaded de requisição/resposta, onde cada requisição é tratada por um thread separado. Como resultado, usar instâncias singleton no Nest é totalmente seguro para nossas aplicações.

Dito isso, existem casos de borda específicos em que ter durações baseadas em requisição para controllers pode ser necessário. Exemplos incluem cache por requisição em aplicações GraphQL, rastreamento de requisições ou implementação de multi-tenancy. Você pode aprender mais sobre como controlar escopos de injeção aqui.

Assincronia

Amamos JavaScript moderno, especialmente sua ênfase no tratamento de dados assíncronos. É por isso que o Nest suporta totalmente funções async. Toda função async deve retornar uma Promise, o que permite que você retorne um valor adiado que o Nest pode resolver automaticamente. Aqui está um exemplo:

// cats.controller.ts
@Get()
async findAll(): Promise<any[]> {
  return [];
}

Este código é perfeitamente válido. Mas o Nest vai um passo além, permitindo que os handlers de rota também retornem fluxos observáveis ​​RxJS. O Nest lidará com a assinatura internamente e resolverá o valor emitido final assim que o fluxo for concluído.

// cats.controller.ts
@Get()
findAll(): Observable<any[]> {
  return of([]);
}

Ambas as abordagens são válidas, e você pode escolher aquela que melhor se adapta às suas necessidades.

Cargas de requisição

Em nosso exemplo anterior, o handler de rota POST não aceitou nenhum parâmetro do cliente. Vamos corrigir isso adicionando o decorator @Body().

Antes de prosseguirmos (se você estiver usando TypeScript), precisamos definir o esquema DTO (Data Transfer Object). Um DTO é um objeto que especifica como os dados devem ser enviados pela rede. Podemos definir o esquema DTO usando interfaces TypeScript ou classes simples. No entanto, recomendamos usar classes aqui. Por quê? As classes fazem parte do padrão JavaScript ES6, portanto, permanecem intactas como entidades reais no JavaScript compilado. Em contraste, as interfaces TypeScript são removidas durante a transpilção, o que significa que o Nest não pode referenciá-las em tempo de execução. Isso é importante porque recursos como Pipes dependem de ter acesso ao metatipo de variáveis ​​em tempo de execução, o que só é possível com classes.

Vamos criar a classe CreateCatDto:

// create-cat.dto.ts
export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

Ele tem apenas três propriedades básicas. Posteriormente, podemos usar o DTO recém-criado dentro do CatsController:

// cats.controller.ts
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return 'This action adds a new cat';
}

DICA Nosso ValidationPipe pode filtrar propriedades que não devem ser recebidas pelo handler do método. Nesse caso, podemos colocar na lista de permissões as propriedades aceitáveis ​​e qualquer propriedade não incluída na lista de permissões é automaticamente removida do objeto resultante. No exemplo CreateCatDto, nossa lista de permissões são as propriedades name, age e breed. Aprenda mais aqui.

Parâmetros de consulta

Ao lidar com parâmetros de consulta em suas rotas, você pode usar o decorator @Query() para extraí-los das requisições recebidas. Vamos ver como isso funciona na prática.

Considere uma rota onde queremos filtrar uma lista de gatos com base em parâmetros de consulta como idade e raça. Primeiro, defina os parâmetros de consulta no CatsController:

// cats.controller.ts
@Get()
async findAll(@Query('age') age: number, @Query('breed') breed: string) {
  return `This action returns all cats filtered by age: ${age} and breed: ${breed}`;
}

Neste exemplo, o decorator @Query() é usado para extrair os valores de age e breed da string de consulta. Por exemplo, uma requisição para:

GET /cats?age=2&breed=Persian

resultaria em age sendo 2 e breed sendo Persian.

Se sua aplicação exigir o tratamento de parâmetros de consulta mais complexos, como objetos aninhados ou arrays:

?filter[where][name]=John&filter[where][age]=30
?item[]=1&item[]=2

você precisará configurar seu adaptador HTTP (Express ou Fastify) para usar um analisador de consulta apropriado. No Express, você pode usar o analisador estendido, que permite objetos de consulta ricos:

const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.set('query parser', 'extended');

No Fastify, você pode usar a opção querystringParser:

const app = await NestFactory.create<NestFastifyApplication>(
  AppModule,
  new FastifyAdapter({
    querystringParser: (str) => qs.parse(str),
  }),
);

DICA qs é um analisador de string de consulta que suporta aninhamento e arrays. Você pode instalá-lo usando npm install qs.

Tratamento de erros

Existe um capítulo separado sobre como lidar com erros (ou seja, trabalhar com exceções) aqui.

Exemplo de recurso completo

Abaixo está um exemplo que demonstra o uso de vários decorators disponíveis para criar um controller básico. Este controller fornece alguns métodos para acessar e manipular dados internos.

// cats.controller.ts
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto';
 
@Controller('cats')
export class CatsController {
  @Post()
  create(@Body() createCatDto: CreateCatDto) {
    return 'This action adds a new cat';
  }
 
  @Get()
  findAll(@Query() query: ListAllEntities) {
    return `This action returns all cats (limit: ${query.limit} items)`;
  }
 
  @Get(':id')
  findOne(@Param('id') id: string) {
    return `This action returns a #${id} cat`;
  }
```typescript
  @Put(':id')
  update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
    return `This action updates a #${id} cat`;
  }
 
  @Delete(':id')
  remove(@Param('id') id: string) {
    return `This action removes a #${id} cat`;
  }
}

DICA O Nest CLI oferece um gerador (esquemático) que cria automaticamente todo o código boilerplate, poupando você de fazê-lo manualmente e melhorando a experiência geral do desenvolvedor. Saiba mais sobre este recurso aqui.

Começando a usar

Mesmo com o CatsController totalmente definido, o Nest ainda não o conhece e não criará automaticamente uma instância da classe.

Os controllers devem sempre fazer parte de um módulo, e é por isso que incluímos o array controllers dentro do decorator @Module(). Como não definimos nenhum outro módulo além do módulo raiz AppModule, usaremos ele para registrar o CatsController:

// app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
 
@Module({
  controllers: [CatsController],
})
export class AppModule {}

Anexamos os metadados à classe do módulo usando o decorator @Module(), e agora o Nest pode facilmente determinar quais controllers precisam ser montados.

Abordagem específica da biblioteca

Até agora, cobrimos a maneira Nest padrão de manipular respostas. Outra abordagem é usar um objeto de resposta específico da biblioteca. Para injetar um objeto de resposta específico, podemos usar o decorator @Res(). Para destacar as diferenças, vamos reescrever o CatsController assim:

// cats.controller.ts
import { Controller, Get, Post, Res, HttpStatus } from '@nestjs/common';
import { Response } from 'express';
 
@Controller('cats')
export class CatsController {
  @Post()
  create(@Res() res: Response) {
    res.status(HttpStatus.CREATED).send();
  }
 
  @Get()
  findAll(@Res() res: Response) {
     res.status(HttpStatus.OK).json([]);
  }
}

Embora essa abordagem funcione e ofereça mais flexibilidade, dando controle total sobre o objeto de resposta (como a manipulação de cabeçalhos e o acesso a recursos específicos da biblioteca), ela deve ser usada com cautela. Geralmente, esse método é menos claro e apresenta algumas desvantagens. A principal desvantagem é que seu código se torna dependente da plataforma, pois diferentes bibliotecas subjacentes podem ter APIs diferentes para o objeto de resposta. Além disso, pode tornar os testes mais desafiadores, pois você precisará simular o objeto de resposta, entre outras coisas.

Além disso, ao usar essa abordagem, você perde a compatibilidade com os recursos do Nest que dependem do tratamento padrão de resposta, como Interceptors e os decorators @HttpCode() / @Header(). Para resolver isso, você pode habilitar a opção passthrough assim:

// cats.controller.ts
@Get()
findAll(@Res({ passthrough: true }) res: Response) {
  res.status(HttpStatus.OK);
  return [];
}

Com essa abordagem, você pode interagir com o objeto de resposta nativo (por exemplo, definir cookies ou cabeçalhos com base em condições específicas), ao mesmo tempo em que permite que o framework lide com o restante.

Providers

Providers são um conceito central no Nest. Muitas das classes básicas do Nest, como serviços, repositórios, factories e helpers, podem ser tratadas como providers. A ideia principal por trás de um provider é que ele pode ser injetado como uma dependência, permitindo que os objetos formem vários relacionamentos entre si. A responsabilidade de "conectar" esses objetos é amplamente tratada pelo sistema de tempo de execução do Nest.

No capítulo anterior, criamos um simples CatsController. Os controllers devem lidar com as requisições HTTP e delegar tarefas mais complexas aos providers. Providers são classes JavaScript simples declaradas como providers em um módulo NestJS. Para obter mais detalhes, consulte o capítulo "Módulos".

DICA Como o Nest permite que você projete e organize dependências de forma orientada a objetos, recomendamos fortemente que você siga os princípios SOLID.

Serviços

Vamos começar criando um simples CatsService. Este serviço irá lidar com o armazenamento e recuperação de dados e será usado pelo CatsController. Por causa de seu papel no gerenciamento da lógica da aplicação, é um candidato ideal para ser definido como um provider.

// cats.service.ts
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
 
@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];
 
  create(cat: Cat) {
    this.cats.push(cat);
  }
 
  findAll(): Cat[] {
    return this.cats;
  }
}

DICA Para criar um serviço usando o CLI, basta executar o comando $ nest g service cats.

Nosso CatsService é uma classe básica com uma propriedade e dois métodos. A principal adição aqui é o decorator @Injectable(). Este decorator anexa metadados à classe, sinalizando que o CatsService é uma classe que pode ser gerenciada pelo contêiner IoC do Nest.

Além disso, este exemplo utiliza uma interface Cat, que provavelmente se parece com isto:

// interfaces/cat.interface.ts
export interface Cat {
  name: string;
  age: number;
  breed: string;
}

Agora que temos uma classe de serviço para recuperar cats, vamos usá-la dentro do CatsController:

// cats.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
 
@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}
 
  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }
 
  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}

O CatsService é injetado através do construtor da classe. Observe o uso da palavra-chave private. Esta abreviação nos permite tanto declarar quanto inicializar o membro catsService na mesma linha, simplificando o processo.

Injeção de dependência

O Nest é construído em torno do poderoso padrão de design conhecido como Injeção de Dependência. Recomendamos fortemente a leitura de um ótimo artigo sobre este conceito na documentação oficial do Angular.

No Nest, graças aos recursos do TypeScript, o gerenciamento de dependências é simples porque elas são resolvidas com base em seus tipos. No exemplo abaixo, o Nest resolverá o catsService criando e retornando uma instância de CatsService (ou, no caso de um singleton, retornando a instância existente se ela já tiver sido solicitada em outro lugar). Esta dependência é então injetada no construtor do seu controller (ou atribuída à propriedade especificada):

constructor(private catsService: CatsService) {}

Escopos

Os providers normalmente têm uma vida útil ("escopo") que se alinha com o ciclo de vida da aplicação. Quando a aplicação é inicializada, cada dependência deve ser resolvida, o que significa que cada provider é instanciado. Da mesma forma, quando a aplicação é desligada, todos os providers são destruídos. No entanto, também é possível tornar um provider com escopo de requisição, o que significa que sua vida útil está vinculada a uma requisição específica, em vez do ciclo de vida da aplicação. Você pode aprender mais sobre essas técnicas no capítulo Escopos de injeção.

Aprenda da maneira certa!

  • 80+ capítulos
  • 5+ horas de vídeos
  • Certificado oficial
  • Sessões de mergulho profundo

EXPLORE CURSOS OFICIAIS

Providers personalizados

O Nest vem com um contêiner de inversão de controle ("IoC") integrado que gerencia as relações entre os providers. Este recurso é a base da injeção de dependência, mas na verdade é muito mais poderoso do que cobrimos até agora. Existem várias maneiras de definir um provider: você pode usar valores simples, classes e factories síncronas ou assíncronas. Para obter mais exemplos de como definir providers, consulte o capítulo Injeção de Dependência.

Providers opcionais

Ocasionalmente, você pode ter dependências que nem sempre precisam ser resolvidas. Por exemplo, sua classe pode depender de um objeto de configuração, mas, se nenhum for fornecido, valores padrão devem ser usados. Nesses casos, a dependência é considerada opcional, e a ausência do provider de configuração não deve resultar em um erro.

Para marcar um provider como opcional, use o decorator @Optional() na assinatura do construtor.

import { Injectable, Optional, Inject } from '@nestjs/common';
 
@Injectable()
export class HttpService<T> {
  constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {}
}

No exemplo acima, estamos usando um provider personalizado, e é por isso que incluímos o token personalizado HTTP_OPTIONS. Os exemplos anteriores demonstraram a injeção baseada em construtor, onde uma dependência é indicada através de uma classe no construtor. Para obter mais detalhes sobre providers personalizados e como seus tokens associados funcionam, consulte o capítulo Providers personalizados.

Injeção baseada em propriedade

A técnica que usamos até agora é chamada de injeção baseada em construtor, onde os providers são injetados através do método construtor. Em certos casos específicos, a injeção baseada em propriedade pode ser útil. Por exemplo, se sua classe de nível superior depende de um ou mais providers, passá-los até o super() em subclasses pode se tornar complicado. Para evitar isso, você pode usar o decorator @Inject() diretamente no nível da propriedade.

import { Injectable, Inject } from '@nestjs/common';
 
@Injectable()
export class HttpService<T> {
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}

AVISO Se sua classe não estender outra classe, geralmente é melhor usar a injeção baseada em construtor. O construtor especifica claramente quais dependências são necessárias, oferecendo melhor visibilidade e tornando o código mais fácil de entender em comparação com as propriedades da classe anotadas com @Inject.

Registro de provider

Agora que definimos um provider (CatsService) e um consumidor (CatsController), precisamos registrar o serviço no Nest para que ele possa lidar com a injeção. Isso é feito editando o arquivo do módulo (app.module.ts) e adicionando o serviço ao array providers no decorator @Module().

// app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
 
@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class AppModule {}

O Nest agora será capaz de resolver as dependências da classe CatsController.

Neste ponto, nossa estrutura de diretórios deve ser assim:

src
cats
  dto
    create-cat.dto.ts
  interfaces
    cat.interface.ts
  cats.controller.ts
  cats.service.ts
app.module.ts
main.ts

Instanciação manual

Até agora, cobrimos como o Nest lida automaticamente com a maioria dos detalhes da resolução de dependências. No entanto, em alguns casos, você pode precisar sair do sistema de Injeção de Dependência integrado e recuperar ou instanciar manualmente providers. Duas dessas técnicas são brevemente discutidas abaixo.

  • Para recuperar instâncias existentes ou instanciar providers dinamicamente, você pode usar a referência do Módulo.
  • Para obter providers dentro da função bootstrap() (por exemplo, para aplicações autônomas ou para usar um serviço de configuração durante a inicialização), consulte Aplicações autônomas.

Módulos

Um módulo é uma classe que é anotada com o decorator @Module(). Este decorator fornece metadados que o Nest usa para organizar e gerenciar a estrutura da aplicação de forma eficiente.

Cada aplicação Nest tem pelo menos um módulo, o módulo raiz, que serve como ponto de partida para o Nest construir o gráfico da aplicação. Este gráfico é uma estrutura interna que o Nest usa para resolver relacionamentos e dependências entre módulos e providers. Embora aplicações pequenas possam ter apenas um módulo raiz, geralmente este não é o caso. Os módulos são altamente recomendados como uma forma eficaz de organizar seus componentes. Para a maioria das aplicações, você provavelmente terá vários módulos, cada um encapsulando um conjunto de recursos intimamente relacionados.

O decorator @Module() aceita um único objeto com propriedades que descrevem o módulo:

  • providers: os providers que serão instanciados pelo injetor Nest e que podem ser compartilhados pelo menos em todo este módulo
  • controllers: o conjunto de controllers definidos neste módulo que precisam ser instanciados
  • imports: a lista de módulos importados que exportam os providers que são necessários neste módulo
  • exports: o subconjunto de providers que são fornecidos por este módulo e devem estar disponíveis em outros módulos que importam este módulo. Você pode usar o próprio provider ou apenas seu token (valor fornecido)

O módulo encapsula providers por padrão, o que significa que você só pode injetar providers que fazem parte do módulo atual ou são explicitamente exportados de outros módulos importados. Os providers exportados de um módulo servem essencialmente como a interface pública ou API do módulo.

Módulos de recursos

Em nosso exemplo, o CatsController e o CatsService estão intimamente relacionados e servem o mesmo domínio da aplicação. Faz sentido agrupá-los em um módulo de recurso. Um módulo de recurso organiza o código que é relevante para um recurso específico, ajudando a manter limites claros e uma melhor organização. Isso é particularmente importante à medida que a aplicação ou equipe cresce e se alinha com os princípios SOLID.

Em seguida, criaremos o CatsModule para demonstrar como agrupar o controller e o serviço.

// cats/cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
 
@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

DICA Para criar um módulo usando o CLI, basta executar o comando $ nest g module cats.

Acima, definimos o CatsModule no arquivo cats.module.ts e movemos tudo relacionado a este módulo para o diretório cats. A última coisa que precisamos fazer é importar este módulo no módulo raiz (o AppModule, definido no arquivo app.module.ts).

// app.module.ts
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
 
@Module({
  imports: [CatsModule],
})
export class AppModule {}

Aqui está como nossa estrutura de diretórios se parece agora:

src
  cats
    dto
      create-cat.dto.ts
    interfaces
      cat.interface.ts
    cats.controller.ts
    cats.module.ts
    cats.service.ts
app.module.ts
main.ts

Módulos compartilhados

No Nest, os módulos são singletons por padrão e, portanto, você pode compartilhar a mesma instância de qualquer provider entre vários módulos sem esforço.

Cada módulo é automaticamente um módulo compartilhado. Uma vez criado, ele pode ser reutilizado por qualquer módulo. Vamos imaginar que queremos compartilhar uma instância do CatsService entre vários outros módulos. Para fazer isso, primeiro precisamos exportar o provider CatsService adicionando-o ao array exports do módulo, conforme mostrado abaixo:

// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
 
@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService]
})
export class CatsModule {}

Agora, qualquer módulo que importar o CatsModule tem acesso ao CatsService e compartilhará a mesma instância com todos os outros módulos que o importarem também.

Se registrarmos diretamente o CatsService em todos os módulos que o exigem, de fato funcionaria, mas resultaria em cada módulo obtendo sua própria instância separada do CatsService. Isso pode levar ao aumento do uso de memória, pois várias instâncias do mesmo serviço são criadas, e também pode causar um comportamento inesperado, como inconsistência de estado se o serviço mantiver algum estado interno.

Ao encapsular o CatsService dentro de um módulo, como o CatsModule, e exportá-lo, garantimos que a mesma instância do CatsService seja reutilizada em todos os módulos que importam o CatsModule. Isso não apenas reduz o consumo de memória, mas também leva a um comportamento mais previsível, pois todos os módulos compartilham a mesma instância, tornando mais fácil gerenciar estados ou recursos compartilhados. Este é um dos principais benefícios da modularidade e injeção de dependência em frameworks como NestJS - permitindo que os serviços sejam compartilhados com eficiência em toda a aplicação.

Explore seu gráfico com NestJS Devtools

  • Visualizador de gráfico
  • Navegador de rotas
  • Playground interativo
  • Integração CI/CD

INSCREVA-SE

Reexportação de módulos

Como visto acima, os módulos podem exportar seus providers internos. Além disso, eles podem reexportar módulos que importam. No exemplo abaixo, o CommonModule é importado e exportado do CoreModule, tornando-o disponível para outros módulos que importam este.

@Module({
  imports: [CommonModule],
  exports: [CommonModule],
})
export class CoreModule {}

Injeção de dependência

Uma classe de módulo pode injetar providers também (por exemplo, para fins de configuração):

// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
 
@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {
  constructor(private catsService: CatsService) {}
}

No entanto, as próprias classes de módulo não podem ser injetadas como providers devido à dependência circular.

Módulos globais

Se você tiver que importar o mesmo conjunto de módulos em todos os lugares, pode ser tedioso. Ao contrário do Nest, os providers do Angular são registrados no escopo global. Uma vez definidos, eles estão disponíveis em todos os lugares. O Nest, no entanto, encapsula providers dentro do escopo do módulo. Você não pode usar os providers de um módulo em outro lugar sem primeiro importar o módulo encapsulador.

Quando você deseja fornecer um conjunto de providers que devem estar disponíveis em todos os lugares prontos para uso (por exemplo, helpers, conexões de banco de dados, etc.), torne o módulo global com o decorator @Global().

import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
 
@Global()
@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}

O decorator @Global() torna o módulo com escopo global. Os módulos globais devem ser registrados apenas uma vez, geralmente pelo módulo raiz ou central. No exemplo acima, o provider CatsService será onipresente, e os módulos que desejam injetar o serviço não precisarão importar o CatsModule em seu array imports.

DICA Tornar tudo global não é recomendado como prática de design. Embora os módulos globais possam ajudar a reduzir o boilerplate, geralmente é melhor usar o array imports para disponibilizar a API de um módulo para outros módulos de maneira controlada e clara. Essa abordagem fornece melhor estrutura e capacidade de manutenção, garantindo que apenas as partes necessárias do módulo sejam compartilhadas com outros, evitando o acoplamento desnecessário entre partes não relacionadas da aplicação.

Módulos dinâmicos

Os módulos dinâmicos no Nest permitem que você crie módulos que podem ser configurados em tempo de execução. Isso é especialmente útil quando você precisa fornecer módulos flexíveis e personalizáveis ​​onde os providers podem ser criados com base em determinadas opções ou configurações. Aqui está uma breve visão geral de como os módulos dinâmicos funcionam.

// database.module.ts
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';
 
@Module({
  providers: [Connection],
  exports: [Connection],
})
export class DatabaseModule {
  static forRoot(entities = [], options?): DynamicModule {
    const providers = createDatabaseProviders(options, entities);
    return {
      module: DatabaseModule,
      providers: providers,
      exports: providers,
    };
  }
}

DICA O método forRoot() pode retornar um módulo dinâmico de forma síncrona ou assíncrona (ou seja, via uma Promise).

Este módulo define o provider Connection por padrão (nos metadados do decorator @Module()), mas, adicionalmente - dependendo das entidades e objetos de opções passados ​​para o método forRoot() - expõe uma coleção de providers, por exemplo, repositórios. Observe que as propriedades retornadas pelo módulo dinâmico estendem (em vez de substituir) os metadados do módulo base definidos no decorator @Module(). É assim que o provider Connection declarado estaticamente e os providers de repositório gerados dinamicamente são exportados do módulo.

Se você deseja registrar um módulo dinâmico no escopo global, defina a propriedade global como true.

{
  global: true,
  module: DatabaseModule,
  providers: providers,
  exports: providers,
}

AVISO Como mencionado acima, tornar tudo global não é uma boa decisão de design.

O DatabaseModule pode ser importado e configurado da seguinte maneira:

// app.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';
 
@Module({
  imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {}

Se você quiser reexportar um módulo dinâmico, pode omitir a chamada do método forRoot() no array exports:

// app.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';
 
@Module({
  imports: [DatabaseModule.forRoot([User])],
  exports: [DatabaseModule],
})
export class AppModule {}

O capítulo Módulos dinâmicos abrange este tópico com mais detalhes e inclui um exemplo de trabalho.

DICA Aprenda como construir módulos dinâmicos altamente personalizáveis ​​com o uso de ConfigurableModuleBuilder aqui neste capítulo.

Middleware

Middleware é uma função que é chamada antes do handler da rota. Funções de middleware têm acesso aos objetos de requisição e resposta e à função de middleware next() no ciclo de requisição-resposta da aplicação. A função de middleware next é comumente denotada por uma variável chamada next.

O middleware do Nest é, por padrão, equivalente ao middleware do express. A seguinte descrição da documentação oficial do express descreve as capacidades do middleware:

As funções de middleware podem executar as seguintes tarefas:

  • executar qualquer código.
  • fazer alterações nos objetos de requisição e resposta.
  • encerrar o ciclo de requisição-resposta.
  • chamar a próxima função de middleware na pilha.

Se a função de middleware atual não encerrar o ciclo de requisição-resposta, ela deve chamar next() para passar o controle para a próxima função de middleware. Caso contrário, a requisição será deixada pendurada.

Você implementa o middleware Nest personalizado em uma função ou em uma classe com um decorator @Injectable(). A classe deve implementar a interface NestMiddleware, enquanto a função não tem nenhum requisito especial. Vamos começar implementando um recurso de middleware simples usando o método de classe.

AVISO Express e fastify lidam com middleware de forma diferente e fornecem diferentes assinaturas de método, leia mais aqui.

// logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
 
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}

Injeção de dependência

O middleware do Nest suporta totalmente a injeção de dependência. Assim como com providers e controllers, eles podem injetar dependências que estão disponíveis dentro do mesmo módulo. Como de costume, isso é feito através do construtor.

Aplicando middleware

Não há lugar para middleware no decorator @Module(). Em vez disso, nós os configuramos usando o método configure() da classe do módulo. Módulos que incluem middleware devem implementar a interface NestModule. Vamos configurar o LoggerMiddleware no nível do AppModule.

// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
 
@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
  }
}

No exemplo acima, configuramos o LoggerMiddleware para os handlers de rota /cats que foram definidos anteriormente dentro do CatsController. Também podemos restringir ainda mais o middleware a um método de requisição específico, passando um objeto contendo o caminho da rota e o método de requisição para o método forRoutes() ao configurar o middleware. No exemplo abaixo, observe que importamos o enum RequestMethod para referenciar o tipo de método de requisição desejado.

// app.module.ts
import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
 
@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}

DICA O método configure() pode ser tornado assíncrono usando async/await (por exemplo, você pode aguardar a conclusão de uma operação assíncrona dentro do corpo do método configure()).

AVISO Ao usar o adaptador express, o aplicativo NestJS registrará json e urlencoded do pacote body-parser por padrão. Isso significa que, se você quiser personalizar esse middleware via MiddlewareConsumer, precisará desativar o middleware global definindo a flag bodyParser como false ao criar a aplicação com NestFactory.create().

Wildcards de rota

Rotas baseadas em padrão também são suportadas no middleware NestJS. Por exemplo, o curinga nomeado (*splat) pode ser usado como um curinga para corresponder a qualquer combinação de caracteres em uma rota. No exemplo a seguir, o middleware será executado para qualquer rota que comece com abcd/, independentemente do número de caracteres que se seguem.

forRoutes({
  path: 'abcd/*splat',
  method: RequestMethod.ALL,
});

DICA splat é simplesmente o nome do parâmetro curinga e não tem nenhum significado especial. Você pode nomeá-lo como quiser, por exemplo, *wildcard.

O caminho da rota 'abcd/*' corresponderá a abcd/1, abcd/123, abcd/abc e assim por diante. O hífen (-) e o ponto (.) são interpretados literalmente por caminhos baseados em string. No entanto, abcd/ sem caracteres adicionais não corresponderá à rota. Para isso, você precisa envolver o curinga entre chaves para torná-lo opcional:

forRoutes({
  path: 'abcd/{*splat}',
  method: RequestMethod.ALL,
});

Middleware consumer

O MiddlewareConsumer é uma classe auxiliar. Ele fornece vários métodos integrados para gerenciar o middleware. Todos eles podem ser simplesmente encadeados no estilo fluent. O método forRoutes() pode aceitar uma única string, várias strings, um objeto RouteInfo, uma classe controller e até mesmo várias classes controller. Na maioria dos casos, você provavelmente passará apenas uma lista de controllers separados por vírgulas. Abaixo está um exemplo com um único controller:

// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller';
 
@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(CatsController);
  }
}

DICA O método apply() pode aceitar um único middleware ou vários argumentos para especificar vários middlewares.

Excluindo rotas

Às vezes, podemos querer excluir determinadas rotas da aplicação do middleware. Isso pode ser facilmente alcançado usando o método exclude(). O método exclude() aceita uma única string, várias strings ou um objeto RouteInfo para identificar as rotas a serem excluídas.

Aqui está um exemplo de como usá-lo:

consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'cats', method: RequestMethod.GET },
    { path: 'cats', method: RequestMethod.POST },
    'cats/{*splat}',
  )
  .forRoutes(CatsController);

DICA O método exclude() suporta parâmetros curinga usando o pacote path-to-regexp.

Com o exemplo acima, o LoggerMiddleware será vinculado a todas as rotas definidas dentro do CatsController, exceto as três passadas para o método exclude().

Essa abordagem oferece flexibilidade na aplicação ou exclusão de middleware com base em rotas ou padrões de rota específicos.

Middleware funcional

A classe LoggerMiddleware que temos usado é bastante simples. Não tem membros, nenhum método adicional e nenhuma dependência. Por que não podemos simplesmente defini-lo em uma função simples em vez de uma classe? Na verdade, podemos. Esse tipo de middleware é chamado de middleware funcional. Vamos transformar o middleware de logger baseado em classe em middleware funcional para ilustrar a diferença:

// logger.middleware.ts
import { Request, Response, NextFunction } from 'express';
 
export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`);
  next();
};

E usá-lo dentro do AppModule:

// app.module.ts
consumer
  .apply(logger)
  .forRoutes(CatsController);

DICA Considere usar a alternativa de middleware funcional mais simples sempre que seu middleware não precisar de nenhuma dependência.

Múltiplos middleware

Como mencionado acima, para vincular vários middleware que são executados sequencialmente, simplesmente forneça uma lista separada por vírgulas dentro do método apply():

consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);

Middleware global

Se quisermos vincular o middleware a todas as rotas registradas de uma vez, podemos usar o método use() que é fornecido pela instância INestApplication:

// main.ts
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(process.env.PORT ?? 3000);

DICA Acessar o contêiner DI em um middleware global não é possível. Você pode usar um middleware funcional em vez disso ao usar app.use(). Alternativamente, você pode usar um middleware de classe e consumi-lo com .forRoutes('*') dentro do AppModule (ou qualquer outro módulo).