Blog Formação DEV

Introdução ao NestJS

Eleve a qualidade dos seus projetos backend com o NestJS, um framework simples, eficiente, confiável e escalável.
Introdução ao NestJS
Texto de: Geraldo Daros

Introdução

O NestJS é um poderoso framework Node.js desenvolvido por um polonês chamado Kamil Mysliwiec, que vem ganhando popularidade no ecossistema Node.JS. Essa ferramenta utiliza TypeScript e visa construir aplicações backend que sejam eficientes, confiáveis e escaláveis de forma simples e eficaz. O NestJS possui uma arquitetura baseada no Angular e utilizada Express por debaixo dos panos, podendo também ser configurada para utilizar o Fastify.

Benefícios do NestJS

O NestJS trás diversos benefícios para a sua aplicação, e alguns deles podem ser vistos na lista abaixo:

  • Simplicidade e Facilidade de Uso: O NestJS é intuitivo e foi projetado para ser simples de aprender e utilizar. Sua arquitetura clara e bem definida facilita o desenvolvimento, mesmo para iniciantes no mundo do Node.js.
  • Padronização de Projeto e Organização: O NestJS promove uma robusta padronização de projeto, ajudando a manter o código organizado e legível. Sua estrutura modular e o uso de decoradores mantêm a consistência em todo o projeto.
  • Ótima Documentação: A documentação do NestJS é extensa e bem elaborada, oferecendo muitos exemplos práticos de código. Além disso, fornece guias detalhados para integração com diversas ferramentas, facilitando a implementação de soluções completas e robustas.
  • Comunidade Ativa: O NestJS possui uma comunidade ativa que contribui constantemente com novos pacotes, extensões e soluções para problemas comuns. Esta comunidade ativa garante muitos recursos e suporte disponíveis para os desenvolvedores.
  • Arquitetura Modular: O NestJS trabalha com módulos ou componentes, permitindo que cada módulo seja responsável por uma funcionalidade específica. Isso torna os projetos altamente organizados, extensíveis e favorece a reutilização de código, facilitando a manutenção e a escalabilidade do sistema.
  • Suporte Total ao TypeScript: O NestJS oferece suporte completo ao TypeScript, aproveitando todos os benefícios deste superset do JavaScript, como tipagem estática, interfaces e classes. Isso resulta em código mais robusto e menos propenso a erros.
  • Escalabilidade, Versatilidade e Progressão: O NestJS é projetado para ser escalável, capaz de lidar com o crescimento de aplicações tanto em tamanho quanto em complexidade. Sua versatilidade permite que ele seja usado para uma ampla gama de aplicações, desde pequenas APIs até grandes sistemas corporativos. Além disso, sua arquitetura progressiva permite que desenvolvedores adotem novos recursos e melhorias gradualmente, sem grandes refatorações.

Instalação NestJS

Para começar a usar o NestJS, é necessário instalar a Nest CLI, que facilita o trabalho com o framework através da linha de comando. Para fazer essa instalação, a documentação recomenda ter uma versão do Node.js igual ou superior à 16. Uma vez que você tenha verificado a sua versão do Node, pode instalar o NestCLI globalmente, com o seguinte comando no terminal:

$ npm install -g @nestjs/cli

Para verificar se a CLI foi instalada com sucesso, use o comando a seguir do seu terminal:

$ nest -v

Ao rodar esse comando, você deverá receber a versão mais recente do NestJS.

Criando Projeto NestJS

Chegou a hora, vamos botar a mão na massa e criar um projeto NestJS! Para criar um projeto NestJS, utilizamos a CLI instalada anteriormente com o seguinte comando:

$ nest new nestjs-cod3r

Após rodar o comando, você verá a seguinte pergunta:

? Which package manager would you ❤️  to use? (Use arrow keys)

npm
yarn
pnpm

Neste momento, selecione o gerenciador de pacotes que deseja usar no projeto. Continuaremos a partir deste ponto usando o npm. A própria CLI fornecerá as instruções para rodar o projeto. No caso do comando que executamos, o nome do projeto é nestjs-cod3r. Portanto, ao final do log, aparecerão as seguintes instruções:

$ cd nestjs-cod3r
$ npm run start

Isso indica que para rodar o projeto, basta navegar para a pasta criada do projeto e executar npm run start.

Se você seguir essa instrução, deverá ver o seguinte no console:

nestjs-cod3r@0.0.1 start
nest start

[Nest] 8308      LOG [NestFactory] Starting Nest application...
[Nest] 8308      LOG [InstanceLoader] AppModule dependencies initialized +7ms
[Nest] 8308      LOG [RoutesResolver] AppController {/}: +3ms
[Nest] 8308      LOG [RouterExplorer] Mapped {/, GET} route +1ms
[Nest] 8308      LOG [NestApplication] Nest application successfully started +2ms

Isso já mostra as rotas disponíveis, especificamente o AppController mapeado para o GET usando o endpoint básico da aplicação /. Por padrão, o NestJS roda na porta 3000, então para acessar a rota disponibilizada por padrão, podemos usar: http://localhost:3000/ e essa rota deve retornar "Hello World!".

Conhecendo o Projeto

Agora vamos analisar a estrutura básica de um projeto NestJS.

São muitos arquivos, mas pode manter a calma que veremos o que cada arquivo faz e qual sua responsabilidade a seguir:

Pastas:

  • dist: A pasta dist é onde o código JavaScript transpilado a partir do código TypeScript da aplicação é armazenado.
  • node_modules: A pasta node_modules contém todas as dependências do projeto, ou seja, todos os pacotes de terceiros que a aplicação precisa para funcionar.
  • src: A pasta src é onde você coloca o código-fonte principal da aplicação. Este é o local onde você define a estrutura, lógica e funcionalidade da sua aplicação.
  • test: A pasta test é onde você coloca os testes da sua aplicação. Em um projeto NestJS, testes são uma parte importante para garantir que o código funcione conforme o esperado.

Arquivos:

Agora vamos ver as funções e responsabilidades de cada arquivo.

  • app.controller.spec.ts:
    • Contém testes unitários para app.controller.ts.
  • app.controller.ts:
    • Lida com as solicitações HTTP. Decorado com @Controller, geralmente contém métodos decorados com @Get, @Post, para receber requisições GET e POST, respectivamente. É esse arquivo que permite que algo seja retornado, ao acessarmos http://localhost:3000/. Por padrão, ele chama a camada de serviço que retorna uma string "Hello World!".
  • app.service.ts:
    • Contém a lógica de negócios da aplicação e pode ser injetado em controladores via injeção de dependência.
  • app.module.ts:
    • O módulo principal da aplicação. Decorado com @Module, é usado para organizar e configurar outros módulos, controladores e provedores (services, repositories, etc.) da aplicação.
  • main.ts:
    • Arquivo de entrada da aplicação. Cria uma instância da aplicação NestJS usando o NestFactory e inicia o servidor na porta especificada.
  • .gitignore:
  • nest-cli.json:
    • Arquivo de configuração para o Nest CLI. Define configurações relacionadas à geração de código e outras opções específicas do Nest CLI.
  • package.json:
    • Contém informações sobre o projeto, dependências, scripts de execução, entre outras configurações.
  • tsconfig.json:
    • Arquivo de configuração do TypeScript. Define as opções de compilação.
  • tsconfig.build.json:
    • Arquivo de configuração do TypeScript específico para o modo de construção (build). Define opções de compilação para o código que será executado em produção.
  • .prettierrc:
    • Arquivo de configuração para o Prettier, uma ferramenta de formatação de código. Define regras de formatação para manter o código consistente.
    • Para formatar o código, use o comando npm run format
  • .eslintrc.js:
    • Arquivo de configuração para o ESLint, uma ferramenta de linting para JavaScript e TypeScript. Define regras para manter um código limpo e consistente.
    • Para rodar o lint, use o comando npm run lint

Arquitetura

A arquitetura do NestJS é baseada em módulos. A aplicação é composta por diversos módulos, formando um conjunto dentro do módulo principal, o app.module. Um módulo é uma unidade de organização que agrupa vários componentes relacionados (como controladores e provedores). Os módulos ajudam a estruturar a aplicação em partes coesas e reutilizáveis, promovendo uma arquitetura modular.

Os módulos possuem uma estrutura básica, geralmente incluindo arquivos de configuração (module.ts), controladores (controller.ts), serviços (service.ts) e arquivos de testes. No entanto, não há obrigatoriedade de incluir todos esses arquivos em um módulo, mas ele só será considerado um módulo se tiver o arquivo de configuração (module.ts).

O padrão de nomenclatura é simples e segue a estrutura: [nome do módulo].[tipo do arquivo].extensão. Por exemplo:

  • pessoa.controller.ts
  • pessoa.service.ts
  • pessoa.module.ts

Neste caso, fica explícito que se trata da estrutura básica de um módulo chamado "pessoa". Embora esses nomes sejam recomendados para manter a clareza e a consistência, eles não são estritamente necessários para o funcionamento, desde que sejam referenciados corretamente onde for necessário.

Módulos

Para compreender como o NestJS funciona e como desenvolver nele, é essencial conhecer o funcionamento dos módulos, como se comunicam e como configurá-los. Um módulo no NestJS é simplesmente uma classe anotada com @Module, que pode receber alguns parâmetros de configuração: imports, controllers, providers e exports.

Cada aplicação possui pelo menos um módulo, o módulo raiz. O módulo raiz é o ponto de partida que o Nest usa para construir a aplicação, estabelecendo a estrutura interna de dados que o Nest utiliza para resolver relacionamentos e dependências de módulos e provedores.

É possível ter apenas um módulo na aplicação inteira? Sim, mas essa não é uma escolha recomendada. A documentação do NestJS enfatiza que os módulos são uma forma eficaz de organizar seus componentes. Portanto, a maioria das aplicações utilizará múltiplos módulos, cada um encapsulando um conjunto de funcionalidades relacionadas.

Parâmetros do @Module:

  • Providers: Provedores que serão instanciados pelo injetor de dependências do Nest e que podem ser compartilhados, pelo menos, dentro deste módulo.
  • Controllers: Conjunto de controladores definidos neste módulo que devem ser instanciados.
  • Imports: Lista de módulos importados que exportam os provedores exigidos neste módulo.
  • Exports: Exporta provedores, permitindo que um provedor seja utilizado em outro módulo sem importar diretamente o provedor, mas apenas o módulo que o expõe.

Podemos gerar novos módulos com o comando:

nest g module [nome]

Módulo padrão criado na criação de um projeto NestJS:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

CRUD REST API

O NestJS nos permite criar manualmente arquivo por arquivo ou gerar arquivos específicos usando a CLI do Nest. No entanto, há uma forma ainda mais interessante: gerar um CRUD completo. Como fazer isso?

Usando a CLI do Nest, basta executar o seguinte comando:

nest generate resource pessoa

Esse comando gera um "recurso" chamado "pessoa". Após rodar esse comando, a CLI pergunta:

? What transport layer do you use? (Use arrow keys)
❯ REST API
GraphQL (code first)
GraphQL (schema first)
Microservice (non-HTTP)
WebSockets

Você deve escolher a "camada de transporte" que deseja que o Nest use para criar o recurso. Neste caso, usaremos REST API, que provavelmente é a mais comum.

Selecionando a opção REST API, uma nova pergunta surge:

? Would you like to generate CRUD entry points? (Y/n) 

Basicamente, "CRUD entry points" se refere à entidade "pessoa" e aos DTOs de criação e atualização.

Ao confirmar, o console exibe o seguinte ao gerar o recurso "pessoa":

CREATE src/pessoa/pessoa.controller.ts (959 bytes)
CREATE src/pessoa/pessoa.controller.spec.ts (596 bytes)
CREATE src/pessoa/pessoa.module.ts (264 bytes)
CREATE src/pessoa/pessoa.service.ts (661 bytes)
CREATE src/pessoa/pessoa.service.spec.ts (478 bytes)
CREATE src/pessoa/dto/create-pessoa.dto.ts (33 bytes)
CREATE src/pessoa/dto/update-pessoa.dto.ts (181 bytes)
CREATE src/pessoa/entities/pessoa.entity.ts (24 bytes)
UPDATE src/app.module.ts (326 bytes)

Ele criou diversos arquivos e também atualizou o app.module.ts. Por quê? Porque é necessário declarar ao módulo raiz (app.module.ts) que estamos criando um novo módulo na aplicação. A mudança é simplesmente importar o módulo "pessoa" dentro do app.module.ts e adicioná-lo ao array de imports, assim:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PessoaModule } from './pessoa/pessoa.module';

@Module({
  imports: [PessoaModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Comparado à versão inicial do projeto, o AppModule não mudou muito. Apenas declaramos PessoaModule no array de imports e o importamos.

A CLI do Nest fez tudo para nós. Agora, ao rodar a aplicação, podemos ver no log de execução algumas informações diferentes:

[Nest] 14372       LOG [NestFactory] Starting Nest application...
[Nest] 14372       LOG [InstanceLoader] AppModule dependencies initialized +8ms
[Nest] 14372       LOG [InstanceLoader] PessoaModule dependencies initialized +1ms
[Nest] 14372       LOG [RoutesResolver] AppController {/}: +3ms
[Nest] 14372       LOG [RouterExplorer] Mapped {/, GET} route +2ms
[Nest] 14372       LOG [RoutesResolver] PessoaController {/pessoa}: +0ms
[Nest] 14372       LOG [RouterExplorer] Mapped {/pessoa, POST} route +0ms
[Nest] 14372       LOG [RouterExplorer] Mapped {/pessoa, GET} route +1ms
[Nest] 14372       LOG [RouterExplorer] Mapped {/pessoa/:id, GET} route +0ms
[Nest] 14372       LOG [RouterExplorer] Mapped {/pessoa/:id, PATCH} route +0ms
[Nest] 14372       LOG [RouterExplorer] Mapped {/pessoa/:id, DELETE} route +1ms
[Nest] 14372       LOG [NestApplication] Nest application successfully started +1ms

Note que temos informações das rotas para acessar os recursos de "pessoa" no log, como: PessoaController acessível via /pessoa, e outros métodos disponíveis. Podemos acessar todos eles e os fluxos estarão funcionando perfeitamente, embora apenas retornando uma string, já que a implementação completa ainda não foi feita.

Recebendo Requisições

Um dos pontos mais importantes em uma REST API é o recebimento das requisições, sendo necessário configurar nossos endpoints e parâmetros recebidos. Nossas requisições chegam diretamente nas controllers, podendo haver alguns filtros e verificações antes de chegar nas controllers, geralmente usados para autenticação e autorização, mas isso não será abordado neste artigo.

Vamos entender como um método pode ser mapeado para responder a uma determinada requisição.

Controller gerada anteriormente pelo Nest:

import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { PessoaService } from './pessoa.service';
import { CreatePessoaDto } from './dto/create-pessoa.dto';
import { UpdatePessoaDto } from './dto/update-pessoa.dto';

@Controller('pessoa')
export class PessoaController {
  constructor(private readonly pessoaService: PessoaService) {}

  @Post()
  create(@Body() createPessoaDto: CreatePessoaDto) {
    return this.pessoaService.create(createPessoaDto);
  }

  @Get()
  findAll() {
    return this.pessoaService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.pessoaService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updatePessoaDto: UpdatePessoaDto) {
    return this.pessoaService.update(+id, updatePessoaDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.pessoaService.remove(+id);
  }
}

Começamos com uma classe exportada chamada PessoaController, anotada com @Controller() para ser reconhecida como um controller Nest. O parâmetro passado para esse decorator define a URL base desse recurso, neste caso, pessoa.

O construtor da classe está injetando uma dependência de PessoaService, e o Nest faz essa injeção automaticamente.

Para associarmos os métodos a requisições HTTP específicas, usamos decorators como @Get, @Post, @Patch, @Delete. Esses decorators podem ter parâmetros para configurar rotas específicas, como @Post('create').

Para receber parâmetros via URL, adicionamos um parâmetro no decorator do método HTTP e indicamos que é um parâmetro, não apenas uma string, adicionando dois pontos (":") antes da string. Referenciamos o nome do parâmetro como argumento do método, por exemplo:

@Get(':id')
  findOne(@Param('id') id: string) {
    return this.pessoaService.findOne(+id);
  }

Configuramos um parâmetro na URL em um método HTTP @Get, e seu nome será id (:id). Para capturar esse valor no método, anotamos o argumento do método com o decorator @Param('nomeDoParametro').

Quando precisamos receber um corpo na requisição, configuramos esse corpo com @Body(), por exemplo:

@Post('create')
  create(@Body() createPessoaDto: CreatePessoaDto) {
    return this.pessoaService.create(createPessoaDto);
  }

Este método será chamado quando houver uma requisição do tipo Post em /pessoa/create. Ele espera receber no corpo da requisição um CreatePessoaDto. Basta adicionar @Body() antes do argumento do método e o Nest entenderá que deve esperar esse corpo na requisição.

Services

Você deve ter notado o uso constante do PessoaService. Mas por quê? Geralmente, usamos o conceito de Service para encapsular regras de negócio, e é aí que os serviços entram. A camada de controller serve apenas para receber a requisição e enviar os dados para o service. Se houver algum tratamento nesses dados, é responsabilidade do service realizar esse tratamento. Basicamente, um service é uma classe injetável via injeção de dependência do Nest, usada para validar e tratar dados. Se você tivesse uma conexão com um banco de dados, chamaria essa conexão após passar pelo service. O fluxo seria basicamente: controller ⇒ service ⇒ banco de dados.

Os services devem conter a anotação @Injectable() para funcionarem perfeitamente. O Nest consegue inferir um injetável sem a anotação, mas isso não é recomendado. Não faça isso.

Podemos também gerar services com o comando:

nest g service [nome]

Bonus: Entidade e Dto

O que é uma Entidade?

No contexto do NestJS, especialmente quando se utiliza o TypeORM (um ORM para TypeScript e JavaScript), uma entidade é uma classe que mapeia uma tabela do banco de dados. Cada instância dessa classe representa uma linha da tabela. Existem muitas definições sobre o que é uma entidade em diferentes contextos, mas neste contexto específico, foque na explicação acima.

O que é um DTO?

Um DTO (Data Transfer Object) é um objeto usado para transferir dados entre processos. No NestJS, os DTOs são usados principalmente para definir a estrutura dos dados que serão recebidos nas requisições HTTP e os dados que serão enviados nas respostas HTTP.

Conclusão

O NestJS oferece uma abordagem estruturada e eficiente para o desenvolvimento de aplicativos back-end em Node.js, com sua integração com TypeScript e uma arquitetura modular robusta. Sua documentação abrangente e comunidade ativa tornam a aprendizagem e o suporte acessíveis. Com recursos avançados e uma ampla gama de extensões, o NestJS é uma escolha sólida para projetos de qualquer tamanho, promovendo desenvolvimento mais rápido e seguro.

Sobre o autor
Cod3r

Cod3r

Com mais de 400 mil alunos, a Cod3r é uma das principais escolas de tecnologia do País. Um de seus produtos mais importantes é a Formação DEV, com objetivo de preparar os profissionais para o mercado.

Ótimo! Inscreveu-se com sucesso.

Bem-vindo de volta! Registou-se com sucesso.

Assinou com sucesso o Blog Formação DEV .

Sucesso! Verifique o seu e-mail para obter o link mágico para se inscrever.

As suas informações de pagamento foram atualizadas.

Seu pagamento não foi atualizado.