📚 Módulo 02: Backend - Injeção de Dependências e CRUD
Neste módulo, transformaremos o PrismaClient independente que geramos no módulo anterior em um Serviço Injetável Global dentro do ecossistema NestJS. Em seguida, faremos uso da “Nest CLI” para gerar automaticamente toda a estrutura arquitetural limpa (Module, Controller, Service) do nosso domínio de Categorias.
⚙️ 1. O Prisma como um Serviço Injetável (Global)
O NestJS baseia-se pesadamente no conceito de Injeção de Dependências (DI). Não devemos instanciar o new PrismaClient() manualmente em cada arquivo. Criaremos um serviço global que fornece o banco para quem precisar.
Gere o módulo Prisma via CLI:
npx nest g module prisma
npx nest g service prisma
src/prisma/prisma.service.ts
Estenda o PrismaClient e acione a conexão quando o módulo iniciar:
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
console.log('🐘 Conectado ao PostgreSQL via Prisma');
}
}
src/prisma/prisma.module.ts
Configure o módulo como @Global() para que ele fique disponível em toda a API sem precisar ser importado repetidamente:
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService], // Expõe o serviço para os outros módulos usarem
})
export class PrismaModule {}
🏛️ 2. Arquitetura Limpa (O Diagrama de Fluxo)
A arquitetura do NestJS impõe uma divisão estrita de responsabilidades:
- Module: A caixa que empacota tudo.
- Controller: O porteiro. Recebe o JSON HTTP, não processa regra de negócio, e envia para o serviço.
- Service: O engenheiro. Processa regras e solicita dados ao Prisma.
flowchart LR
A[Internet / Next.js] -->|HTTP GET /categorias| B(CategoriaController)
B -->|Método buscarTodos()| C{CategoriaService}
C -->|this.prisma.categoria.findMany| D[(Banco Postgres)]
D -->|Array de JSON| C
C -->|Array de JSON| B
B -->|HTTP 200 OK| A
📦 3. Criando o CRUD de Categorias
Utilize a automação da CLI para gerar os arquivos já amarrados:
npx nest g resource categoria --no-spec
# Escolha a opção: "REST API" e "Y" para CRUD entry points
O Serviço (categoria.service.ts)
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateCategoriaDto } from './dto/create-categoria.dto';
@Injectable()
export class CategoriaService {
constructor(private prisma: PrismaService) {}
async create(createCategoriaDto: CreateCategoriaDto) {
return this.prisma.categoria.create({
data: { nome: createCategoriaDto.nome },
});
}
async findAll() {
return this.prisma.categoria.findMany({
orderBy: { nome: 'asc' }
});
}
async findOne(id: number) {
const categoria = await this.prisma.categoria.findUnique({
where: { id },
});
if (!categoria) throw new NotFoundException('Categoria não encontrada');
return categoria;
}
}
O Controlador (categoria.controller.ts)
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { CategoriaService } from './categoria.service';
import { CreateCategoriaDto } from './dto/create-categoria.dto';
@Controller('categorias')
export class CategoriaController {
constructor(private readonly categoriaService: CategoriaService) {}
@Post()
create(@Body() createCategoriaDto: CreateCategoriaDto) {
return this.categoriaService.create(createCategoriaDto);
}
@Get()
findAll() {
return this.categoriaService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.categoriaService.findOne(+id);
}
}
✅ Pré-Requisitos deste Módulo
Antes de passar para a modelagem do catálogo de produtos e validações por DTO, certifique-se de que:
- As queries de banco de dados foram versionadas e o banco de dados semente populado no Módulo 01.
- A CLI global do NestJS está instalada localmente no ambiente de desenvolvimento.
🤔 Por que fizemos assim?
- Por que declarar o
PrismaModulecom o decorator@Global()? Evita a prolixidade do código (boilerplate). Praticamente todos os domínios da API (como Categorias, Produtos, Usuários e Pedidos) necessitam disparar chamadas ao banco de dados relacional. Anotar o módulo do Prisma como Global disponibiliza a instância injetável doPrismaServiceem qualquer controlador ou serviço de forma automática, dispensando a necessidade de importar o módulo repetidamente em cada nova pasta criada. - Por que implementar a interface
OnModuleInitnoPrismaService? Assegura a prontidão de rede da API. O método de ciclo de vidaonModuleInit()é disparado pelo NestJS logo após a inicialização das dependências. Chamarawait this.$connect()nesse hook obriga o client do Prisma a realizar o handshake físico de conexão com o banco de dados (como o Neon) de forma antecipada, evitando delays e latências no primeiro acesso do usuário às rotas. - Por que dividir estritamente a arquitetura de CRUD em Controllers e Services? Segue o princípio de responsabilidade única (SRP - Single Responsibility Principle). A camada de Controle (
Controller) gerencia as rotas web, lê parâmetros e retorna serializações HTTP. A camada de Serviço (Service) executa a inteligência de negócios, consistência de dados e as queries SQL. Esta separação permite testar a lógica operacional do serviço isoladamente em testes unitários simples, sem a necessidade de simular requisições de rede.
🔍 Checkpoint
- Recurso Gerado: Valide se a pasta
src/categoriafoi populada com a estrutura lógica de controlador, serviço e classes DTO. - Handshake Confirmado: Inicialize a aplicação localmente e certifique-se de que a mensagem
🐘 Conectado ao PostgreSQL via Prismaé exibida no console de debug do terminal. - Rota de Listagem Operante: Dispare uma consulta do tipo GET na rota
http://localhost:3000/categoriase confirme se o banco de dados responde listando as categorias didáticas criadas no seeder.
⚠️ Erros Comuns
| Erro | Causa | Solução |
|---|---|---|
Exceção Nest cannot resolve dependencies of CategoriaService ao subir a API |
O PrismaService não foi exportado no array do PrismaModule ou o módulo do banco não foi importado no módulo raiz. |
Certifique-se de incluir PrismaService na propriedade exports de prisma.module.ts e de declarar o PrismaModule na lista de imports de app.module.ts. |
| O NestJS aceita IDs inválidos e lança erros internos 500 no console | A rota /categorias/:id recebeu uma string inválida e o serviço falhou ao tentar forçar a coerção direta sem validação. |
Garanta o uso do parse numérico na passagem de parâmetros de controladores: use o operador unário +id ou o transformador Number(id) nas assinaturas de rotas. |
| O banco falha ao consultar registros por estarem nulos, mas não sinaliza de forma amigável ao cliente | Falta de tratamento de resultado nulo (null) no fluxo do banco de dados na camada de serviço. |
Sempre verifique o resultado retornado pelo Prisma. Caso seja nulo, lance explicitamente a exceção new NotFoundException('Categoria não encontrada') para unificar as respostas em JSON. |
🏁 Conclusão
O seu primeiro módulo (Categoria) foi concluído e demonstra quão verboso, limpo e direto o NestJS e o Prisma são em harmonia. No Módulo 03 implementaremos o Catálogo de Produtos e integraremos um dos conceitos de segurança mais importantes do NestJS: Data Transfer Objects (DTOs) blindados com Validation Pipes.