Prisma ORM: Guia Completo para Node.js
Prisma é o ORM mais adotado no ecossistema Node.js/TypeScript em 2024. Diferente de ORMs tradicionais como Sequelize, o Prisma gera tipos TypeScript a partir do schema — seu editor conhece cada campo, cada relação, cada retorno de query.
Instalação e configuração inicial
npm install prisma @prisma/client
npx prisma init --datasource-provider postgresqlIsso cria prisma/schema.prisma e adiciona DATABASE_URL no .env.
Definindo o schema
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Usuario {
id String @id @default(cuid())
email String @unique
nome String
role Role @default(USER)
criadoEm DateTime @default(now())
posts Post[]
perfil Perfil?
@@index([email])
@@map("usuarios")
}
model Perfil {
id String @id @default(cuid())
bio String?
avatar String?
usuarioId String @unique
usuario Usuario @relation(fields: [usuarioId], references: [id], onDelete: Cascade)
@@map("perfis")
}
model Post {
id String @id @default(cuid())
titulo String
conteudo String
publicado Boolean @default(false)
criadoEm DateTime @default(now())
atualizadoEm DateTime @updatedAt
autorId String
autor Usuario @relation(fields: [autorId], references: [id])
tags Tag[]
comentarios Comentario[]
@@index([autorId])
@@index([publicado, criadoEm(sort: Desc)])
@@map("posts")
}
model Tag {
id String @id @default(cuid())
nome String @unique
posts Post[]
@@map("tags")
}
model Comentario {
id String @id @default(cuid())
texto String
postId String
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
@@map("comentarios")
}
enum Role {
USER
ADMIN
EDITOR
}Migrations
# Desenvolvimento: cria e aplica a migration
npx prisma migrate dev --name create-usuarios-posts
# Produção: aplica migrations pendentes sem criar novas
npx prisma migrate deploy
# Reseta o banco (desenvolvimento — DESTRÓI dados)
npx prisma migrate reset
# Visualiza o banco no browser
npx prisma studioInstância singleton do PrismaClient
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma ||
new PrismaClient({
log: process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn']
: ['error'],
});
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;O singleton evita criar múltiplas conexões em desenvolvimento com hot reload (Next.js, nodemon).
CRUD com tipagem completa
import { prisma } from './lib/prisma';
import type { Usuario, Post } from '@prisma/client';
// Create
async function criarUsuario(email: string, nome: string): Promise<Usuario> {
return prisma.usuario.create({
data: { email, nome },
});
}
// Read com seleção de campos
async function buscarUsuario(id: string) {
return prisma.usuario.findUniqueOrThrow({
where: { id },
select: {
id: true,
nome: true,
email: true,
role: true,
// Exclui automaticamente campos sensíveis não selecionados
},
});
}
// Update
async function publicarPost(postId: string): Promise<Post> {
return prisma.post.update({
where: { id: postId },
data: { publicado: true },
});
}
// Delete com soft delete pattern
async function deletarPost(postId: string) {
// Cascade configurado no schema deleta comentários automaticamente
return prisma.post.delete({ where: { id: postId } });
}Queries com relações
// Inclui relações (equivale a JOIN)
const postsComAutor = await prisma.post.findMany({
where: { publicado: true },
include: {
autor: {
select: { nome: true, email: true },
},
tags: true,
_count: { select: { comentarios: true } },
},
orderBy: { criadoEm: 'desc' },
take: 10,
skip: 0,
});
// Filtros aninhados
const usuariosAtivos = await prisma.usuario.findMany({
where: {
posts: {
some: {
publicado: true,
criadoEm: { gte: new Date('2024-01-01') },
},
},
},
include: {
_count: { select: { posts: true } },
},
});Transactions
// Transação interativa (para lógica condicional)
const resultado = await prisma.$transaction(async (tx) => {
const usuario = await tx.usuario.create({
data: { email: 'novo@email.com', nome: 'Novo Usuário' },
});
const perfil = await tx.perfil.create({
data: { usuarioId: usuario.id, bio: 'Olá!' },
});
// Se qualquer operação falhar, tudo é revertido automaticamente
return { usuario, perfil };
});
// Transação em lote (mais performática para operações independentes)
const [novoPost, tagAtualizada] = await prisma.$transaction([
prisma.post.create({ data: { titulo: 'Novo', conteudo: '...', autorId: 'x' } }),
prisma.tag.update({ where: { nome: 'nodejs' }, data: { nome: 'Node.js' } }),
]);Queries raw para casos complexos
// SQL raw tipado
const resultado = await prisma.$queryRaw<{ mes: string; total: number }[]>`
SELECT
TO_CHAR(criado_em, 'YYYY-MM') AS mes,
COUNT(*)::int AS total
FROM posts
WHERE publicado = true
GROUP BY mes
ORDER BY mes DESC
LIMIT 12
`;
// Execute para INSERT/UPDATE/DELETE sem retorno tipado
await prisma.$executeRaw`
UPDATE usuarios SET ultimo_acesso = NOW()
WHERE id = ${usuarioId}
`;Evitando o problema N+1
// ❌ N+1: 1 query para posts + N queries para cada autor
const posts = await prisma.post.findMany();
const postsComAutor = await Promise.all(
posts.map(async (post) => ({
...post,
autor: await prisma.usuario.findUnique({ where: { id: post.autorId } }),
}))
);
// ✅ 2 queries com JOIN automático via include
const postsComAutor = await prisma.post.findMany({
include: { autor: true },
});Connection pooling em produção com PgBouncer
O Prisma abre uma connection pool interna, mas em ambientes serverless (Lambda, Vercel) cada instância abre seu próprio pool — podendo esgotar as conexões do PostgreSQL.
# Com Prisma Accelerate (serviço gerenciado da Prisma)
DATABASE_URL="prisma://accelerate.prisma-data.net/?api_key=..."
# Ou com PgBouncer próprio
DATABASE_URL="postgresql://user:pass@pgbouncer:6432/db?pgbouncer=true"// Para serverless: cria cliente sem pool interno
const prisma = new PrismaClient({
datasources: {
db: { url: process.env.DATABASE_URL + '?connection_limit=1' },
},
});Seed de dados
// prisma/seed.ts
import { prisma } from '../lib/prisma';
async function main() {
await prisma.usuario.upsert({
where: { email: 'admin@blog.com' },
update: {},
create: {
email: 'admin@blog.com',
nome: 'Administrador',
role: 'ADMIN',
posts: {
create: [
{ titulo: 'Primeiro post', conteudo: 'Conteúdo inicial', publicado: true },
],
},
},
});
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect());// package.json
{
"prisma": {
"seed": "tsx prisma/seed.ts"
}
}npx prisma db seedO Prisma é uma escolha sólida para projetos TypeScript. O gerador de tipos elimina uma classe inteira de bugs em tempo de desenvolvimento — campos com typo, relações inexistentes e tipos incorretos viram erros de compilação, não de runtime.