Arquitetura Modular no NestJS
O NestJS foi construído com a modularidade como pilar fundamental. A arquitetura modular permite organizar sua aplicação em componentes independentes e reutilizáveis, facilitando manutenção e escalabilidade. Cada módulo encapsula controladores, serviços e configurações relacionadas a um domínio específico da aplicação.
Para criar um módulo, você precisa de um arquivo de definição que use o decorator @Module(). Vamos construir um exemplo prático com um módulo de usuários:
// users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // Permite outros módulos usarem este serviço
})
export class UsersModule {}
O módulo acima exporta UsersService, permitindo que outros módulos o importem. No app.module.ts principal, você importa os módulos:
// app.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { AuthModule } from './auth/auth.module';
@Module({
imports: [UsersModule, AuthModule],
})
export class AppModule {}
Essa estrutura garante baixo acoplamento e alta coesão — princípios fundamentais de bom design.
Injeção de Dependência (DI) e Provedores
A Injeção de Dependência no NestJS é gerenciada automaticamente pelo container IoC (Inversão de Controle). Provedores são classes marcadas com @Injectable() que podem ser injetadas em controladores, outros serviços ou guards. Isso elimina a necessidade de instanciação manual e facilita testes.
Vejamos um exemplo completo com um serviço que usa banco de dados:
// users.service.ts
import { Injectable } from '@nestjs/common';
interface User {
id: number;
name: string;
email: string;
}
@Injectable()
export class UsersService {
private users: User[] = [
{ id: 1, name: 'João', email: 'joao@example.com' },
{ id: 2, name: 'Maria', email: 'maria@example.com' },
];
findAll(): User[] {
return this.users;
}
findById(id: number): User | undefined {
return this.users.find(user => user.id === id);
}
create(name: string, email: string): User {
const newUser: User = {
id: this.users.length + 1,
name,
email,
};
this.users.push(newUser);
return newUser;
}
}
Agora o controlador injeta o serviço:
// users.controller.ts
import { Controller, Get, Post, Param, Body } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findById(@Param('id') id: string) {
return this.usersService.findById(parseInt(id));
}
@Post()
create(@Body() body: { name: string; email: string }) {
return this.usersService.create(body.name, body.email);
}
}
O NestJS automaticamente instancia UsersService e a injeta no controlador. Para injetar em outro serviço, use a mesma sintaxe.
Provedores Customizados
Às vezes você precisa configurar provedores de forma mais complexa. Use o padrão de objeto:
// users.module.ts (atualizado)
@Module({
providers: [
{
provide: 'USER_REPOSITORY',
useValue: [], // Valor direto
},
UsersService,
],
})
export class UsersModule {}
E injete com @Inject():
constructor(@Inject('USER_REPOSITORY') private userRepo: any[]) {}
Guards e Autenticação
Guards são responsáveis por controlar o acesso às rotas. Eles implementam a interface CanActivate e retornam true para permitir acesso ou false para bloqueá-lo. Um caso de uso comum é validar tokens JWT.
Vamos criar um guard de autenticação:
// auth.guard.ts
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Request } from 'express';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<Request>();
const token = request.headers.authorization?.split(' ')[1];
if (!token) {
throw new UnauthorizedException('Token não encontrado');
}
// Validação simplificada — em produção use JWT real
if (token !== 'valid-token-123') {
throw new UnauthorizedException('Token inválido');
}
request['user'] = { id: 1, name: 'João' }; // Adiciona usuário à requisição
return true;
}
}
Agora use o guard nas rotas:
// users.controller.ts (atualizado)
import { UseGuards } from '@nestjs/common';
import { AuthGuard } from '../auth/auth.guard';
@Controller('users')
@UseGuards(AuthGuard) // Aplica globalmente ao controlador
export class UsersController {
// ... métodos anteriores
}
Ou aplique apenas em métodos específicos:
@Get('profile')
@UseGuards(AuthGuard)
getProfile(@Request() req: any) {
return req.user;
}
Para aplicar globalmente a toda aplicação, configure no main.ts:
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AuthGuard } from './auth/auth.guard';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new AuthGuard());
await app.listen(3000);
}
bootstrap();
Guards com Roles
Para controle baseado em papéis, crie um guard que verifica permissões:
// roles.guard.ts
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
if (!requiredRoles) {
return true; // Sem restrição se não houver roles definidas
}
const request = context.switchToHttp().getRequest();
const userRole = request.user?.role;
if (!requiredRoles.includes(userRole)) {
throw new ForbiddenException('Acesso negado');
}
return true;
}
}
Use com um decorator customizado:
// roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
// Na rota:
@Get('admin')
@Roles('admin')
@UseGuards(RolesGuard)
getAdminData() {
return { message: 'Dados sensíveis' };
}
Conclusão
Dominando esses três pilares — arquitetura modular, injeção de dependência e guards — você construirá aplicações NestJS profissionais, testáveis e seguras. A modularidade permite organização escalável, a DI reduz acoplamento e facilita testes, enquanto guards protegem suas rotas com lógica de autenticação e autorização robusta. Pratique combinando esses conceitos em projetos reais para consolidar o aprendizado.