📚 Módulo 01: Backend - Modelagem de Dados com Prisma ORM
Neste módulo, daremos início à construção da API do nosso e-commerce inicializando o NestJS e configurando a nossa camada de dados declarativa utilizando o Prisma ORM.
O Prisma revolucionou a comunicação com bancos de dados no ecossistema TypeScript ao abolir as classes de entidade prolixas (como as do TypeORM ou Hibernate) em favor de um arquivo descritivo .prisma.
🗄️ 1. O Diagrama de Entidade Relacionamento (ER)
A base da TecLoja exige as seguintes entidades: Categorias de produtos, Catálogo de Produtos, Clientes cadastrados, Pedidos faturados e os Itens detalhados desses pedidos (mantendo o histórico do preço congelado).
erDiagram
CATEGORIA ||--o{ PRODUTO : possui
PRODUTO ||--o{ ITEM_PEDIDO : compoe
PEDIDO ||--|{ ITEM_PEDIDO : contem
USUARIO ||--o{ PEDIDO : realiza
CATEGORIA {
Int id PK
String nome
}
PRODUTO {
Int id PK
String nome
Float preco
Int estoque
Int categoriaId FK
}
USUARIO {
Int id PK
String email
String senha
String role
}
PEDIDO {
Int id PK
Float valorTotal
String status
Int usuarioId FK
}
ITEM_PEDIDO {
Int id PK
Int quantidade
Float precoUnitario
Int pedidoId FK
Int produtoId FK
}
🛠️ 2. Inicialização do Projeto e Configuração do Prisma
Inicie o repositório backend em seu terminal:
# 1. Cria a base limpa do NestJS
npx @nestjs/cli new tecloja-backend
# 2. Entra no diretório
cd tecloja-backend
# 3. Instala a CLI do Prisma e inicializa as pastas
npm install prisma --save-dev
npx prisma init
Ao rodar o init, um arquivo .env foi gerado. Modifique-o para apontar para o seu banco Neon PostgreSQL (ou Docker local):
DATABASE_URL="postgresql://usuario:senha@ep-wispy-water-1234.us-east-2.aws.neon.tech/neondb?sslmode=require"
📝 3. Modelagem Física (schema.prisma)
Abra o arquivo prisma/schema.prisma e modele as tabelas traduzindo fielmente o diagrama ER acima:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Categoria {
id Int @id @default(autoincrement())
nome String @unique
produtos Produto[]
@@map("categorias")
}
model Produto {
id Int @id @default(autoincrement())
nome String
descricao String? @db.Text
preco Decimal @db.Decimal(10, 2)
estoque Int @default(0)
categoriaId Int
categoria Categoria @relation(fields: [categoriaId], references: [id])
itensPedido ItemPedido[]
@@map("produtos")
}
model Usuario {
id Int @id @default(autoincrement())
nome String
email String @unique
senha String
role String @default("USER") // 'USER' ou 'ADMIN'
pedidos Pedido[]
@@map("usuarios")
}
model Pedido {
id Int @id @default(autoincrement())
dataCriacao DateTime @default(now())
status String @default("PAGO")
valorTotal Decimal @db.Decimal(10, 2)
usuarioId Int
usuario Usuario @relation(fields: [usuarioId], references: [id])
itens ItemPedido[]
@@map("pedidos")
}
model ItemPedido {
id Int @id @default(autoincrement())
quantidade Int
precoUnitario Decimal @db.Decimal(10, 2)
pedidoId Int
pedido Pedido @relation(fields: [pedidoId], references: [id], onDelete: Cascade)
produtoId Int
produto Produto @relation(fields: [produtoId], references: [id], onDelete: Restrict)
@@map("itens_pedido")
}
Para efetivar a criação das tabelas no PostgreSQL e gerar o Client TypeScript, execute:
npx prisma migrate dev --name init_schema
🌱 4. Alimentação de Dados Didáticos (Seed)
Crie o arquivo prisma/seed.ts e adicione produtos e o usuário Admin padrão:
// prisma/seed.ts
import { PrismaClient } from '@prisma/client';
import * as bcrypt from 'bcrypt';
const prisma = new PrismaClient();
async function main() {
const senhaHash = await bcrypt.hash('admin123', 10);
await prisma.usuario.upsert({
where: { email: 'admin@tecloja.com' },
update: {},
create: {
nome: 'Administrador Supremo',
email: 'admin@tecloja.com',
senha: senhaHash,
role: 'ADMIN',
},
});
const catSmart = await prisma.categoria.upsert({
where: { nome: 'Smartphones' },
update: {},
create: { nome: 'Smartphones' },
});
await prisma.produto.create({
data: {
nome: 'iPhone 15 Pro',
descricao: 'Titânio Aeroespacial',
preco: 7999.00,
estoque: 15,
categoriaId: catSmart.id,
}
});
console.log('🌱 Seed executado com sucesso!');
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
Configure o comando de seed no final do package.json:
"prisma": {
"seed": "ts-node prisma/seed.ts"
}
E rode: npx prisma db seed
✅ Pré-Requisitos deste Módulo
Antes de passar para a integração do Prisma com o NestJS e criação do CRUD de categorias, certifique-se de que:
- Os conceitos sobre a evolução de SPAs para a arquitetura SSR e BFF foram compreendidos no Módulo 00.
- Você possui as credenciais ou a connection string (
DATABASE_URL) de um banco de dados PostgreSQL ativo.
🤔 Por que fizemos assim?
- Por que modelar tabelas e relações no arquivo
schema.prismaem vez de usar classes TypeScript (como no TypeORM)? O Prisma centraliza a modelagem de tabelas, tipos e relacionamentos em um único arquivo descritivo e unificado. Em vez de espalhar dezenas de decorators de classe TypeScript que podem divergir da estrutura física real do banco de dados, o Prisma CLI analisa este arquivo e gera automaticamente oPrisma Clientcom tipagem estática e auto-complete perfeito no editor de código, eliminando a escrita manual de mapeadores de dados. - Por que definir
onDelete: Cascadenos itens de pedidos eonDelete: Restrictnos produtos? Regras de integridade referencial previnem dados órfãos e inconsistências financeiras. MapearonDelete: Cascadenos itens vinculados a um pedido garante que a exclusão de umPedidolimpe automaticamente seus respectivos registros na tabela associativa, o que é esperado (um item de pedido não pode existir sem um pedido pai). Por outro lado,onDelete: Restrictno relacionamento de Produto impede que um administrador remova do catálogo um eletrônico que já possua histórico de vendas registradas, blindando a consistência fiscal da loja. - Por que usar
upsertem vez decreateno script de seed para criar usuários e categorias? O script de seeding (seed.ts) pode ser reexecutado repetidamente durante o desenvolvimento ou builds automáticos de CI/CD. Usar o métodocreatedireto lançaria uma exceção de violação de chave única (P2002) no banco de dados ao tentar cadastrar o mesmo e-mail de administrador ou categoria repetidamente. O métodoupsertatua com idempotência: ele pesquisa o registro por sua chave única; se encontrado, aplica a atualização (que pode ser deixada vazia), e se não existir, cria o novo registro de forma segura.
🔍 Checkpoint
- Variável de Conexão configurada: Confirme se o arquivo
.envfoi gerado na raiz do backend e se a variávelDATABASE_URLaponta para o banco de dados PostgreSQL correspondente. - Versionamento Criado: Verifique se o comando
npx prisma migrate devcriou a pastaprisma/migrationscontendo as queries SQL físicas executadas no banco de dados. - Tabelas Populadas: Execute o semeador de dados
npx prisma db seede valide na sua ferramenta cliente de banco (DBeaver/DBeaver) se as tabelas foram devidamente populadas com os dados do catálogo didático.
⚠️ Erros Comuns
| Erro | Causa | Solução |
|---|---|---|
A execução de prisma migrate falha com erros de conexão de rede ou rejeição de autenticação |
A string de conexão cadastrada no arquivo .env possui erros de senha, host ou falta o parâmetro SSL obrigatório do Neon. |
Verifique detalhadamente as credenciais no .env e garanta que a opção sslmode=require está presente no final da URL. |
| O TypeScript reclama que os modelos do Prisma ou as funções de consulta não existem no Prisma Client | O arquivo de tipos estáticos no node_modules está desatualizado em relação às modificações feitas no arquivo de esquema. |
Sempre que realizar modificações físicas em schema.prisma, execute o comando npx prisma generate no terminal para atualizar os tipos locais. |
| Preços de produtos no carrinho apresentando erros matemáticos e perdas de centavos | Uso do tipo genérico Float ou Double para armazenar moedas ou valores decimais financeiros. |
Mapeie campos monetários no Prisma utilizando a diretiva @db.Decimal(10, 2) para garantir a precisão de casas decimais idêntica às regras fiscais e matemáticas. |
🏁 Conclusão
Temos um banco Postgres provisionado na nuvem, tabelas construídas e sementes (Seeds) ativas. No Módulo 02, integraremos o Prisma ao motor do NestJS e criaremos o primeiro módulo CRUD (Categorias) utilizando Arquitetura Limpa.