Usar NestJS no Node.js oferece uma arquitetura estruturada para APIs, serviços, filas, WebSockets e aplicações corporativas em TypeScript. O framework organiza o código em modules, controllers, providers, guards, interceptors, pipes e exception filters.
Essa estrutura ajuda equipes grandes a manter limites claros entre transporte, regra de negócio e infraestrutura. Porém, decorators e injeção de dependência não garantem automaticamente um bom design. É necessário evitar módulos globais excessivos, providers com muitas responsabilidades, autorização apenas em guards genéricos e acoplamento direto entre controllers e banco.
Neste guia, você aprenderá a criar um projeto NestJS, montar modules e controllers, usar providers, validar entradas, autenticar, autorizar, integrar PostgreSQL, configurar logs, testar e preparar o deploy.
O que é NestJS?
NestJS é um framework progressivo para aplicações Node.js. A documentação oficial do NestJS apresenta controllers, providers, modules e recursos de plataforma. O handbook do TypeScript ajuda a entender decorators usados pelo framework.
Quando usar?
- APIs grandes;
- equipes com padrões compartilhados;
- microserviços;
- GraphQL;
- WebSockets;
- aplicações com filas;
- domínios com muitos módulos;
- projetos que valorizam injeção de dependência.
Quando evitar?
- script pequeno;
- função serverless mínima;
- equipe que prefere composição funcional simples;
- API com poucas rotas e ciclo curto;
- projeto em que decorators e metadata não são desejados.
Instalando a CLI
npm install -g @nestjs/cli
nest new minha-apiA CLI cria estrutura inicial, scripts, testes e configuração TypeScript. Também é possível configurar manualmente sem instalação global.
Estrutura inicial
src/
app.controller.ts
app.module.ts
app.service.ts
main.tsÀ medida que a aplicação cresce, organize por domínio:
src/
users/
orders/
billing/
auth/
shared/Bootstrap
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
await app.listen(process.env.PORT || 3000);
}
bootstrap();Configuração, validação, logs e shutdown devem ser adicionados antes de escutar.
Module
import { Module } from '@nestjs/common';
@Module({
imports: [],
controllers: [UsersController],
providers: [UsersService, UsersRepository],
exports: [UsersService]
})
export class UsersModule {}O module declara dependências e elementos disponíveis. Exporte apenas o que outros módulos realmente usam.
Controller
import { Controller, Get, Param } from '@nestjs/common';
@Controller('users')
export class UsersController {
constructor(
private readonly usersService: UsersService
) {}
@Get(':id')
findById(@Param('id') id: string) {
return this.usersService.findById(id);
}
}Controllers cuidam do transporte HTTP e delegam a regra de negócio.
Provider
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
constructor(
private readonly usersRepository: UsersRepository
) {}
}Providers podem ser serviços, repositories, clients, factories e adapters.
Injeção por token
export const USER_REPOSITORY = Symbol('USER_REPOSITORY');
@Module({
providers: [
{
provide: USER_REPOSITORY,
useClass: PostgresUsersRepository
}
]
})
export class UsersModule {}Tokens explícitos ajudam a depender de abstrações. O artigo sobre injeção de dependência será publicado ao final desta rodada.
Request DTO
export class CreateUserDto {
name: string;
email: string;
}Tipos TypeScript desaparecem no runtime. Use validação real com class-validator, Zod ou outra biblioteca.
ValidationPipe
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true
}));whitelist remove campos desconhecidos. forbidNonWhitelisted pode rejeitá-los explicitamente, reduzindo mass assignment.
DTO com decorators
import { IsEmail, IsString, MaxLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MaxLength(100)
name: string;
@IsEmail()
email: string;
}Pipes customizados
Pipes transformam e validam parâmetros antes do controller. Use para UUID, enums e formatos específicos, sem incluir regra de domínio extensa.
Exception filters
Filters transformam exceções em respostas controladas. Não exponha stack, SQL ou mensagens internas.
throw new NotFoundException({
code: 'USER_NOT_FOUND',
message: 'Usuário não encontrado'
});Interceptor
Interceptors podem medir duração, transformar respostas, adicionar cache e observar execução.
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context, next) {
const startedAt = performance.now();
return next.handle().pipe(
finalize(() => {
logger.info({
durationMs: performance.now() - startedAt
}, 'Requisição concluída');
})
);
}
}Middleware
Middleware executa antes do route handler e pode criar request ID, carregar headers ou aplicar políticas HTTP gerais.
Guard
Guards decidem se uma rota pode prosseguir:
@Injectable()
export class AuthGuard implements CanActivate {
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
request.user = await authenticate(request);
return Boolean(request.user);
}
}Autenticação
Valide token, sessão ou certificado e coloque uma identidade mínima no request. Consulte JWT Seguro no Node.js e Sessões Seguras no Node.js.
Authorization guard
@Permissions('project.delete')
@Delete(':id')
remove(@Param('id') id: string) {}Um guard pode verificar permission geral, mas o service ainda precisa validar tenant e ownership do recurso.
Consulte RBAC no Node.js e ABAC no Node.js.
Decorator customizado
export const CurrentUser = createParamDecorator(
(_, context: ExecutionContext) => {
return context.switchToHttp().getRequest().user;
}
);Retorne uma identidade controlada, não o token completo.
Multi-tenancy
O tenant deve vir da identidade e participar das queries. Consulte Multi-Tenancy no Node.js.
Repository
@Injectable()
export class UsersRepository {
constructor(private readonly pool: Pool) {}
async findById({ tenantId, userId }) {
const result = await this.pool.query(`
SELECT id, name, email
FROM users
WHERE tenant_id = $1
AND id = $2
`, [tenantId, userId]);
return result.rows[0] ?? null;
}
}Não acesse banco no controller
Isso mistura HTTP, regra de negócio e persistência, dificultando testes e transações.
Transações
Crie um serviço transacional ou Unit of Work. Consulte Transações PostgreSQL no Node.js.
TypeORM, Prisma e outros
Nest integra com várias bibliotecas. Escolha pela necessidade do projeto, não apenas pela integração pronta. Raw SQL e queries dinâmicas continuam exigindo segurança.
ConfigModule
ConfigModule.forRoot({
isGlobal: true,
validate: validateEnvironment
})Valide configuração no startup. Segredos precisam de cofre e rotação. Consulte Gestão de Segredos no Node.js.
Módulos globais
isGlobal reduz imports, mas aumenta dependências implícitas. Use global apenas para configuração e infraestrutura realmente transversal.
Dynamic modules
Um módulo dinâmico aceita opções e providers:
StorageModule.register({
driver: 's3'
})Evite APIs de configuração complexas quando providers explícitos bastam.
Lifecycle hooks
OnModuleInit;OnApplicationBootstrap;OnModuleDestroy;BeforeApplicationShutdown;OnApplicationShutdown.
Use para abrir e fechar recursos, sem esconder trabalho demorado.
Graceful shutdown
app.enableShutdownHooks();Feche pool, filas e clients. Consulte Graceful Shutdown no Node.js.
Fastify adapter
Nest pode rodar sobre Fastify para performance e plugins diferentes. Consulte Fastify com Node.js.
Express adapter
É o padrão comum e oferece amplo ecossistema. Ajuste trust proxy, body limits, timeouts e segurança.
OpenAPI
O módulo Swagger gera documentação a partir de decorators e metadata. Revise o schema gerado e não exponha rotas administrativas indevidamente.
Consulte OpenAPI com Node.js.
GraphQL
Nest oferece integração code-first e schema-first. N+1, complexidade e autorização continuam relevantes. Consulte DataLoader no Node.js.
Microservices
Nest possui transporters para brokers e RPC. Mensagens precisam de idempotência, retry, timeout e DLQ.
Filas
Workers devem criar contexto por job e não reutilizar request-scoped providers sem necessidade.
Provider scopes
- singleton: padrão e mais eficiente;
- request: nova instância por request;
- transient: nova instância por consumidor.
Request scope aumenta custo e pode se propagar pela árvore. Use apenas quando necessário.
AsyncLocalStorage
Para request ID e metadados transversais, considere AsyncLocalStorage no Node.js em vez de tornar muitos providers request-scoped.
Circular dependency
forwardRef() resolve alguns ciclos, mas muitos ciclos indicam limites de módulo ruins. Extraia uma abstração ou reorganize o domínio.
God service
Um service com dezenas de repositories e métodos é um sinal de responsabilidades misturadas. Divida por caso de uso ou agregado.
Cache
Cache interceptors ajudam em respostas simples, mas dados privados precisam de chaves com tenant e usuário.
Rate limiting
Use proteção global e regras por rota. Consulte Rate Limiting no Node.js.
Helmet e CORS
Configure headers de segurança e allowlist de origens. Veja Helmet e CSP no Node.js e CORS em APIs Node.js.
Body limits
Defina limites para JSON e multipart. Consulte Multipart Upload no Node.js.
Logging
Substitua ou integre o logger padrão com logs estruturados. Inclua request ID, rota normalizada, status e duração.
Consulte Logs com Pino no Node.js.
Exception logging
Não registre duas vezes no filter e interceptor. Defina um ponto central e redija segredos.
Métricas
Use interceptor ou middleware para duração, contagem e erros. Monitore também pool, event loop e downstreams.
Health checks
Liveness confirma o processo. Readiness verifica capacidade essencial, sem consultar todas as dependências a cada segundo.
Testes unitários
const module = await Test.createTestingModule({
providers: [
UsersService,
{
provide: UsersRepository,
useValue: usersRepositoryMock
}
]
}).compile();Testes de controller
Mock o service e valide transporte, status e DTO. Regras de negócio pertencem ao service.
Testes de integração
Use banco real em container para repositories e transações.
Testes end-to-end
await request(app.getHttpServer())
.get('/api/users/42')
.set('Authorization', token)
.expect(200);Override de provider
O testing module permite substituir clients externos. Não transforme todos os testes em mocks que ignoram SQL e serialização reais.
Teste de autorização
Inclua sem token, permission ausente, outro tenant e recurso inexistente.
Teste de validação
Envie campos desconhecidos, strings longas, UUID inválido e tipos incorretos.
Teste de shutdown
Inicie requisição lenta, envie SIGTERM e confirme que o processo drena antes de fechar.
Deploy
Compile no CI e execute o artefato já testado. Não instale dependências ou compile em produção.
Docker
Use multi-stage e usuário não root. Consulte Docker Multi-stage para Node.js.
Erros comuns
- Controller com regra de negócio: camadas se misturam.
- Módulo global para tudo: dependências ficam invisíveis.
- Request scope indiscriminado: custo cresce.
- Guard como única autorização: recurso fica fora do escopo.
- DTO sem runtime validation: TypeScript não protege a rede.
- forwardRef em excesso: módulos estão acoplados.
- Provider gigante: responsabilidades se acumulam.
Boas práticas
- Organize por domínio.
- Mantenha controllers finos.
- Injete abstrações.
- Valide inputs globalmente.
- Autorize por recurso.
- Evite módulos globais excessivos.
- Use singleton por padrão.
- Centralize erros e logs.
- Teste providers e HTTP.
- Implemente graceful shutdown.
Conclusão
Usar NestJS no Node.js oferece convenções e ferramentas para aplicações TypeScript grandes. Modules, providers e decorators ajudam a manter uma arquitetura uniforme entre equipes.
O framework funciona melhor quando controllers permanecem finos, providers possuem responsabilidades claras e autorização chega até o recurso. Com validação, testes, observabilidade e shutdown controlado, NestJS fornece estrutura sem transformar decorators em dependências implícitas difíceis de manter.



