Módulos, controllers e providers no NestJS
Organize uma API NestJS em módulos, controllers e providers, entenda injeção de dependência e corrija o erro de provider não encontrado.
Módulos, controllers e providers são o trio que organiza uma aplicação NestJS. Nesta lição, você vai criar uma funcionalidade de mentorias, expor rotas GET e POST e enxergar exatamente como a injeção de dependência liga as três peças.
Partimos do projeto criado na lição de
primeiro projeto NestJS. Mantenha o
servidor parado enquanto gera os arquivos; depois a gente comprova cada rota com
curl.
Uma oficina com balcão, mecânicos e área de serviço
Imagine uma oficina. O balcão recebe o pedido do cliente e encaminha o trabalho. O mecânico conhece o serviço e executa a tarefa. A área de motos reúne equipe, ferramentas e ordens ligadas a motos, sem misturá-las com o setor de carros.
No Nest, o controller é o balcão HTTP, o provider é a peça que sabe executar a operação e o module agrupa uma funcionalidade. A injeção de dependência é o mecanismo pelo qual o Nest entrega o provider pedido pelo controller.
A oficina é só o mapa de responsabilidades. No comportamento técnico, o Nest lê
metadados de @Module, cria instâncias no contêiner e resolve tokens pelos tipos
do constructor. Não existe encaminhamento físico; existe uma chamada de método
entre objetos já ligados na inicialização.
Gere as três peças com o CLI
Dentro de agenda-mentorias, execute:
npx nest generate module mentorias
npx nest generate controller mentorias --no-spec
npx nest generate service mentorias --no-specO gerador não inventou a arquitetura; ele aplicou a convenção e atualizou os registros. Confira a pasta:
src/
├── app.module.ts
└── mentorias/
├── mentorias.controller.ts
├── mentorias.module.ts
└── mentorias.service.tsManter arquivos do mesmo domínio próximos facilita encontrar uma mudança. Isso é chamado de feature module: um módulo centrado numa capacidade do produto, não num tipo genérico de arquivo espalhado pelo projeto.
O provider guarda a regra por enquanto
Comece com dados em memória para isolar a arquitetura. O banco entra numa lição posterior.
import { Injectable } from '@nestjs/common';
@Injectable()
export class MentoriasService {
private readonly mentorias = [
{ id: 1, titulo: 'TypeScript sem medo', vagas: 12 },
];
listar() {
return this.mentorias;
}
criar(titulo: string, vagas: number) {
const mentoria = {
id: this.mentorias.length + 1,
titulo,
vagas,
};
this.mentorias.push(mentoria);
return mentoria;
}
}@Injectable() marca a classe como provider gerenciado pelo contêiner. O nome
não quer dizer que ela injeta algo; quer dizer que pode participar da injeção.
Aqui ela mantém a lista e sabe listar ou criar uma mentoria.
Esse armazenamento desaparece quando o processo reinicia e o ID baseado no tamanho não é seguro para concorrência. Ele serve para observar a divisão das camadas. Em produção, persistência e geração de chave pertencem ao banco.
O controller traduz HTTP para chamadas do service
Agora ligue endereço e verbo aos métodos:
import { Body, Controller, Get, Post } from '@nestjs/common';
import { MentoriasService } from './mentorias.service';
@Controller('mentorias')
export class MentoriasController {
constructor(private readonly mentoriasService: MentoriasService) {}
@Get()
listar() {
return this.mentoriasService.listar();
}
@Post()
criar(@Body() body: { titulo: string; vagas: number }) {
return this.mentoriasService.criar(body.titulo, body.vagas);
}
}@Controller('mentorias') cria o prefixo. @Get() e @Post() registram métodos
HTTP. @Body() extrai o corpo JSON. O TypeScript descreve o objeto durante a
compilação, mas ainda não valida o conteúdo enviado pela rede; esse limite será
resolvido em DTO e validação.
O constructor declara a dependência. Você não escreveu
new MentoriasService(), então o controller não decide como montar o service.
Isso permite substituir o provider em testes e centraliza seu ciclo de vida.
O módulo registra quem pertence à funcionalidade
O CLI deve ter deixado o módulo desta forma:
import { Module } from '@nestjs/common';
import { MentoriasController } from './mentorias.controller';
import { MentoriasService } from './mentorias.service';
@Module({
controllers: [MentoriasController],
providers: [MentoriasService],
})
export class MentoriasModule {}As quatro listas possíveis de @Module respondem perguntas diferentes:
controllers: quais entradas HTTP este módulo oferece;providers: quais dependências o contêiner pode criar aqui;imports: quais módulos externos oferecem dependências necessárias;exports: quais providers daqui podem ser usados por outros módulos.
Por padrão, o provider fica encapsulado. Isso evita transformar toda classe em uma variável global disponível em qualquer lugar.
AppModule importa a funcionalidade
O módulo raiz precisa conhecer MentoriasModule:
import { Module } from '@nestjs/common';
import { MentoriasModule } from './mentorias/mentorias.module';
@Module({
imports: [MentoriasModule],
})
export class AppModule {}Repare em dois tipos de import. O import do TypeScript torna a classe visível
no arquivo. A lista imports do decorator conecta os módulos no grafo interno
do Nest. Ter apenas um dos dois não completa a ligação.
Veja as rotas surgirem no bootstrap
Compile e inicie:
npm run build
npm run startO build comprova os tipos; os logs comprovam o registro em runtime. Em outro terminal, consulte a lista:
curl -s http://localhost:3000/mentoriasAgora crie uma mentoria e consulte novamente:
curl -s -X POST http://localhost:3000/mentorias \
-H 'Content-Type: application/json' \
-d '{"titulo":"APIs que não quebram","vagas":8}'
curl -s http://localhost:3000/mentoriasO Nest devolve 201 no POST por padrão. O controller entendeu HTTP; o service alterou a lista; a resposta foi serializada em JSON pelo adapter.
Erro real: o provider sumiu do contexto do módulo
Para enxergar a regra, remova temporariamente o service de providers:
@Module({
controllers: [MentoriasController],
- providers: [MentoriasService],
+ providers: [],
})Ao iniciar, o laboratório produziu:
Potential solutions:
- Is MentoriasModule a valid NestJS module?
- If MentoriasService is a provider, is it part of the current MentoriasModule?
- If MentoriasService is exported from a separate @Module, is that module imported within MentoriasModule?
Leia de dentro para fora. MentoriasController pediu algo na posição zero do
constructor. O token era MentoriasService. O contexto consultado era
MentoriasModule. Portanto, registre o provider ali ou importe um módulo que o
exporta.
@Module({
controllers: [MentoriasController],
providers: [MentoriasService],
})
export class MentoriasModule {}Após restaurar essa linha, o mesmo projeto compilou e iniciou. O erro não pede
que você adicione @Injectable() aleatoriamente; ele pede que confirme o caminho
do token no grafo de módulos.
Quando usar exports
Imagine que um futuro RelatoriosModule precise consultar mentorias pelo mesmo
service. MentoriasModule pode tornar esse provider público:
@Module({
controllers: [MentoriasController],
providers: [MentoriasService],
exports: [MentoriasService],
})
export class MentoriasModule {}Então RelatoriosModule importa MentoriasModule. Não registre novamente o
service em cada módulo só para silenciar o erro: isso pode criar instâncias e
estados separados. Exporte apenas o que faz parte da interface pública da
funcionalidade.
Missão: adicione busca por ID sem misturar camadas
Crie no service um método buscarPorId(id: number). No controller, registre
GET /mentorias/:id, leia @Param('id', ParseIntPipe) id: number e delegue ao
service. Se o item não existir, lance NotFoundException no service.
curl -i http://localhost:3000/mentorias/1
curl -i http://localhost:3000/mentorias/999O critério de sucesso é: o primeiro comando responde 200 com a mentoria; o
segundo responde 404; o controller não percorre arrays e o service não conhece
Request nem Response. Esse limite mostra que você entendeu o trio além dos
nomes dos arquivos.
Use o guia de NestJS para revisar o mapa. Na próxima etapa, a gente protege o POST contra corpo incompleto, número inválido e campo extra.
Perguntas frequentes
Qual a diferença entre controller e provider no NestJS?
Todo provider precisa ser um service?
Para que serve exports em um módulo?
Module do NestJS é igual a módulo ES do TypeScript?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 24.16.0, NestJS 11.2.1, Nest CLI 11.0.24 e TypeScript 5.9.3, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- NestJS — módulos — docs.nestjs.com
- NestJS — controllers — docs.nestjs.com
- NestJS — providers — docs.nestjs.com
- NestJS — erros comuns — docs.nestjs.com


