ORM com Prisma: modelagem e consultas
1. Introdução ao Prisma ORM
Prisma é um ORM (Object-Relational Mapping) moderno para Node.js e TypeScript que simplifica drasticamente a interação com bancos de dados relacionais. Diferente de alternativas como Sequelize (que usa uma abordagem baseada em classes e herança) ou TypeORM (que exige decorators e configurações complexas), o Prisma adota uma abordagem declarativa: você define seus modelos em um arquivo schema, e o Prisma gera automaticamente um cliente tipado e seguro.
No ecossistema React + Node.js, o Prisma se destaca por oferecer:
- Type safety nativo: consultas são verificadas em tempo de compilação
- Auto-complete: IDEs modernas sugerem campos e relacionamentos
- Migrations declarativas: mudanças no schema geram migrations automaticamente
O fluxo básico é: Schema → Migrations → Client → Queries. Você define os modelos, executa migrations para sincronizar o banco, gera o cliente e então realiza consultas tipadas.
2. Configuração inicial e instalação
Para começar, instale o Prisma CLI e as dependências necessárias:
npm install prisma @prisma/client
npx prisma init
O comando prisma init cria a estrutura de pastas:
- prisma/schema.prisma — arquivo de definição de modelos
- .env — variáveis de ambiente (ex: DATABASE_URL)
Configure o schema.prisma para usar PostgreSQL:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
No .env, defina a string de conexão:
DATABASE_URL="postgresql://usuario:senha@localhost:5432/meubanco"
3. Modelagem de dados com Prisma Schema
Vamos modelar um sistema de blog com usuários, posts e categorias:
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
categories Category[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Category {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}
Principais elementos:
- @id: chave primária
- @default(autoincrement()): auto incremento
- @unique: campo único
- @relation: define relacionamentos (aqui, 1:N entre User e Post)
- Campos opcionais: name String? (aceita null)
- Cardinalidade: Post[] indica que um User tem muitos posts
Para relacionamentos N:N (Post ↔ Category), o Prisma cria automaticamente uma tabela de junção.
4. Migrations e sincronização com o banco
Para criar e aplicar a primeira migration:
npx prisma migrate dev --name init
Este comando:
1. Compara o schema com o banco atual
2. Gera um arquivo SQL de migration em prisma/migrations/
3. Executa a migration no banco
4. Gera o Prisma Client
Para prototipagem rápida sem migrations:
npx prisma db push
Para aplicar migrations em produção:
npx prisma migrate deploy
O histórico de migrations fica em prisma/migrations/. Para rollback, use prisma migrate reset (cuidado: perde dados).
5. Prisma Client: consultas básicas
Instale e use o Prisma Client no Node.js:
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
// CREATE
const newUser = await prisma.user.create({
data: {
email: 'joao@email.com',
name: 'João Silva',
posts: {
create: { title: 'Primeiro Post' }
}
}
})
// READ - único
const user = await prisma.user.findUnique({
where: { email: 'joao@email.com' }
})
// READ - múltiplos com filtros
const publishedPosts = await prisma.post.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 10,
skip: 0,
select: {
id: true,
title: true,
author: { select: { name: true } }
}
})
// UPDATE
const updatedPost = await prisma.post.update({
where: { id: 1 },
data: { published: true }
})
// DELETE
await prisma.post.delete({ where: { id: 1 } })
6. Consultas avançadas e relacionamentos
Para carregar relacionamentos (eager loading):
const userWithPosts = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: {
include: { categories: true }
}
}
})
Filtros aninhados com AND/OR:
const filteredPosts = await prisma.post.findMany({
where: {
AND: [
{ published: true },
{ title: { contains: 'Prisma' } }
],
OR: [
{ authorId: 1 },
{ authorId: 2 }
]
}
})
Operações em lote e agregações:
// Atualizar múltiplos
await prisma.post.updateMany({
where: { published: false },
data: { published: true }
})
// Agregações
const stats = await prisma.post.aggregate({
_count: { id: true },
_avg: { authorId: true }
})
// Contagem com filtro
const total = await prisma.post.count({
where: { published: true }
})
7. Integração com React e boas práticas
Crie uma API REST com Express que usa Prisma:
// controllers/postController.js
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export const getPosts = async (req, res) => {
try {
const posts = await prisma.post.findMany({
include: { author: { select: { name: true } } }
})
res.json(posts)
} catch (error) {
if (error instanceof PrismaClientKnownRequestError) {
res.status(400).json({ error: error.message })
} else {
res.status(500).json({ error: 'Erro interno' })
}
}
}
Separe em camadas: controllers (HTTP), services (lógica de negócio) e repositórios (acesso a dados com Prisma). No frontend React, consuma a API:
// components/PostList.jsx
import { useEffect, useState } from 'react'
export function PostList() {
const [posts, setPosts] = useState([])
useEffect(() => {
fetch('/api/posts')
.then(res => res.json())
.then(setPosts)
}, [])
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title} - {post.author.name}</li>
))}
</ul>
)
}
8. Dicas de performance e manutenção
Para consultas complexas não suportadas pelo Prisma Client, use raw queries:
const rawResult = await prisma.$queryRaw`
SELECT u.name, COUNT(p.id) as post_count
FROM "User" u
LEFT JOIN "Post" p ON u.id = p.author_id
GROUP BY u.id
HAVING COUNT(p.id) > 5
`
Adicione índices no schema para otimizar consultas frequentes:
model Post {
id Int @id @default(autoincrement())
title String
published Boolean @default(false)
authorId Int
createdAt DateTime @default(now())
@@index([authorId, published])
@@index([createdAt])
}
Para produção, use prisma migrate deploy em vez de prisma migrate dev. Versionamento de migrations é essencial — nunca edite migrations já aplicadas em produção.
Referências
- Documentação oficial do Prisma — Guia completo sobre schema, migrations, client e deploy
- Prisma com Node.js e Express — Tutorial oficial de integração com Express e deploy
- Prisma: Consultas avançadas e relacionamentos — Referência detalhada sobre CRUD, filtros e agregações
- Boas práticas com Prisma em produção — Guia de performance, índices e migrações seguras
- Prisma + React: exemplo prático de fullstack — Tutorial completo de aplicação fullstack com React e Prisma