Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

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.

Rodolfo Mori5 min de leitura

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:

bash
npx nest generate module mentorias
npx nest generate controller mentorias --no-spec
npx nest generate service mentorias --no-spec
CREATE src/mentorias/mentorias.module.ts UPDATE src/app.module.ts CREATE src/mentorias/mentorias.controller.ts UPDATE src/mentorias/mentorias.module.ts CREATE src/mentorias/mentorias.service.ts UPDATE src/mentorias/mentorias.module.ts

O gerador não inventou a arquitetura; ele aplicou a convenção e atualizou os registros. Confira a pasta:

text
src/
├── app.module.ts
└── mentorias/
    ├── mentorias.controller.ts
    ├── mentorias.module.ts
    └── mentorias.service.ts

Manter 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.

ts
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:

ts
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:

ts
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:

ts
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:

bash
npm run build
npm run start
[InstanceLoader] MentoriasModule dependencies initialized [RoutesResolver] MentoriasController {/mentorias} [RouterExplorer] Mapped {/mentorias, GET} route [RouterExplorer] Mapped {/mentorias, POST} route [NestApplication] Nest application successfully started

O build comprova os tipos; os logs comprovam o registro em runtime. Em outro terminal, consulte a lista:

bash
curl -s http://localhost:3000/mentorias
[{"id":1,"titulo":"TypeScript sem medo","vagas":12}]

Agora crie uma mentoria e consulte novamente:

bash
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/mentorias
{"id":2,"titulo":"APIs que não quebram","vagas":8} [{"id":1,"titulo":"TypeScript sem medo","vagas":12},{"id":2,"titulo":"APIs que não quebram","vagas":8}]

O 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:

diff
 @Module({
   controllers: [MentoriasController],
-  providers: [MentoriasService],
+  providers: [],
 })

Ao iniciar, o laboratório produziu:

UnknownDependenciesException: Nest can't resolve dependencies of the MentoriasController (?). Please make sure that the argument MentoriasService at index [0] is available in the MentoriasModule module.

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.

ts
@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:

ts
@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.

bash
curl -i http://localhost:3000/mentorias/1
curl -i http://localhost:3000/mentorias/999

O 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.

  • nestjs
  • modulos
  • controllers
  • providers
  • injecao de dependencia
  • typescript

Perguntas frequentes

Qual a diferença entre controller e provider no NestJS?
O controller traduz requisições HTTP para chamadas da aplicação. O provider executa uma responsabilidade reutilizável, como regra de negócio, acesso a dados ou integração externa.
Todo provider precisa ser um service?
Não. Service é a convenção mais comum, mas repositórios, factories e clientes externos também podem ser providers quando são registrados no contêiner de injeção.
Para que serve exports em um módulo?
exports torna providers selecionados parte da interface pública do módulo. Outro módulo só consegue injetá-los quando importa o módulo que os exporta.
Module do NestJS é igual a módulo ES do TypeScript?
Não. import e export do TypeScript organizam arquivos. Uma classe com @Module descreve ao Nest o grafo de controllers, providers e outros módulos.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

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

  1. NestJS — módulos — docs.nestjs.com
  2. NestJS — controllers — docs.nestjs.com
  3. NestJS — providers — docs.nestjs.com
  4. NestJS — erros comuns — docs.nestjs.com

Continue por aqui