Olá Pessoal! Nesta edição do PET Redação, vamos entender mais sobre um framework que vem fazendo muito sucesso no desenvolvimento server-side em Node.js, o NestJS.
Introdução
O Nest é um framework Node.js para implementar aplicações backend eficientes, confiáveis e escaláveis utilizando Typescript (padrão) ou Javascript. Construído em Typescript, ele utiliza diversos princípios, como Programação Orientada a Objetos, Programação Funcional e Programação Funcional Reativa.
Ao optar pelo Nest, você tem a opção de escolher entre dois dos mais famosos frameworks para servidores HTTP: o Express (padrão) e o Fastify. Essa escolha não apenas proporciona uma camada de abstração em algumas partes do desenvolvimento, mas também garante liberdade para os desenvolvedores adaptarem a estrutura às necessidades específicas de seus projetos.
O maior problema que o NestJS propõe-se a resolver é a arquitetura em aplicações server-side Javascript, sendo bastante similar e, até mesmo, inspirado no Angular, um framework para client-side. A arquitetura do NestJS é, principalmente, dividida em: Controllers, Providers (Services, Repositories, Helpers, etc) e Modules. Há outras divisões também importantes, mas vamos entrar em detalhes em cada uma delas mais tarde.
No tópico a seguir veremos como instalar e criar o primeiro projeto com o NestJS e, logo mais, entenderemos mais sobre a arquitetura e os principais fundamentos que o NestJS utiliza, Aproveite!
Instalação e primeiro projeto
Para ter acesso a todos os recursos que o NestJS oferece é necessário instalar a CLI deles. Isso pode ser feito a partir do gerenciador de pacotes do Node.js, o npm, a partir do seguinte comando:
npm install -g @nestjs/cli
Esse comando instala globalmente (por isso o -g) o Nest CLI na sua máquina. Agora, vamos entrar em detalhes sobre os comandos do Nest, começando pela criação de um novo projeto com NestJS:
nest new nome_novo_projeto
nest n nome_novo_projeto
Após rodar o comando, vai ser perguntado qual a preferência do gerenciador de pacotes, possuindo 3 opções: npm, yarn e pnpm. Qualquer uma das opções vai satisfazer os nossos requisitos, na dúvida selecione o npm mesmo (Caso opte pelo yarn ou pnpm, será necessário instalá-los globalmente). Abrindo a pasta do projeto criado você vai ver algo assim:
O Nest CLI possui diversos outros comandos úteis para agilizar o processo de desenvolvimento e auxiliar na produtividade, porém vamos entender mais sobre alguns deles depois, pois explicar a arquitetura e os fundamentos do NestJS é o tema principal dessa redação
Arquitetura
Os controllers são responsáveis pelo processamento das requisições (requests) e retorno de uma resposta (response) para o client. Normalmente, um possui mais de uma rota, por exemplo, temos um controller que gerencia as requisições relacionadas ao usuário, dessa forma podemos ter uma rota que retorna todos os usuários, outra que cria um usuário novo, outra para deletar um usuário, etc. Até aí nada de novo das aplicações server-side comuns, porém o NestJS utiliza classes e decorators para criar os controllers.
Os Decorators são uma funcionalidade poderosa e útil da linguagem TypeScript utilizada para anotar classes, métodos, propriedades ou parâmetros de métodos. Eles são uma parte fundamental da arquitetura do Nest.js e são amplamente utilizados para definir comportamentos adicionais ou metadados para várias partes de uma aplicação. Existem vários decorators integrados fornecidos pelo Nest.js, e você também pode criar seus próprios decorators personalizados quando necessário.
Para definir um controller, utiliza-se o decorator @Controller, que pode receber um nome como parâmetro. Além disso, as rotas desse controller também podem ter diversos decorators, como @Get, @Post, @Patch, etc. Isso é necessário para definir o que essa rota deve fazer Um exemplo de um controller de usuários pode ser visto abaixo:
Os providers são uma parte muito importante para a arquitetura do Nest, mas diferente dos controllers, os providers podem ser vários tipos de classes, os mais comuns são services, repositories, helpers, etc. Como visto anteriormente, os controllers são responsáveis por lidar com requisições HTTP, mas além disso, eles também encarregam tarefas mais complexas para os providers resolverem. Por exemplo, podemos criar um serviço para realizar as tarefas de cada função do nosso controller definido anteriormente. Esse serviço deve ter uma função para adicionar novos usuários e retornar todos que já existem. Uma forma simples de realizar isso pode ser vista no exemplo abaixo:
O decorator @Injectable é comumente utilizado para o container de Inversão de Controle do Nest consiga gerenciar o nosso provider. Esse conceito de Inversão de Controle é acompanhado por outro conceito chamado Injecção de Dependências, que é extremamente importante para entender o funcionamento de aplicações em NestJS. Um exemplo de Injeção de dependência pode ser feito adaptando o usersController para injetar o usersService nele. Como no exemplo abaixo:
import { Controller, Get, Post } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Post()
create() {
return 'Adiciona novo usuário';
}
@Get()
findAll() {
return 'Retorna todos usuários';
}
}
Os providers são uma parte muito importante para a arquitetura do Nest, mas diferente dos controllers, os providers podem ser vários tipos de classes, os mais comuns são services, repositories, helpers, etc. Como visto anteriormente, os controllers são responsáveis por lidar com requisições HTTP, mas além disso, eles também encarregam tarefas mais complexas para os providers resolverem. Por exemplo, podemos criar um serviço para realizar as tarefas de cada função do nosso controller definido anteriormente. Esse serviço deve ter uma função para adicionar novos usuários e retornar todos que já existem. Uma forma simples de realizar isso pode ser vista no exemplo abaixo:
import { Injectable } from '@nestjs/common';
import { User } from './interfaces/user.interface';
@Injectable()
export class UsersService {
private readonly users: User[] = [];
create(user: User) {
this.users.push(user);
}
findAll(): User[] {
return this.users;
}
}
O decorator @Injectable é comumente utilizado para o container de Inversão de Controle do Nest consiga gerenciar o nosso provider. Esse conceito de Inversão de Controle é acompanhado por outro conceito chamado Injecção de Dependências, que é extremamente importante para entender o funcionamento de aplicações em NestJS. Um exemplo de Injeção de dependência pode ser feito adaptando o usersController para injetar o usersService nele. Como no exemplo abaixo:
import { Body, Controller, Get, Post } from '@nestjs/common';
import { UsersService } from './users.service';
import { User } from './interfaces/user.interface';
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
@Post()
async create(@Body() user: User) {
return this.usersService.create(user);
}
@Get()
async findAll(): Promise<User[]> {
return this.usersService.findAll();
}
}
Com essa adaptação, o usersService é injetado no constructor da classe do usersController e utiliza-se dos métodos que create e findAll que esse provider possui. No Nest, esse controle de dependências é feito a partir da tipagem do Typescript, por isso só é ncessário colocar o tipo do nosso provider, que o Nest vai criar uma instância do usersService para ser utilizada dentro do controller.
Outro ponto extremamente importante, o Nest precisa ser informado sobre quais classes são providers, assim como controllers, entre outros. Dessa forma, ele consegue realizar todo o gerenciamento para Inversão de Controle, Injeção de Dependências, etc. Para isso, vamos entrar na última etapa da arquitetura do Nest, os modules.
Os modules são utilizados para registrar as classes da aplicação nas suas respectivas funções e, dessa forma, o NestJS gera um grafo da aplicação, que é a estrutura de dados usada para resolver as relações e dependências de modules e providers. Para nosso exemplo podemos ter o seguinte usersModule:
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
@Module({
controllers: [UsersController],
providers: [UsersService]
})
export class UsersModule {}
Como podemos ver, os modules são reconhecidos pelo decorator @Module, onde pode-se também definir toda sua estrutura. Essa estrutura pode variar bastante dependendo da configuração e complexidade do seu projeto, mas os modules são constituídos por 4 propriedades básicas: controllers, providers, imports e exports.
- Controllers: um array de controllers para todas as instâncias de controllers do module criado;
- Providers: Um array de providers que serão instanciados pelo injetor do Nest;
- Imports: Um array de modules que são importados para uso nesse module, por exemplo, poderíamos ter um módulo com uma conexão com o banco de dados, logo eu poderia importar esse módulo;
- Exports: Um array de providers que serão disponibilizados para outros módulos utilizarem, por exemplo, poderíamos disponibilizar o usersService para outros módulos terem acesso aos seus métodos.
Conclusão
Com isso, vimos que com o NestJS é possível criar aplicações com uma arquitetura modular, bem organizada e escalável. No entanto, esse texto é somente uma introdução aos conceitos do Nest, então você deve estudar e aplicar em projetos para conseguir ter total entendimento dessa ferramenta poderosa. Para isso, vou colocar uma lista dos comandos do NestJS que ajudarão na produtividade do desenvolvimento e um artigo do Angular extremamente útil para melhor entendimento sobre Inversão de Controle e Injeção de Dependências, que são conceitos fundamentais para utilizar o NestJS na melhor forma. Obrigado, até a próxima.