DEV Community

Tarcísio Corte
Tarcísio Corte

Posted on

3

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:

Estrutura básica de um projeto criado com Nest CLI

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
Enter fullscreen mode Exit fullscreen mode

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.

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (3)

Collapse
 
micalevisk profile image
Micael Levi L. C.

Boa!

Grupo BR no Telegram sobre NestJS pra quem quiser tirar dúvidas e discutir sobre o fw: t.me/nestjsbr

Collapse
 
suhakim profile image
sadiul hakim

I desire to learn nestjs

Collapse
 
tarcisio profile image
Tarcísio Corte

Look at my github github.com/tarcisiocorte that I always update more and more javascript/typescript code.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay