NestJS com TypeScript: Arquitetura Modular, DI e Guards na Prática Já leu

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 . Vamos construir um exemplo prático com um módulo de usuários: O módulo acima exporta , permitindo que outros módulos o importem. No principal, você importa os módulos: 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 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: Agora

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.

Referências


Artigos relacionados