Guia completo
NestJS: guia completo para criar APIs organizadas
Entenda módulos, controllers, providers, DTOs, pipes, guards e Prisma no NestJS 11, com uma API executada de verdade e uma ordem clara de estudo.
NestJS é um framework de back-end para criar APIs e outras aplicações de servidor em Node.js com uma estrutura explícita. Em vez de cada projeto inventar onde ficam rotas, regras e dependências, o Nest oferece módulos, controllers, providers, pipes e guards que trabalham juntos.
Neste guia, a gente vai montar o mapa completo do NestJS 11.2.1 por meio de uma API de mentorias. Você verá onde cada peça entra, quais problemas ela resolve e como uma requisição atravessa a aplicação até chegar ao PostgreSQL. Se Node ainda é novidade, vale passar primeiro pelo guia de Node.js.
Uma central de atendimento com setores bem definidos
Imagine a central de atendimento de uma escola. A recepção entende o pedido do aluno, identifica o setor certo e devolve uma resposta. O setor de matrículas conhece as regras de matrícula. Antes de alguém acessar uma ficha reservada, uma pessoa confere a credencial. Os dados permanentes ficam no arquivo da escola.
No mapa técnico, o controller é a recepção: recebe a requisição HTTP e escolhe qual operação chamar. O provider, normalmente um service, é o setor que executa a regra. O module declara quais equipes pertencem àquela área. Um pipe confere e transforma os dados de entrada. Um guard decide se a requisição pode prosseguir. Prisma conversa com o banco que faz o papel do arquivo permanente.
A comparação termina aí. O Nest não tem pessoas passando papéis nem obriga cada requisição a percorrer todas as peças. Ele constrói um grafo de dependências ao iniciar a aplicação e chama funções JavaScript conforme decorators e metadados. O modelo mental ajuda a separar responsabilidades; o comportamento real continua sendo código executado pelo Node.
O que o Nest acrescenta ao Node e ao Express
Node.js fornece o runtime: ele executa JavaScript ou TypeScript compilado e oferece recursos de rede, arquivos e processos. Express e Fastify são plataformas HTTP: recebem conexões, combinam método e endereço e produzem respostas. Nest fica numa camada acima, criando uma arquitetura consistente sem esconder essas bases.
No projeto padrão, @nestjs/platform-express é instalado automaticamente. A prova aparece no cabeçalho da primeira resposta testada:
curl -i http://localhost:3000/Hello World!
O cabeçalho mostra Express, mas a rota foi declarada num controller do Nest. Essa separação explica por que você encontra termos dos dois mundos num erro ou plugin. Fastify também é suportado, porém não é uma troca que a gente faz só por moda: meça o gargalo e confira a compatibilidade das integrações antes.
A aplicação nasce pelo módulo raiz
O arquivo main.ts é o ponto de entrada. NestFactory.create(AppModule) lê o módulo raiz, monta o contêiner de injeção de dependências e devolve a aplicação.
Só depois listen abre a porta HTTP.
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
}
void bootstrap();O termo técnico bootstrap significa inicialização. É como abrir a escola de manhã: ligar os sistemas, reunir os setores declarados e só então destrancar a recepção. Tecnicamente, o Nest percorre os módulos, instancia providers, resolve dependências e registra rotas antes de começar a aceitar tráfego.
Ao executar esse arquivo no laboratório, as últimas linhas confirmaram a rota e o servidor:
Uma aplicação tem pelo menos um módulo raiz. Conforme cresce, ele importa módulos de funcionalidade, como MentoriasModule, AuthModule e PrismaModule. A
sequência detalhada de instalação está em primeiro projeto NestJS.
Decorators são etiquetas que o framework consegue ler
@Module(), @Controller(), @Injectable() e @Get() são decorators do TypeScript. Em português simples, são marcações colocadas em classes ou métodos
para anexar metadados. O Nest lê esses metadados e decide como registrar cada peça.
import { Controller, Get } from "@nestjs/common";
@Controller("mentorias")
export class MentoriasController {
@Get()
listar() {
return [{ id: 1, titulo: "TypeScript sem medo" }];
}
}@Controller('mentorias') define o prefixo /mentorias. @Get() associa o método listar ao verbo GET nesse endereço. O decorator não executa a regra de
negócio sozinho; ele registra informações que o roteador usa durante o bootstrap.
Uma etiqueta numa pasta de arquivo diz “matrículas”, mas não preenche a ficha. Da mesma forma, o decorator identifica a função; o corpo do método ainda precisa produzir o resultado correto. Essa distinção ajuda a depurar: rota não mapeada aponta para metadados ou módulo; resposta errada aponta para a operação chamada.
Módulo, controller e provider formam o primeiro trio
Um module delimita uma área da aplicação e declara suas peças. Um controller traduz HTTP para chamadas de aplicação. Um provider é algo que o contêiner do Nest sabe criar e injetar; services, repositórios e clientes de API costumam ser providers.
import { Module } from "@nestjs/common";
import { MentoriasController } from "./mentorias.controller";
import { MentoriasService } from "./mentorias.service";
@Module({
controllers: [MentoriasController],
providers: [MentoriasService],
})
export class MentoriasModule {}import { Injectable } from "@nestjs/common";
@Injectable()
export class MentoriasService {
listar() {
return [{ id: 1, titulo: "TypeScript sem medo", vagas: 12 }];
}
}import { Controller, Get } from "@nestjs/common";
import { MentoriasService } from "./mentorias.service";
@Controller("mentorias")
export class MentoriasController {
constructor(private readonly mentoriasService: MentoriasService) {}
@Get()
listar() {
return this.mentoriasService.listar();
}
}O constructor não cria o service com new. Ele pede uma dependência pelo tipo, e o contêiner entrega a instância registrada em providers. Esse mecanismo se
chama injeção de dependência: a classe declara do que precisa, enquanto a composição da aplicação decide como fornecer.
No teste, GET /mentorias devolveu:
Se MentoriasService sair de providers, o Nest interrompe o bootstrap. A mensagem real é longa porque já oferece as hipóteses úteis:
Leia esse padrão como um mapa: qual classe precisava da dependência, qual token faltou e em qual módulo o Nest procurou. A lição de módulos, controllers e providers monta o trio do zero e reproduz esse erro de propósito.
DTO e pipe protegem a fronteira da API
O TypeScript verifica tipos enquanto o projeto é compilado, mas quem chama sua API envia bytes pela rede. Um cliente pode mandar "8", 0, null ou um campo
que nem existe. Por isso, tipo estático não valida entrada em runtime.
Um DTO, sigla de Data Transfer Object, descreve o formato esperado na fronteira. Um ValidationPipe transforma e valida o objeto recebido antes de o controller usá-lo.
import { Type } from "class-transformer";
import { IsInt, IsString, Max, Min, MinLength } from "class-validator";
export class CreateMentoriaDto {
@IsString()
@MinLength(5)
titulo!: string;
@Type(() => Number)
@IsInt()
@Min(1)
@Max(20)
vagas!: number;
}app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);Pense num formulário recebido no balcão. O DTO é o modelo de ficha aceito; o pipe é a conferência que recusa campo estranho, verifica regras e converte um
número escrito como texto quando isso foi configurado. No runtime, decorators do class-validator descrevem as regras e class-transformer realiza a
conversão.
Uma entrada inválida foi enviada no laboratório:
curl -X POST http://localhost:3000/mentorias \
-H 'Content-Type: application/json' \
-d '{"titulo":"API","vagas":0,"admin":true}'O controller nem precisou criar três ifs. Isso não elimina validações de negócio, como impedir duas mentorias no mesmo horário; o pipe cuida do contrato de
entrada. A separação completa está em DTO e validação no NestJS.
Guard decide se a rota pode continuar
Autenticação responde “quem fez a requisição?”. Autorização responde “essa identidade pode fazer esta ação?”. Um guard entra antes do controller e retorna ou produz a decisão de acesso a partir do contexto da execução.
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization?.split(" ")[1];
if (!token) {
throw new UnauthorizedException("token ausente ou inválido");
}
request.user = await this.jwtService.verifyAsync(token);
return true;
}
}O modelo cotidiano é a catraca de um evento. O ingresso identifica a sessão, e a catraca decide se abre; ela não apresenta a palestra. No Nest, o token é verificado pelo guard, e o controller só executa depois da decisão. O limite é importante: JWT assinado não prova sozinho que uma conta continua ativa nem que tem permissão para qualquer recurso.
Sem cabeçalho Authorization, a execução testada devolveu:
Segredos não entram no código nem no artigo. O laboratório gerou JWT_SECRET com openssl rand -hex 32 e manteve o valor apenas no processo. Em produção, use
um gerenciador de segredos, senha com hash e autorização por papéis ou recursos. O passo a passo está em
guards e autenticação no NestJS.
Prisma entra como provider, não como variável espalhada
Banco é uma dependência da aplicação. No Nest, faz sentido encapsular a conexão num provider com ciclo de vida definido. No Prisma 7, o PrismaClient exige um
driver adapter; no PostgreSQL, usamos @prisma/adapter-pg.
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleDestroy {
constructor() {
const connectionString = process.env.DATABASE_URL;
if (!connectionString) throw new Error("DATABASE_URL não definida");
super({ adapter: new PrismaPg({ connectionString }) });
}
async onModuleDestroy() {
await this.$disconnect();
}
}Pense num único terminal autorizado a consultar o arquivo da escola. Os setores pedem operações por esse terminal em vez de cada um instalar uma conexão
improvisada. Tecnicamente, PrismaService é injetado nos services, o adapter conecta o Client ao driver pg e PostgreSQL mantém as tabelas.
Sem adapter, o próprio Prisma 7.9.1 interrompe a criação do Client:
Com o provider configurado, dois POST /mentorias e um SELECT direto no banco confirmaram a persistência:
SELECT id, titulo, vagas FROM mentorias ORDER BY id;O guia de Prisma explica schema e migrations; o guia de PostgreSQL mostra o que o banco continua garantindo. Para juntar as ferramentas dentro do framework, siga Prisma com NestJS.
Como uma requisição percorre a aplicação
Quando chega POST /mentorias, o caminho não é “mágica do decorator”. Em alto nível, o adapter HTTP recebe a conexão; middleware pode preparar a requisição;
guards decidem acesso; pipes validam parâmetros e corpo; o controller chama o provider; interceptors podem envolver a execução; exception filters formatam erros
não tratados.
cliente HTTP
-> adapter Express
-> middleware
-> guard
-> pipe
-> controller
-> service/provider
-> PrismaService
-> PostgreSQL
<- resposta HTTPEsse desenho é uma ferramenta de diagnóstico. Um 401 antes de qualquer log do controller direciona a investigação ao guard. Um 400 com mensagens de campo aponta
para pipe e DTO. Um P1001 do Prisma aponta para conexão com banco. Um resultado incorreto com status 200 normalmente pede revisão da regra no service.
Interceptors e exception filters têm posições específicas no ciclo completo, inclusive no caminho de volta. O diagrama acima mostra só as peças usadas pelo nosso exemplo; não conclua que todo projeto precisa ativar todas elas.
Tabela de decisão: onde colocar cada responsabilidade
Uma regra prática para revisar uma API Nest: escolha a camada pela pergunta que o código responde. A tabela não vem do gerador; ela é uma decisão de organização para reduzir controllers enormes e services que fazem tudo.
| Se o código responde… | Coloque primeiro em… | Sinal de que está no lugar errado |
|---|---|---|
| Qual rota e método HTTP chegaram? | controller | regra de preço misturada com @Body() |
| O formato externo é aceitável? | DTO + pipe | if de tipo repetido em vários controllers |
| A identidade pode prosseguir? | guard | cada rota decodifica JWT por conta própria |
| Qual é a regra do caso de uso? | service/provider | controller conhece detalhes do banco |
| Como os dados são persistidos? | provider de acesso a dados | new PrismaClient() espalhado pelo projeto |
| Quais peças pertencem à funcionalidade? | module | tudo registrado apenas no AppModule |
Há exceções. Uma aplicação pequena não precisa de uma camada para cada verbo, e uma validação que consulta o banco pode pertencer ao caso de uso, não ao DTO. O critério é manter a fronteira clara: HTTP de um lado, regra do produto no meio e infraestrutura do outro.
O mapa do cluster NestJS
Você não precisa aprender todas as abstrações de uma vez. A ordem abaixo mantém uma aplicação funcionando a cada etapa:
- Primeiro projeto: instalar a versão estável, ler o scaffold e confirmar
Hello World!com Express padrão. - Módulos, controllers e providers: criar
MentoriasModule, expor GET e POST e entender injeção de dependência. - DTO e validação: proteger o corpo da requisição com regras observáveis e resposta 400.
- Guards e autenticação: emitir JWT, proteger uma rota e diferenciar 401 de autorização.
- Prisma e PostgreSQL: persistir mentorias com adapter, migration e consulta direta ao banco.
O pillar dá o mapa; cada lição entrega uma prática fechada. Ao final, você terá uma API pequena, mas com as mesmas fronteiras que aparecem em serviços maiores.
Para aplicar a sequência num caso completo, siga o tutorial
API NestJS: CRUD de tarefas com Prisma e PostgreSQL.
Ele conecta DTO, service, PrismaService, migration, respostas HTTP e smoke test.
Quando NestJS vale a escolha
Nest costuma valer o custo quando a API terá vários domínios, mais de uma pessoa no time, autenticação, banco, filas, testes ou integrações que precisam de uma forma compartilhada de organização. A estrutura reduz discussões repetidas e o contêiner facilita trocar dependências em testes.
Para um webhook de vinte linhas ou uma função temporária, o framework pode ser mais estrutura do que benefício. Para uma equipe que já domina um framework menor e mantém bons limites, migrar sem problema concreto apenas troca um conjunto de convenções por outro. Nest é ferramenta, não certificado de arquitetura.
Também não escolha por um benchmark isolado. Express é o adapter padrão e Fastify é opcional; banco, chamadas externas e desenho da aplicação frequentemente custam mais do que o roteador. Primeiro meça uma rota representativa, depois decida.
Do laboratório para uma API que outra pessoa mantém
Uma arquitetura organizada não termina quando curl responde. Ela precisa continuar compreensível quando alguém altera uma regra, uma dependência falha ou uma
nova versão vai para produção. O Nest oferece lugares para essas decisões, mas o gerador não escolhe a política por você.
Comece por configuração. Porta, conexão do banco e chave JWT pertencem ao ambiente; não entram como literais no repositório. Valide variáveis obrigatórias
durante o bootstrap. Se DATABASE_URL estiver ausente, é melhor o processo encerrar com uma mensagem direta do que aceitar tráfego e falhar apenas no primeiro
cadastro. O mesmo vale para a chave que assina tokens.
Depois pense em testabilidade. Injeção de dependência permite montar um controller com um provider falso e observar somente a tradução HTTP. Também permite testar um service com um repositório controlado, sem depender de uma rede instável. O objetivo não é substituir todo objeto por mock; é escolher o limite do teste. Regras puras pedem testes rápidos, integração com Prisma pede banco de teste e o caminho HTTP completo pede teste de ponta a ponta.
No laboratório, o projeto compilado manteve o teste inicial passando:
npm test -- --runInBandUma linha verde não prova a aplicação inteira. Ela prova apenas os cenários que foram escritos. Para a agenda, uma cobertura útil incluiria DTO inválido, token ausente, criação válida, título duplicado conforme a regra do produto e consulta após reiniciar o processo. Nomeie o comportamento esperado; evite testes que só confirmam que um método foi chamado sem verificar o efeito.
A terceira frente é observabilidade. Logs devem informar rota, duração, categoria do erro e um identificador de correlação, sem registrar senha, token ou corpo sensível. Um 400 esperado não precisa parecer queda do servidor. Um erro de conexão com PostgreSQL precisa conservar o código que ajuda a investigar, mas a resposta pública não deve devolver stack trace ou endereço interno.
Por fim, defina o encerramento. Quando o processo recebe um sinal de parada, ele deve deixar de aceitar trabalho novo, concluir o que for seguro e liberar
recursos. Providers podem participar dos hooks de ciclo de vida; o PrismaService deste cluster usa OnModuleDestroy para desconectar. Em filas ou transações
longas, a política exige mais cuidado do que chamar $disconnect().
Pense nessa passagem como entregar uma loja para a equipe do turno seguinte. Não basta a porta abrir: chaves precisam estar no lugar certo, caixa precisa ser conferido, ocorrências precisam ficar registradas e o fechamento precisa seguir uma ordem. No mapa técnico, configuração, testes, logs e lifecycle cumprem essas funções. O limite da analogia é que disponibilidade e concorrência são medidas do sistema; por isso, decisões finais devem vir de testes e telemetria, não da imagem da loja.
Como ler erros do Nest sem tentar adivinhar
Erros do framework quase sempre entregam três coordenadas: a peça que estava sendo criada, a dependência ou rota envolvida e o contexto do módulo. Comece por essas três informações antes de alterar imports ao acaso.
Se aparecer Nest can't resolve dependencies, confira providers, exports e imports. Se aparecer EADDRINUSE, a arquitetura já subiu até tentar abrir uma
porta ocupada; encerre o processo antigo ou use outra porta. Se a API retorna 400, leia a lista produzida pelo pipe. Se retorna 401, confirme o formato
Bearer TOKEN e a mesma chave de assinatura. Se Prisma mostra P1001, verifique host, porta, container e DATABASE_URL sem imprimir a senha.
PORT=3001 npm run start:devTrocar a porta resolve EADDRINUSE; não resolve controller ausente. Uma boa correção responde diretamente à coordenada indicada pelo erro.
Missão: desenhe antes de aumentar a API
Pegue uma funcionalidade conhecida, como agendamento de consultas, pedidos de uma lanchonete ou empréstimo de livros. Escreva em uma folha cinco nomes: module, controller, service, DTO e guard. Ao lado, coloque uma responsabilidade real de cada peça e desenhe as setas da requisição.
Depois implemente apenas GET /itens e POST /itens em memória. O critério de sucesso é objetivo: o build termina sem erro, o log mostra as duas rotas
mapeadas, o POST válido responde 201, o campo extra produz 400 e uma rota protegida produz 401 sem token. Só então conecte o banco.
Essa missão força a pergunta mais importante do NestJS: não “qual decorator eu copio?”, mas “qual peça deveria conhecer esta decisão?”. Quando essa resposta fica clara, o framework deixa de parecer uma coleção de arquivos e vira um mapa para evoluir a aplicação.
Trilha
Node e APIs
JavaScript no servidor: rotas, banco de dados, autenticação e a API que o front consome.
nestjs
API NestJS: CRUD de tarefas com Prisma e PostgreSQL
Perguntas frequentes
O que é NestJS?
Preciso aprender Express antes de NestJS?
NestJS é front-end ou back-end?
NestJS 11 exige qual versão do Node?
Devo escolher Express ou Fastify no NestJS?
Posso usar Prisma com NestJS?
O código deste guia foi executado em Node 24.16.0, NestJS 11.2.1, Nest CLI 11.0.24, TypeScript 5.9.3, Prisma 7.9.1 e PostgreSQL 18.6, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- NestJS — documentação oficial — docs.nestjs.com
- NestJS — primeiros passos e plataformas HTTP — docs.nestjs.com
- NestJS — módulos — docs.nestjs.com
- NestJS — providers — docs.nestjs.com
- NestJS — ciclo da requisição — docs.nestjs.com
- NestJS — testes — docs.nestjs.com
- NestJS — receita oficial com Prisma — docs.nestjs.com
