<h2>O que é NestJS e por que arquitetura modular importa</h2>
<p>NestJS é um framework progressivo para construir aplicações Node.js eficientes e escaláveis. Ele oferece uma arquitetura opinada baseada em decoradores TypeScript, inspirada em padrões do Angular e em conceitos de programação orientada a objetos. A diferença crucial do NestJS em relação a outros frameworks como Express ou Fastify é que ele <strong>força uma estrutura organizacional</strong> desde o início do projeto.</p>
<p>A arquitetura modular é essencial porque permite que sua aplicação cresça sem virar um caos de dependências. Quando você organiza seu código em módulos bem definidos, cada parte da aplicação tem uma responsabilidade clara, fica mais fácil testar, manter e até mesmo refatorar. Imagine uma API com autenticação, usuários, produtos e pagamentos — sem modularização, todos esses conceitos ficariam misturados em uma única pasta, tornando a manutenção um pesadelo. Com módulos, cada contexto de negócio fica isolado e comunicável.</p>
<h2>Entendendo Injeção de Dependências</h2>
<h3>O Problema que a Injeção de Dependências resolve</h3>
<p>Suponha que você tenha uma classe <code>UsuarioService</code> que depende de uma classe <code>DatabaseService</code>. Sem injeção de dependências, você faria assim:</p>
<pre><code class="language-typescript">class UsuarioService {
private db: DatabaseService;
constructor() {
this.db = new DatabaseService(); // Acoplamento forte
}
buscarUsuario(id: string) {
return this.db.query(SELECT * FROM usuarios WHERE id = ${id});
}
}</code></pre>
<p>O problema aqui é que <code>UsuarioService</code> <strong>cria sua própria instância</strong> de <code>DatabaseService</code>. Se você quiser mudar para outro banco de dados em testes, ou usar uma implementação fake, você é forçado a modificar a classe. Isso é <strong>acoplamento forte</strong> e torna seu código frágil.</p>
<h3>Como Injeção de Dependências resolve isso</h3>
<p>A injeção de dependências é um padrão que <strong>remove a responsabilidade de criar dependências</strong> da classe. Em vez de criar, você <strong>recebe</strong> as dependências:</p>
<pre><code class="language-typescript">class UsuarioService {
constructor(private db: DatabaseService) {} // Dependência injetada
buscarUsuario(id: string) {
return this.db.query(SELECT * FROM usuarios WHERE id = ${id});
}
}
// Uso
const db = new DatabaseService();
const usuarioService = new UsuarioService(db);</code></pre>
<p>Agora qualquer classe que criar <code>UsuarioService</code> pode passar qualquer implementação de <code>DatabaseService</code> — seja real ou um mock para testes. Seu código ficou <strong>desacoplado</strong>. NestJS automatiza isso através de seu sistema de injeção nativo usando decoradores.</p>
<h2>Arquitetura Modular na prática com NestJS</h2>
<h3>Estrutura de um módulo simples</h3>
<p>Um módulo NestJS é criado usando o decorador <code>@Module()</code>. Vamos criar um exemplo completo de um módulo de usuários:</p>
<pre><code class="language-typescript">// usuarios/usuarios.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsuariosService {
private usuarios = [
{ id: 1, nome: 'Alice' },
{ id: 2, nome: 'Bob' },
];
buscarTodos() {
return this.usuarios;
}
buscarPorId(id: number) {
return this.usuarios.find(u => u.id === id);
}
criar(dados: { nome: string }) {
const novoUsuario = {
id: this.usuarios.length + 1,
nome: dados.nome,
};
this.usuarios.push(novoUsuario);
return novoUsuario;
}
}</code></pre>
<p>Uma <strong>Service</strong> em NestJS é uma classe marcada com <code>@Injectable()</code>. Ela contém a lógica de negócio. Note que não há nenhuma responsabilidade de HTTP aqui — é apenas lógica pura.</p>
<p>Agora criamos um <strong>Controller</strong>, que é responsável por receber requisições HTTP e delegar para a service:</p>
<pre><code class="language-typescript">// usuarios/usuarios.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { UsuariosService } from './usuarios.service';
@Controller('usuarios')
export class UsuariosController {
constructor(private readonly usuariosService: UsuariosService) {}
@Get()
buscarTodos() {
return this.usuariosService.buscarTodos();
}
@Get(':id')
buscarPorId(@Param('id') id: string) {
return this.usuariosService.buscarPorId(Number(id));
}
@Post()
criar(@Body() dados: { nome: string }) {
return this.usuariosService.criar(dados);
}
}</code></pre>
<p>Aqui vemos <strong>injeção de dependências em ação</strong>: o controller recebe <code>UsuariosService</code> através do construtor, sem precisar instanciar manualmente. O NestJS cuida disso.</p>
<p>Agora juntamos tudo em um <strong>módulo</strong>:</p>
<pre><code class="language-typescript">// usuarios/usuarios.module.ts
import { Module } from '@nestjs/common';
import { UsuariosService } from './usuarios.service';
import { UsuariosController } from './usuarios.controller';
@Module({
controllers: [UsuariosController],
providers: [UsuariosService],
exports: [UsuariosService], // Permite que outros módulos use essa service
})
export class UsuariosModule {}</code></pre>
<p>O módulo é o <strong>contenedor</strong> que agrupa controllers e providers (services). Quando você marca <code>UsuariosService</code> em <code>providers</code>, o NestJS automaticamente instancia uma única instância dessa classe (padrão Singleton) e a injeta em qualquer lugar que seja solicitada.</p>
<h3>Importando módulos em sua aplicação</h3>
<pre><code class="language-typescript">// app.module.ts
import { Module } from '@nestjs/common';
import { UsuariosModule } from './usuarios/usuarios.module';
@Module({
imports: [UsuariosModule],
})
export class AppModule {}</code></pre>
<p>Seu módulo raiz importa o módulo de usuários. Quando a aplicação inicia, NestJS automaticamente instancia tudo que está declarado.</p>
<h2>Casos avançados e boas práticas</h2>
<h3>Removendo dependências de banco de dados real em testes</h3>
<p>Um exemplo muito comum é quando você quer testar seu <code>UsuariosService</code> sem depender de um banco de dados real. Você cria um <strong>provider alternativo</strong>:</p>
<pre><code class="language-typescript">// usuarios/usuarios.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsuariosService } from './usuarios.service';
describe('UsuariosService', () => {
let service: UsuariosService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsuariosService],
}).compile();
service = module.get<UsuariosService>(UsuariosService);
});
it('deve retornar lista de usuários', () => {
const resultado = service.buscarTodos();
expect(resultado).toHaveLength(2);
});
it('deve criar novo usuário', () => {
const antes = service.buscarTodos().length;
service.criar({ nome: 'Charlie' });
const depois = service.buscarTodos().length;
expect(depois).toBe(antes + 1);
});
});</code></pre>
<p>O <code>Test.createTestingModule()</code> permite que você crie um contexto isolado apenas para testes, sem carregar toda a aplicação.</p>
<h3>Usando interfaces para criar pontos de extensão</h3>
<p>Quando você quer permitir múltiplas implementações de um serviço (por exemplo, diferentes formas de autenticação), use interfaces:</p>
<pre><code class="language-typescript">// autenticacao/autenticacao.interface.ts
export interface ProvedorAutenticacao {
autenticar(usuario: string, senha: string): Promise<boolean>;
}
// autenticacao/jwt.autenticacao.ts
import { Injectable } from '@nestjs/common';
import { ProvedorAutenticacao } from './autenticacao.interface';
@Injectable()
export class JwtAutenticacao implements ProvedorAutenticacao {
async autenticar(usuario: string, senha: string): Promise<boolean> {
// Lógica JWT
return true;
}
}
// autenticacao/oauth.autenticacao.ts
import { Injectable } from '@nestjs/common';
import { ProvedorAutenticacao } from './autenticacao.interface';
@Injectable()
export class OAuthAutenticacao implements ProvedorAutenticacao {
async autenticar(usuario: string, senha: string): Promise<boolean> {
// Lógica OAuth
return true;
}
}</code></pre>
<p>Depois, em seu módulo, você <strong>injeta uma implementação específica</strong>:</p>
<pre><code class="language-typescript">// autenticacao/autenticacao.module.ts
import { Module } from '@nestjs/common';
import { ProvedorAutenticacao } from './autenticacao.interface';
import { JwtAutenticacao } from './jwt.autenticacao';
import { OAuthAutenticacao } from './oauth.autenticacao';
@Module({
providers: [
{
provide: ProvedorAutenticacao,
useClass: process.env.AUTENTICACAO === 'oauth'
? OAuthAutenticacao
: JwtAutenticacao,
},
],
exports: [ProvedorAutenticacao],
})
export class AutenticacaoModule {}</code></pre>
<p>Agora qualquer serviço que dependa de <code>ProvedorAutenticacao</code> receberá automaticamente a implementação correta baseada em variáveis de ambiente. Você mudou a estratégia de autenticação <strong>sem tocar em nenhum outro código</strong>.</p>
<h3>Comunicação entre módulos</h3>
<p>Módulos precisam se comunicar. Vamos criar um módulo de pagamentos que depende de usuários:</p>
<pre><code class="language-typescript">// pagamentos/pagamentos.service.ts
import { Injectable } from '@nestjs/common';
import { UsuariosService } from '../usuarios/usuarios.service';
@Injectable()
export class PagamentosService {
constructor(private usuariosService: UsuariosService) {}
processarPagamento(usuarioId: number, valor: number) {
const usuario = this.usuariosService.buscarPorId(usuarioId);
if (!usuario) {
throw new Error('Usuário não encontrado');
}
return {
status: 'sucesso',
usuario: usuario.nome,
valor: valor,
timestamp: new Date(),
};
}
}
// pagamentos/pagamentos.module.ts
import { Module } from '@nestjs/common';
import { PagamentosService } from './pagamentos.service';
import { PagamentosController } from './pagamentos.controller';
import { UsuariosModule } from '../usuarios/usuarios.module';
@Module({
imports: [UsuariosModule], // Importa o módulo para acessar UsuariosService
controllers: [PagamentosController],
providers: [PagamentosService],
})
export class PagamentosModule {}</code></pre>
<p>Aqui o módulo de pagamentos <strong>importa</strong> o módulo de usuários. Isso só funciona porque <code>UsuariosModule</code> exporta <code>UsuariosService</code>. Se não exportasse, <code>PagamentosService</code> não conseguiria acessá-lo.</p>
<h2>Conclusão</h2>
<p>Aprendemos que <strong>injeção de dependências é sobre remover acoplamento</strong>: em vez de suas classes criarem suas próprias dependências, elas as recebem. Isso torna o código testável, flexível e manutenível. O NestJS automatiza esse padrão através de decoradores, você não precisa instanciar manualmente nada — apenas declare o que precisa no construtor e o framework cuida do resto.</p>
<p>Também vimos que <strong>módulos são contentores organizacionais</strong> que agrupam controllers, services e outras partes relacionadas. Quando sua aplicação cresce, você não tem uma pasta gigante de código — tem módulos bem definidos que comunicam entre si através de interfaces públicas (exports). Essa separação clara faz toda a diferença quando você volta ao código em seis meses ou trabalha em equipe.</p>
<p>A mensagem final: comece pensando em <strong>contextos de negócio</strong> (usuários, pagamentos, autenticação) e crie um módulo para cada um. Dentro de cada módulo, separe controllers de services. Use interfaces quando precisar de múltiplas implementações. Assim, sua aplicação crescerá de forma saudável e previsível.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://docs.nestjs.com/modules" target="_blank" rel="noopener noreferrer">NestJS Documentation - Modules</a></li>
<li><a href="https://docs.nestjs.com/providers#dependency-injection" target="_blank" rel="noopener noreferrer">NestJS Documentation - Dependency Injection</a></li>
<li><a href="https://docs.nestjs.com/controllers" target="_blank" rel="noopener noreferrer">NestJS Documentation - Controllers</a></li>
<li><a href="https://www.typescriptlang.org/docs/" target="_blank" rel="noopener noreferrer">TypeScript Official Handbook</a></li>
<li><a href="https://martinfowler.com/articles/injection.html" target="_blank" rel="noopener noreferrer">Martin Fowler - Inversion of Control Containers and the Dependency Injection pattern</a></li>
</ul>
<p><!-- FIM --></p>