JavaScript Avançado

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

8 min de leitura

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

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

<h2>Arquitetura Modular no NestJS</h2>

<p>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.</p>

<p>Para criar um módulo, você precisa de um arquivo de definição que use o decorator <code>@Module()</code>. Vamos construir um exemplo prático com um módulo de usuários:</p>

<pre><code class="language-typescript">// users.module.ts

import { Module } from &#039;@nestjs/common&#039;;

import { UsersController } from &#039;./users.controller&#039;;

import { UsersService } from &#039;./users.service&#039;;

@Module({

controllers: [UsersController],

providers: [UsersService],

exports: [UsersService], // Permite outros módulos usarem este serviço

})

export class UsersModule {}</code></pre>

<p>O módulo acima exporta <code>UsersService</code>, permitindo que outros módulos o importem. No <code>app.module.ts</code> principal, você importa os módulos:</p>

<pre><code class="language-typescript">// app.module.ts

import { Module } from &#039;@nestjs/common&#039;;

import { UsersModule } from &#039;./users/users.module&#039;;

import { AuthModule } from &#039;./auth/auth.module&#039;;

@Module({

imports: [UsersModule, AuthModule],

})

export class AppModule {}</code></pre>

<p>Essa estrutura garante baixo acoplamento e alta coesão — princípios fundamentais de bom design.</p>

<h2>Injeção de Dependência (DI) e Provedores</h2>

<p>A Injeção de Dependência no NestJS é gerenciada automaticamente pelo container IoC (Inversão de Controle). Provedores são classes marcadas com <code>@Injectable()</code> que podem ser injetadas em controladores, outros serviços ou guards. Isso elimina a necessidade de instanciação manual e facilita testes.</p>

<p>Vejamos um exemplo completo com um serviço que usa banco de dados:</p>

<pre><code class="language-typescript">// users.service.ts

import { Injectable } from &#039;@nestjs/common&#039;;

interface User {

id: number;

name: string;

email: string;

}

@Injectable()

export class UsersService {

private users: User[] = [

{ id: 1, name: &#039;João&#039;, email: &#039;joao@example.com&#039; },

{ id: 2, name: &#039;Maria&#039;, email: &#039;maria@example.com&#039; },

];

findAll(): User[] {

return this.users;

}

findById(id: number): User | undefined {

return this.users.find(user =&gt; 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;

}

}</code></pre>

<p>Agora o controlador injeta o serviço:</p>

<pre><code class="language-typescript">// users.controller.ts

import { Controller, Get, Post, Param, Body } from &#039;@nestjs/common&#039;;

import { UsersService } from &#039;./users.service&#039;;

@Controller(&#039;users&#039;)

export class UsersController {

constructor(private readonly usersService: UsersService) {}

@Get()

findAll() {

return this.usersService.findAll();

}

@Get(&#039;:id&#039;)

findById(@Param(&#039;id&#039;) id: string) {

return this.usersService.findById(parseInt(id));

}

@Post()

create(@Body() body: { name: string; email: string }) {

return this.usersService.create(body.name, body.email);

}

}</code></pre>

<p>O NestJS automaticamente instancia <code>UsersService</code> e a injeta no controlador. Para injetar em outro serviço, use a mesma sintaxe.</p>

<h3>Provedores Customizados</h3>

<p>Às vezes você precisa configurar provedores de forma mais complexa. Use o padrão de objeto:</p>

<pre><code class="language-typescript">// users.module.ts (atualizado)

@Module({

providers: [

{

provide: &#039;USER_REPOSITORY&#039;,

useValue: [], // Valor direto

},

UsersService,

],

})

export class UsersModule {}</code></pre>

<p>E injete com <code>@Inject()</code>:</p>

<pre><code class="language-typescript">constructor(@Inject(&#039;USER_REPOSITORY&#039;) private userRepo: any[]) {}</code></pre>

<h2>Guards e Autenticação</h2>

<p>Guards são responsáveis por controlar o acesso às rotas. Eles implementam a interface <code>CanActivate</code> e retornam <code>true</code> para permitir acesso ou <code>false</code> para bloqueá-lo. Um caso de uso comum é validar tokens JWT.</p>

<p>Vamos criar um guard de autenticação:</p>

<pre><code class="language-typescript">// auth.guard.ts

import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from &#039;@nestjs/common&#039;;

import { Request } from &#039;express&#039;;

@Injectable()

export class AuthGuard implements CanActivate {

canActivate(context: ExecutionContext): boolean {

const request = context.switchToHttp().getRequest&lt;Request&gt;();

const token = request.headers.authorization?.split(&#039; &#039;)[1];

if (!token) {

throw new UnauthorizedException(&#039;Token não encontrado&#039;);

}

// Validação simplificada — em produção use JWT real

if (token !== &#039;valid-token-123&#039;) {

throw new UnauthorizedException(&#039;Token inválido&#039;);

}

request[&#039;user&#039;] = { id: 1, name: &#039;João&#039; }; // Adiciona usuário à requisição

return true;

}

}</code></pre>

<p>Agora use o guard nas rotas:</p>

<pre><code class="language-typescript">// users.controller.ts (atualizado)

import { UseGuards } from &#039;@nestjs/common&#039;;

import { AuthGuard } from &#039;../auth/auth.guard&#039;;

@Controller(&#039;users&#039;)

@UseGuards(AuthGuard) // Aplica globalmente ao controlador

export class UsersController {

// ... métodos anteriores

}</code></pre>

<p>Ou aplique apenas em métodos específicos:</p>

<pre><code class="language-typescript">@Get(&#039;profile&#039;)

@UseGuards(AuthGuard)

getProfile(@Request() req: any) {

return req.user;

}</code></pre>

<p>Para aplicar globalmente a toda aplicação, configure no <code>main.ts</code>:</p>

<pre><code class="language-typescript">// main.ts

import { NestFactory } from &#039;@nestjs/core&#039;;

import { AppModule } from &#039;./app.module&#039;;

import { AuthGuard } from &#039;./auth/auth.guard&#039;;

async function bootstrap() {

const app = await NestFactory.create(AppModule);

app.useGlobalGuards(new AuthGuard());

await app.listen(3000);

}

bootstrap();</code></pre>

<h3>Guards com Roles</h3>

<p>Para controle baseado em papéis, crie um guard que verifica permissões:</p>

<pre><code class="language-typescript">// roles.guard.ts

import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from &#039;@nestjs/common&#039;;

import { Reflector } from &#039;@nestjs/core&#039;;

@Injectable()

export class RolesGuard implements CanActivate {

constructor(private reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {

const requiredRoles = this.reflector.get&lt;string[]&gt;(&#039;roles&#039;, 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(&#039;Acesso negado&#039;);

}

return true;

}

}</code></pre>

<p>Use com um decorator customizado:</p>

<pre><code class="language-typescript">// roles.decorator.ts

import { SetMetadata } from &#039;@nestjs/common&#039;;

export const Roles = (...roles: string[]) =&gt; SetMetadata(&#039;roles&#039;, roles);

// Na rota:

@Get(&#039;admin&#039;)

@Roles(&#039;admin&#039;)

@UseGuards(RolesGuard)

getAdminData() {

return { message: &#039;Dados sensíveis&#039; };

}</code></pre>

<h2>Conclusão</h2>

<p>Dominando esses três pilares — <strong>arquitetura modular</strong>, <strong>injeção de dependência</strong> e <strong>guards</strong> — 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.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://docs.nestjs.com/" target="_blank" rel="noopener noreferrer">NestJS Official Documentation</a></li>

<li><a href="https://docs.nestjs.com/modules" target="_blank" rel="noopener noreferrer">NestJS Modules Guide</a></li>

<li><a href="https://docs.nestjs.com/guards" target="_blank" rel="noopener noreferrer">NestJS Guards Documentation</a></li>

<li><a href="https://www.typescriptlang.org/docs/" target="_blank" rel="noopener noreferrer">TypeScript Handbook</a></li>

<li><a href="https://docs.nestjs.com/techniques/validation" target="_blank" rel="noopener noreferrer">NestJS Best Practices</a></li>

</ul>

Comentários

Mais em JavaScript Avançado

O que Todo Dev Deve Saber sobre Template Literal Types e Recursive Types em TypeScript
O que Todo Dev Deve Saber sobre Template Literal Types e Recursive Types em TypeScript

Template Literal Types em TypeScript Template Literal Types permitem criar ti...

Gerenciamento de Estado Avançado: Zustand, Jotai e Recoil Comparados na Prática
Gerenciamento de Estado Avançado: Zustand, Jotai e Recoil Comparados na Prática

Introdução ao Gerenciamento de Estado Moderno O gerenciamento de estado é um...

Boas Práticas de Cluster e Worker Threads em Node.js: Aproveitando Múltiplos Núcleos para Times Ágeis
Boas Práticas de Cluster e Worker Threads em Node.js: Aproveitando Múltiplos Núcleos para Times Ágeis

O Problema de Performance no Node.js Node.js é single-threaded por padrão, o...