NestJS: Pode usar mais e mais!

Venho trabalhando com NestJS em meus projetos pessoais desde 2019, basicamente são projetos para expor pequenas APIs que servem a alguns poucos e pequenos sites.
Recentemente tive a oportunidade de compor um time que estava usando NestJS de uma forma muito séria e fizemos a implementação de uma API muito robusta que esta escalando mais e mais.
Estou muito satisfeito com o resultado que o NestJS tem nos proporcionado e isso me motivou a fazer esses post.

O que é NestJS

Framework de NodeJs que possibilita aos desenvolvedores de TypeScript e JavaScript criar aplicações eficientes e capazes de escalar de maneira muito simples e rápida.
Escrevi TypeScript antes de JavaScript porque o NestJS é TypeScript first ou seja, todo o desenvolvimento é baseado em TypeScript mas é claro você ainda pode usar JavaScript (fique à lá vonté).
Um detalhe sobre o NestJS, "por baixo dos panos" NestJS faz uso do nosso querido Express. Você não é obrigado usar o Express, você pode trocar por Fastify. Esse é um post padrão, então vai seguir com o Express no exemplo!

Vamos ao tutorial

Primeiramente, vamos instalar e fazer uso da Nest CLI que é um command-line que vai nos ajudar a inicializar o projeto, nos ajuda no desenvolvimento e a manter o projeto também.
$ npm i -g @nestjs/cli
$ nest new project-name

Como demonstração neste post vamos criar uma API para registro de contatos.
Execute o comando:
nest new api-contact
Quando aparecer a pergunta - Which package manager would you ❤️ use? - selecione "npm".
Navega até a pasta:
cd api-contact
Você verá a seguinte estrutura inicial do projeto:

O scaffolding do NestJS já traz vários arquivos de projetos que nos ajudam muito durante o desenvolvimento.

Uma coisa, antes de começar a codificar, vamos adicionar o TypeORM

npm i --save @nestjs/typeorm typeorm

Agora que já temos a dependência do TypeORM instalado, vamos criar nossa primeira entidade.
Crie um arquivo chamado contact.entity.ts

import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Contact {
@PrimaryGeneratedColumn()
id: number;
@Column()
fullName: string;
@Column()
birth: Date;
@Column()
phoneNumber: number;
@Column()
email: string;
@Column()
address: string;
}

Agora vamos fazer uma de uma DTO que será usada para salvar os registros de contatos, então crie o arquivo CreateContact.dto.ts:

export class CreateContactDto {
fullName: string;
brith: Date;
phoneNumber: number;
email: string;
address: string;
}

Não vou falar hoje sobre padrão Service Repository, mas pra quem conhece esse conceito vai identificar isso muito fácil no NestJS.
Crie o arquivo contacts.service.ts:

import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateContactDto } from './CreateContact.dto';
import { Contact } from './contact.entity';
@Injectable()
export class ContactsService {
constructor(
@InjectRepository(Contact)
private readonly repository: Repository<Contact>,
) {}
public async findContact(contactId: string): Promise<Contact> {
return this.repository.findOne(contactId);
}
public async findAll(): Promise<Contact[]> {
return this.repository.find({});
}
public async CreateContact(input: CreateContactDto) {
const savedContact = await this.repository.save({ ...input });
if (!savedContact) {
throw new InternalServerErrorException(
'Problem ocorried when trying to create a new contact',
);
}
return savedContact;
}
}

Obviamente a repository do exemplo salva dados em um banco de dados.
Vou fornecer aqui as configurações que eu usei para o PostgreSQL. Para isso usei o arquivo app.module.ts que é padrão já fornecido pelo scaffolding do NestJS:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { Contact } from './contact.entity';
import { ContactsController } from './contacts.controller';
import { ContactsService } from './contacts.service';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: '127.0.0.1',
port: 5432,
username: 'postgres',
password: 'example',
database: 'contactList',
synchronize: true,
entities: [Contact],
}),
TypeOrmModule.forFeature([Contact]),
],
controllers: [AppController, ContactsController],
providers: [AppService, ContactsService],
})
export class AppModule {}
view raw app.module.ts hosted with ❤ by GitHub

Pra facilitar a vida de quem acompanha esse post vou deixar o arquivo docker-compose.yml disponível para quem tem interesse de já rodar a imagem de um PostgreSQL e testar a API.

version: "3.8"
services:
postgres:
image: postgres:13.1
restart: always
environment:
POSTGRES_PASSWORD: example
ports:
- 5432:5432
adminer:
image: adminer
restart: always
ports:
- 8080:8080

Até aqui eu só mostrei o exemplo do código de produção, ou seja o código que necessitamos para rodar a API de forma funcional.
Agora, vamos ver a parte do código que corresponde aos Testes Unitários.

Por padrão o NestJS já cria um arquivo de teste que vem junto com o scaffolding.

Para esse exemplo vou deixar o código de testes unitários que usei para testar a nossa service, então veja o código do arquivo contacts.service.spec.ts:

import {
NotFoundException,
InternalServerErrorException,
} from '@nestjs/common';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Test, TestingModule } from '@nestjs/testing';
import { ContactsService } from './contacts.service';
import { Contact } from './contact.entity';
import { CreateContactDto } from './CreateContact.dto';
describe('ContactService', () => {
let service: ContactsService;
const mockRepository = {
find: jest.fn(),
findOne: jest.fn(),
save: jest.fn(),
update: jest.fn(),
};
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ContactsService,
{
provide: getRepositoryToken(Contact),
useValue: mockRepository,
},
],
}).compile();
service = module.get<ContactsService>(ContactsService);
});
beforeEach(() => {
mockRepository.find.mockReset();
mockRepository.findOne.mockReset();
mockRepository.save.mockReset();
mockRepository.update.mockReset();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('When search All Contacts', () => {
it('should be list all Contacts', async () => {
const c1 = new Contact();
c1.id = 10;
c1.fullName = 'Contact 1';
const c2 = new Contact();
c2.id = 11;
c2.fullName = 'Contact 2';
mockRepository.find.mockReturnValue([c1, c2]);
const contactsResult = await service.findAll();
expect(contactsResult).toHaveLength(2);
expect(mockRepository.find).toHaveBeenCalledTimes(1);
});
});
describe('find a contact', () => {
it('find a existing contact', async () => {
const contact = new Contact();
contact.id = 10;
contact.fullName = 'Contact 1';
mockRepository.findOne.mockReturnValue(contact);
const contactResult = await service.findContact('10');
console.log(contactResult);
expect(contactResult).toMatchObject({
id: contact.id,
fullName: contact.fullName,
});
expect(mockRepository.findOne).toHaveBeenCalledTimes(1);
});
it('should return an exception when do not find a contact', async () => {
mockRepository.findOne.mockReturnValue(null);
expect(service.findContact('3')).rejects.toBeInstanceOf(
NotFoundException,
);
expect(mockRepository.findOne).toHaveBeenCalledTimes(1);
});
});
describe('Create Contact', () => {
it('should create a Contact', async () => {
const input = new CreateContactDto();
input.fullName = 'Contact 1';
mockRepository.save.mockReturnValue(input);
const savedContact = await service.CreateContact(input);
expect(savedContact).toMatchObject(input);
expect(mockRepository.save).toBeCalledTimes(1);
});
it('should return an exception when do not create a contact', async () => {
const input = new CreateContactDto();
input.fullName = 'Contact 1';
mockRepository.save.mockReturnValue(null);
await service.CreateContact(input).catch((e) => {
expect(e).toBeInstanceOf(InternalServerErrorException);
expect(e).toMatchObject({
message: 'Problem ocorried when trying to create a new contact',
});
});
expect(mockRepository.save).toBeCalledTimes(1);
});
});
});

Eu implementei alguns poucos testes, porém o que eu quero demonstrar é a facilidade que temos em montar nossos testes unitários com Jest e NestJS.
Veja como é fluída a codificação dos testes.

Código completo do exemplo no ❤️Github, onde tudo esta!

Conclusão
NestJS é um framework muito estável e de fácil adoção em aplicações de todos os níveis de complexidade isso porque ele é muito simples de se usar e também porque Nest CLI já te serve uma ótima estrutura de código para começo de projeto e de fácil alteração e evolução.

47