Conhecendo o framework NestJS

Conhecendo o framework NestJS

Apresentação do framework

Se você é da área de desenvolvimento, provavelmente já ouviu falar do NestJS (Não NextJS!) em alguma propaganda do Youtube ou até mesmo em outros posts. Esse framework, que usa NodeJS por baixo dos panos e é fortemente inspirado no Angular, tem como principal motivação a proposta de uma arquitetura forte que permite a criação de aplicações escaláveis, fracamente acopladas e de fácil manutenção e teste. Como ele faz isso? Descobriremos nesse post !

Principais pilares

Das inúmeras funcionalidades que o NestJS te oferece, vou destacar as principais que serão utilizadas em praticamente todo projeto.

Modularização

Você provavelmente já ouviu esse termo em algum lugar, que nada mais é do que o próprio nome diz: separar a sua aplicação em módulos. Com essa prática, podemos separar as responsabilidades da nossa aplicação em diferentes módulos que depois podemos "conecta-los" para o compartilhamento de funcionalidades. O NestJS leva a modularização MUITO a sério, como pode ser visto nesse trecho da sua própria documentação:

Nós queremos enfatizar que módulos são fortemente recomendados como uma efetiva maneira de organizar seus componentes. Portanto, para a maiora das aplicações, a arquitetura resultante será de múltiplos módulos, cada um encapsulando uma série de funcionalidades.

Sem dúvidas, é um bom jeito de separar as responsabilidades da sua aplicação, deixando mais fácil uma futura manutenção e testes. Se você nem imagina como se parece um módulo do Nest, aqui está um exemplo simples:

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { DogsModule } from '../dogs/dogs.module'

@Module({
  imports: [DogsModule],
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService]
})
export class CatsModule {}

Como no exemplo, todo módulo que você criar deve ter esse decorator "Module" que serve para o Nest saber que é um módulo e iniciar sua aplicação. Dentro do objeto que esse decorator "Module" recebe como argumento, é definido o que ele pode importar (normalmente outro módulo), controllers (o que veremos mais tarde), providers (que veremos mais tarde) e o que ele pode exportar (normalmente um serviço). No final, exportamos uma classe com o nome do nosso módulo.

Em um grande projeto onde hajam muitos módulos, fica chato toda hora ter que escrever tudo isso para a criação. Por isso, a Nest CLI resolve esse problema para nós, criando toda a estrutura de um módulo, controllers e services passando somente o nome que desejamos. Para saber mais sobre módulos, visite a documentação oficial.

Controllers

Os famosos controlles, são responsáveis por lidarem com as requisições do client e retornar suas devidas repostas. Um exemplo simples de controller no Nest pode ser esse:

import { Controller, Get } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get('index')
  findAll(): string {
    return 'This action returns all cats';
  }
}

Para criarmos esse simples controller precisamos importar um decorator "Controller" e "Get" (parecido com o que fizemos para o módulo). O decorator de "Controller" como o próprio nome já diz, serve para dizer ao Nest que isso será um controller. O "Get" serve para dizer qual será o método http da rota desse controller.

Você desse ter percebido que passamos como argumento a string "cats" para o decorator de controller, isso serve para definir a rota que esse controller irá lidar. Todas as requisições que tenham a url por exemplo "localhost:3000/cats" irão cair nesse controller. A mesma coisa serve para o argumento que passamos para o decorator do método http, no caso "index." Isso quer dizer que uma requisição com a rota "localhost:3000/cats/index" irá ser tratada pela função findAll(), que no momento apenas nos retorna uma string. Assim que é feito o roteamento no NestJs, interessante né?

Claro que isso que estou apresentando é o básico dos controllers, para contruir algo real você precisará de um pouco mais que isso, como receber os dados da requisição como parâmetro da função que você definir para lidar com "X" rota, ou definir quais rotas da sua aplicação serão protegidas ou não. Para saber mais sobre controllers no Nest, visite a documentação oficial.

Providers

Providers são conceitos importantíssimos do Nest, já que neles que ficam praticamente toda a lógica da sua aplicação. A ideia principal dos providers é trabalhar com injeção de dependências, podendo compartilhar por toda a aplicação funcionalidades de diferentes módulos. O principal provider que veremos são os "serviços". Vamos criar um simples provider (serviço), ficará algo assim:

import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {

  sayMeow() {
    return "Meow";
  }

  scratch() {
    return "Scratched!";
  }
}

claro que esse exemplo é muito simples, mas é somente para mostrar que a lógica, as regras de negócio devem ser delegadas para o serviço. Um exemplo mais elaborado seria um serviço para lidar com a rota que criamos no nosso controller. Nós tinhamos uma rota "cats/index", correto? Essa rota com certeza seria tratada por uma função do nosso "CatsService", como por exemplo:

import {  Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Cat } from './cat.entity';

@Injectable()
export class CatService {
  constructor(
    @InjectRepository(Cat)
    private catsRepository: Repository<Cat>
  ) {}

  async findAll() {
    return await this.catsRepository.findAll();
  }

esse exemplo mesmo continuando simples, já é algo funcional. Apenas injetamos um repositório de Cat que foi feito com TypeORM no construtor da classe (não vou abordar a configuração de repositórios TypeORM neste post, se quiser ver mais sobre isso acesse a documentação oficial. Apenas tenha em mente que estou injetando um certo repositório TypeORM nesse serviço para usar suas funcionalidades.) e declaramos uma função "findAll" que nos retorna todos os items desse repositório. Ainda um simples exemplo de serviço, mas funcional.

Injeção de dependências

Por último, mas não menos importante, a famosa injeção de dependêcias. Na última sessão você deve ter percebido que antes de declararmos nossa classe de serviço, colocamos um decorator "Injectable", isso quer dizer que nossa classe aceita a injeção de dependências. Isso que estamos fazendo, injetando o repostório por meio do construtor da classe, voilà! Por enquanto apenas injetamos um respositório, mas podemos injetar serviços em outros serviços também, que é o mais comum! Supondo que temos o módulo "dog", que se parece com isso:

import { Module } from '@nestjs/common';
import { DogsController } from './dogs.controller';
import { DogsService } from './dogs.service';

@Module({
  controllers: [DogsController],
  providers: [DogsService],
  exports: [DogsService]
})
export class DogsModule {}

você já sabe o que isso significa, mas preste atenção que estamos exportando o serviço desse módulo. Mas como importamos ele em outro módulo? Simples assim:

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { DogsModule } from '../dogs/dogs.module'

@Module({
  imports: [DogsModule],
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

preste atenção que estamos importando o "DogsModule", ou seja, no "CatsModule" temos acesso a tudo que o "DogsModule" exportou! Vou dar um exemplo de como injetar o serviço de "dogs" no serviço de "cats":

import {  Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Cat } from './cat.entity';
import { DogsService } from '../dogs/dogs.service'

@Injectable()
export class CatService {
  constructor(
    @InjectRepository(Cat)
    private catsRepository: Repository<Cat>,
    private readonly dogsService: DogsService,
  ) {}

  async findAll() {
    return await this.catsRepository.findAll();
  }

aqui não estamos usando nenhuma função do serviço "dogs", mas poderiamos usar facilmente dentro de qualquer função com "this.dogsService.nomeDaFunção()".

Outro exemplo importande disso é quando queremos usar uma função do serviço dentro do controller, que é muito feito. Um exemplo:

import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Get('index')
  async findAll(): string {
    return await this.catsService.findAll();
  }
}

aqui injetamos o serviço do próprio módulo para poder usar nas rotas do controller, passando coisas mais complexas para o nosso serviço lidar. Basicamente, isso é injeção de dependências no NestJs!

Considerações finais

Com esse post espero ter ajudado você a entender as funcionalidades básicas do Nest e de como ele se comportar e talvez até motivado a testar esse framework. Se você veio do Angular, será bem mais fácil de aprender ele já que é fortemente inspirado (a sintaxe por exemplo). Caso você queira ver um exemplo de aplicação construid em Nest, tem esse meu projeto que foi o que me motivou a fazer esse post. A documentação oficial do Nest pode ser encontrada aqui, onde há tudo que você precisa saber para construir aplicações reais.

Até a próxima !