Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA

Guia completo

Prisma ORM 7: guia completo com PostgreSQL

Entenda schema, Prisma Client, driver adapter, CRUD, relações e migrations no Prisma ORM 7.9.1 com Node 24 e PostgreSQL 18, do modelo ao SQL real.

5 lições em trilha5 artigos no guiaAtualizado em agosto de 2026

Prisma ORM 7 é uma camada entre sua aplicação TypeScript e o banco de dados. Você descreve modelos num schema.prisma, gera um Prisma Client adaptado a esses modelos e usa métodos como create, findMany e update; no PostgreSQL, o resultado continua sendo tabela, constraint, índice, transação e SQL.

Ao terminar este guia, você entenderá as peças do Prisma 7.9.1, verá um catálogo de livros sair do schema e chegar ao PostgreSQL 18.6 e terá uma ordem de estudo para instalação, modelos, CRUD, relações e migrations. O foco é a arquitetura atual: ESM, prisma.config.ts, driver adapter obrigatório e Client gerado num diretório explícito.

Se tabela, chave primária e JOIN ainda são nomes novos, comece pelo guia de PostgreSQL. O ORM reduz código repetitivo, mas não remove as decisões do banco. Saber o que existe por baixo é o que permite usar a ferramenta sem transformar autocomplete em adivinhação.

Imagine uma livraria que recebe pedidos em português, mas o estoque trabalha com fichas e códigos próprios. Um atendente traduz “quero livros publicados acima de cinquenta reais” para a consulta entendida pelo estoque e devolve a resposta num formato previsível. Ele também conhece o catálogo: se alguém pedir uma coluna que não existe, consegue avisar antes de enviar o pedido.

No mapa técnico, sua aplicação é a pessoa no balcão, Prisma Client é o atendente, o Query Compiler transforma a consulta e o driver pg conversa com PostgreSQL. O schema.prisma descreve o catálogo que gerou esse atendente. O nome ORM vem de object-relational mapping: uma camada que relaciona objetos e operações da aplicação às estruturas relacionais do banco.

O limite da analogia é onde muita gente se confunde. Prisma não é o estoque e não conhece dados que nunca chegaram à execução. O banco ainda garante chave única e chave estrangeira, escolhe plano, bloqueia concorrência e persiste os bytes. Um modelo bem tipado também não prova que um desconto está correto. O ORM organiza a conversa; não substitui SQL, validação ou regra de negócio.

As cinco peças do Prisma 7

Um projeto atual tem cinco partes que cooperam. Separá-las evita copiar um tutorial do Prisma 6 e receber erros aparentemente sem relação.

  1. Prisma Schema: modelos, campos, relações, datasource e generator.
  2. Prisma Config: caminho do schema, migrations e URL usada pela CLI.
  3. Prisma CLI: valida, gera Client e cria ou aplica migrations.
  4. Prisma Client: código gerado a partir dos modelos, importado pela aplicação.
  5. Driver adapter: ponte de runtime entre Client e o driver JavaScript do banco.

No Prisma 7, a URL deixou o bloco datasource e vai para prisma.config.ts. O generator moderno é prisma-client, pede output explícito e gera código no seu projeto. Para PostgreSQL, a aplicação instala @prisma/adapter-pg e entrega o adapter ao construtor do Client. O pacote precisa trabalhar como ESM, com "type": "module" no package.json.

bash
npm install @prisma/client@7.9.1 @prisma/adapter-pg@7.9.1 pg dotenv
npm install --save-dev prisma@7.9.1 typescript tsx @types/node @types/pg

Essas mudanças não são detalhe cosmético. O exemplo antigo abaixo não é a base deste cluster: importar de @prisma/client e chamar new PrismaClient() sem adapter já não representa o fluxo exigido pelo Prisma 7.

Do modelo à consulta existem duas viagens

A primeira viagem acontece no desenvolvimento. Você altera o schema, cria uma migration, aplica SQL no banco e gera o Client. A segunda acontece durante a execução: seu código chama o Client, o Query Compiler prepara a operação e o adapter envia a consulta pelo driver pg.

Pense nas setas desta forma:

text
schema.prisma -> prisma migrate dev -> PostgreSQL
       |
       +------> prisma generate ----> src/generated/prisma

aplicação -> Prisma Client -> @prisma/adapter-pg -> pg -> PostgreSQL

No Prisma 7, migrate dev não executa prisma generate automaticamente. As duas viagens usam o mesmo schema, mas cumprem tarefas diferentes. Migration muda a estrutura persistida; generate muda a API tipada disponível para o TypeScript. Esquecer a segunda etapa deixa o editor enxergando um modelo antigo.

Uma configuração mínima e atual

O schema começa declarando onde o Client será gerado e qual banco será usado:

text
generator client {
  provider     = "prisma-client"
  output       = "../src/generated/prisma"
  moduleFormat = "esm"
}

datasource db {
  provider = "postgresql"
}

output é relativo ao arquivo schema.prisma. Se ele mora em prisma/, o caminho ../src/generated/prisma aponta para dentro de src. O gerador cria arquivos TypeScript que entram no versionamento ou no processo de build conforme a política do projeto. O ponto obrigatório é importar desse caminho, não contar com geração escondida dentro de node_modules.

A configuração da CLI fica em prisma.config.ts:

ts
import "dotenv/config";
import { defineConfig, env } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: { path: "prisma/migrations" },
  datasource: {
    url: env("DATABASE_URL"),
  },
});

dotenv/config carrega o arquivo .env; env interrompe a CLI se a variável obrigatória não existir. Essa URL serve a comandos como validate e migrate. Na aplicação, o adapter recebe a conexão de runtime. Em banco serverless pode haver uma URL direta para migration e outra preparada para o tráfego da aplicação; não misture as duas por conveniência.

O primeiro modelo vira tabela e API

O catálogo usa Autor e Livro. O modelo abaixo mistura decisões da aplicação com decisões reais do PostgreSQL:

text
enum StatusLivro {
  RASCUNHO
  PUBLICADO
  ESGOTADO
}

model Livro {
  id       Int         @id @default(autoincrement())
  titulo   String
  slug     String      @unique
  preco    Decimal     @db.Decimal(10, 2)
  estoque  Int         @default(0)
  status   StatusLivro @default(RASCUNHO)
  autorId  Int         @map("autor_id")

  @@index([autorId])
  @@map("livros")
}

model Livro gera a base para prisma.livro. @id identifica cada registro; @unique cria uma regra de unicidade; @default define valores; o tipo nativo Decimal(10, 2) evita usar ponto flutuante para preço. @map e @@map mantêm nomes agradáveis no TypeScript e nomes snake_case ou plurais no banco.

Ainda falta a relação com Autor, tratada numa lição própria. Por enquanto, perceba a dupla função: o schema orienta o SQL de migration e também a API gerada. Se você acrescentar isbn, precisa migrar o banco e gerar novamente o Client.

Client gerado não é um pacote genérico

Depois de npx prisma generate, a saída real do teste foi:

bash
npx prisma generate
Loaded Prisma config from prisma.config.ts.

Prisma schema loaded from prisma/schema.prisma.

✔ Generated Prisma Client (7.9.1) to ./src/generated/prisma in 18ms

O Client conhece exatamente os modelos, campos, enums e relações daquele schema. É por isso que o editor completa prisma.livro.findMany e rejeita prisma.livro.findTudo. Não existe uma API universal baixada pronta: o gerador produz uma versão alinhada ao seu domínio.

No runtime, configure o adapter uma vez e exporte uma instância compartilhada:

ts
import "dotenv/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "./generated/prisma/client.js";

const connectionString = process.env.DATABASE_URL;

if (!connectionString) {
  throw new Error("DATABASE_URL não foi definida");
}

const adapter = new PrismaPg({ connectionString });
export const prisma = new PrismaClient({ adapter });

O arquivo termina em .ts, mas o import usa .js porque o projeto segue a resolução ESM do NodeNext. Ferramentas como tsx resolvem o fonte durante o desenvolvimento; o JavaScript compilado mantém uma extensão válida para o Node. Em servidor, reaproveite a instância em vez de criar um pool a cada requisição.

O mapa do cluster acompanha uma aplicação real

As cinco lições seguem a ordem em que as dependências aparecem no projeto. A trilha não começa em métodos de consulta porque ainda não existe Client nem tabela para consultarem.

Etapa Resultado verificável Lição
1 PostgreSQL responde e Client é gerado com adapter Instalar Prisma 7 com PostgreSQL
2 Modelos válidos geram tabelas, constraints e tipos Schema e modelos no Prisma
3 Create, read, update e delete alteram dados reais CRUD com Prisma Client
4 Autor e Livro são consultados e gravados juntos Relações no Prisma
5 Mudanças de estrutura ganham SQL versionado Migrations com Prisma

Use a sequência da trilha de banco de dados para enxergar SQL e Prisma lado a lado. O conhecimento não compete: primeiro você entende o que SELECT, INSERT, chave estrangeira e índice fazem; depois observa como o Client expressa essas operações.

Quando quiser reunir configuração, schema, migration e CRUD numa entrega única, siga o tutorial API Node com TypeScript, Express, Prisma e Docker. Ele mostra o Prisma como uma camada do sistema, sem esconder o SQL nem o banco.

CRUD continua sendo uma mudança no banco

Com a instância pronta, criar uma autora com dois livros relacionados usa um nested write. A operação é transacional: se um livro violar uma regra, a autora também não fica gravada pela metade.

ts
const autora = await prisma.autor.create({
  data: {
    nome: "Conceição Evaristo",
    email: "conceicao@example.com",
    livros: {
      create: [
        { titulo: "Olhos d'água", slug: "olhos-dagua", preco: "49.90" },
        { titulo: "Becos da memória", slug: "becos-da-memoria", preco: "59.90" },
      ],
    },
  },
  include: {
    livros: { orderBy: { titulo: "asc" } },
  },
});

console.log({
  autora: autora.nome,
  livros: autora.livros.map((livro) => livro.titulo),
});
{ autora: 'Conceição Evaristo', livros: [ 'Becos da memória', "Olhos d'água" ] }

O Prisma não retorna relações por padrão. include pediu os livros junto da autora; select seria a escolha para trazer campos específicos. No banco, as gravações viram INSERTs coordenados por transação e ligados pela chave estrangeira.

Uma leitura intencional pede somente o que a tela usa:

ts
const livros = await prisma.livro.findMany({
  where: { status: "PUBLICADO", preco: { gte: "50.00" } },
  orderBy: { titulo: "asc" },
  select: {
    titulo: true,
    preco: true,
    autor: { select: { nome: true } },
  },
});

where, orderBy e select são objetos tipados. O editor conhece quais operadores pertencem a Decimal e quais campos existem em Autor. A consulta ainda precisa de índice e desenho adequado para crescer; tipos evitam nomes inválidos, não garantem plano rápido.

Relações têm campo de navegação e chave real

Num relacionamento um-para-muitos, Autor possui livros Livro[]. Livro possui o campo de navegação autor Autor e o escalar autorId Int que vira coluna:

text
model Autor {
  id     Int     @id @default(autoincrement())
  nome   String
  email  String  @unique
  livros Livro[]

  @@map("autores")
}

model Livro {
  id       Int   @id @default(autoincrement())
  titulo   String
  autorId  Int   @map("autor_id")
  autor    Autor @relation(fields: [autorId], references: [id], onDelete: Restrict)

  @@index([autorId])
  @@map("livros")
}

livros e autor ajudam o Client a navegar, mas não viram duas colunas misteriosas. A coluna persistida é autor_id, protegida por foreign key. O onDelete: Restrict impede apagar uma autora que ainda é referenciada. O índice em autorId é explícito porque consultas e joins por essa coluna serão comuns.

No Prisma 7.9.1 com @prisma/adapter-pg, tentar apagar o registro pai produziu o erro controlado abaixo:

DELETE PARENT { code: 'P2039', modelName: 'Autor' }

O detalhe do adapter preservou a mensagem do PostgreSQL: a tabela autores viola a regra RESTRICT da foreign key livros_autor_id_fkey. A correção depende do domínio: remover ou transferir livros antes, permitir autor nulo, ou escolher uma ação referencial diferente de forma consciente.

Migrations são o histórico executável da estrutura

Alterar o schema não muda o banco por telepatia. prisma migrate dev compara o estado desejado, cria SQL dentro de prisma/migrations e aplica a migration no ambiente de desenvolvimento. O arquivo vai para o Git e precisa ser revisado como código.

bash
npx prisma migrate dev --name inicial
npx prisma generate
Applying migration `20260823013306_inicial`

The following migration(s) have been created and applied from new schema changes:

prisma/migrations/ └─ 20260823013306_inicial/ └─ migration.sql

Your database is now in sync with your schema.

O nome da pasta começa com timestamp e muda em cada execução. O conteúdo SQL é a prova do que ocorrerá: CREATE TABLE, ALTER TABLE, índices e foreign keys. Não edite uma migration já aplicada para “arrumar o passado”; crie uma mudança seguinte. Em produção, use prisma migrate deploy, nunca migrate dev.

Um erro de unicidade mostra onde termina o tipo

O schema declarou email String @unique. TypeScript sabe que email é texto, mas não consegue saber quais valores já existem no banco. Duas requisições podem passar pela validação e tentar o mesmo email. A constraint do PostgreSQL decide, e Prisma transforma o retorno num erro conhecido:

ts
import { Prisma } from "./generated/prisma/client.js";

try {
  await prisma.autor.create({
    data: { nome: "Outra autora", email: "conceicao@example.com" },
  });
} catch (erro) {
  if (erro instanceof Prisma.PrismaClientKnownRequestError) {
    console.error({ name: erro.name, code: erro.code });
  }
}
{ name: 'PrismaClientKnownRequestError', code: 'P2002' }

P2002 significa violação de constraint única. Uma API costuma traduzir isso para conflito, sem devolver stack interna ao cliente. Fazer primeiro um findUnique pode melhorar a mensagem, mas não substitui a constraint: outra requisição pode gravar entre a leitura e a escrita.

Tabela de decisão que uso antes de adotar Prisma

Prisma é uma escolha de arquitetura, não um selo obrigatório em toda API Node. Minha decisão começa pela frequência das consultas comuns, necessidade de controle SQL e capacidade da equipe de diagnosticar o banco.

Situação Escolha inicial Motivo e limite
CRUD e relações comuns em TypeScript Prisma Client API gerada reduz nomes inválidos e mantém filtros, retornos e relações tipados.
Projeto novo com schema controlado pela aplicação Prisma Migrate O schema declarativo e o SQL versionado formam um fluxo único de evolução.
Banco legado já existente prisma db pull antes de migrar Introspecção parte da estrutura real; não deixe uma migration inicial tentar recriar tabelas existentes.
Relatório SQL complexo ou recurso específico do PostgreSQL SQL tipado ou raw query localizada Não force uma consulta ilegível pela API; mantenha parâmetros seguros e revisão do SQL.
Equipe sem base de SQL Estudar SQL junto, não esconder O ORM não explica índice ausente, lock, cardinalidade ou plano lento.
Função serverless Adapter e ciclo de conexão revisados Pool, limites da plataforma e URL apropriada importam mais que a sintaxe do Client.
Migration de alto risco SQL inspecionado e rollout em etapas Campo obrigatório, índice pesado e alteração de tipo precisam considerar dados e locks.
Versão RC numa aplicação estável Permanecer na GA Prisma 8 ainda é RC nesta revisão; teste separado não é o mesmo que atualização de produção.

O sinal verde não é “não quero escrever SQL”. É “quero uma API tipada para a maior parte do acesso, e aceito continuar responsável pelo banco”. Quando a equipe não consegue explicar qual tabela e constraint estão por trás de uma query, a abstração já passou do ponto saudável.

Como localizar a camada que realmente falhou

Uma mensagem com a palavra Prisma não significa que o ORM causou o problema. Antes de mudar código, descubra em qual etapa a falha nasceu. Se prisma validate acusa P1012, o schema está incoerente e nenhuma query chegou ao banco. Se prisma generate termina, mas o TypeScript não encontra um campo novo, o import pode apontar para outro output ou o editor pode estar usando Client antigo. Se a aplicação não abre conexão, confira URL, adapter, rede e processo PostgreSQL antes de revisar o model.

Erros conhecidos como P2002 e P2025 atravessam o Client porque o banco ou a operação encontrou um estado esperado: unicidade violada ou registro necessário ausente. Já o P2039 observado com adapter-pg trouxe dentro de meta a causa do driver e o código do PostgreSQL. Nesse caso, o número Prisma é a primeira pista, não toda a investigação. Preserve o erro original nos logs internos e devolva ao consumidor apenas uma resposta de domínio, sem caminho local nem credencial.

Minha sequência de diagnóstico é curta. Primeiro confirmo versão e config com npx prisma --version e npx prisma validate. Depois verifico se migration e banco concordam com npx prisma migrate status. Em seguida reduzo a consulta para um script que cria uma instância, executa uma operação e desconecta. Só depois olho plano, índice, pool e concorrência. Essa ordem separa problema de ferramenta, estrutura, conexão e dado sem trocar cinco coisas ao mesmo tempo.

Em desenvolvimento, logs de query podem mostrar duração e operação, mas não devem virar vazamento de dados pessoais. Se uma listagem fica lenta, observe o SQL e rode EXPLAIN (ANALYZE, BUFFERS) num ambiente seguro. select menor reduz tráfego; ele não compensa um filtro sem índice. Da mesma forma, include torna a intenção conveniente, mas relações profundas e listas grandes ainda precisam de paginação e análise de cardinalidade.

Há uma fronteira final: performance de conexão. O driver pg mantém pool e o adapter usa essa camada. Criar Client dentro de cada handler pode multiplicar pools; guardar uma instância global sem entender o ambiente serverless também pode exceder o limite do provedor. A política correta depende do ciclo de vida da plataforma, mas a pergunta é sempre observável: quantas conexões existem, quanto tempo uma query espera e quem encerra o processo.

Esse modo de depurar é parte da decisão autoral deste guia. Prisma é mais útil quando melhora a comunicação entre aplicação e banco sem esconder as medições. Se a única reação a um erro é adicionar as any, aumentar timeout ou recriar a base, volte uma camada e produza uma prova menor.

O que Prisma resolve e o que continua com você

Prisma resolve geração de uma API alinhada ao schema, composição de consultas, nested writes, histórico de migration e uma camada de erros conhecidos. Ele também deixa mudanças visíveis no editor: retirar um campo do schema faz usos antigos falharem na checagem depois de gerar o Client.

Continuam com você a validação da entrada HTTP, autorização, regras de negócio, índices, limites de conexão, observabilidade, backup, restauração e rollout. data: req.body não fica seguro só porque Prisma rejeita campos que não conhece. Valide formatos e permita somente propriedades autorizadas antes da query.

Também continue olhando SQL. Ative logs de query de modo controlado em desenvolvimento, use EXPLAIN para investigação e acompanhe consultas lentas no PostgreSQL. O artigo de SQL com SELECT, INSERT, UPDATE e DELETE mostra as operações que o CRUD expressa por baixo.

Missão: prove as duas camadas

Suba um PostgreSQL descartável, configure Prisma 7.9.1 com adapter e modele Autor e Livro. Crie a migration inicial, abra o SQL e identifique CREATE TABLE, índice único e foreign key. Depois gere o Client e escreva uma consulta que liste título, preço e nome da autora.

O critério de sucesso tem seis provas: prisma validate confirma o schema; prisma migrate status diz que o banco está atualizado; prisma generate mostra o caminho explícito; o TypeScript rejeita um campo inexistente; a consulta devolve dados do PostgreSQL; e um email duplicado produz P2002 sem gravar uma segunda autora.

Guarde o projeto e faça commits separados para schema, migration e aplicação. Esse histórico deixa visível a ideia central: Prisma tem uma camada de desenvolvimento que gera contratos e uma camada de runtime que conversa com o banco. Quando você consegue apontar onde cada decisão vive, siga para a instalação completa do Prisma com PostgreSQL e reproduza o ambiente do início.

Trilha

Banco de dados

SQL, PostgreSQL, MongoDB e Prisma: modelar, consultar e conectar o banco na API que você escreveu.

Ver a trilha
  1. 01Como instalar Prisma 7 com PostgreSQL
  2. 02Schema e modelos no Prisma 7: campos e regras
  3. 03CRUD com Prisma Client: create, read, update e delete
  4. 04Relações no Prisma: include, connect e nested writes
  5. 05Migrations no Prisma 7: dev, status e deploy

Perguntas frequentes

Prisma substitui o PostgreSQL?
Não. PostgreSQL continua guardando dados, executando SQL, índices, constraints e transações. Prisma modela e consulta esse banco por uma API gerada para TypeScript.
Preciso saber SQL para usar Prisma?
Você consegue começar sem escrever cada comando SQL, mas precisa entender tabela, chave, JOIN, índice e transação para modelar corretamente, analisar consultas e resolver problemas de produção.
Prisma 7 ainda usa prisma-client-js?
O caminho atual usa o gerador prisma-client, com output explícito no projeto. O código gerado é importado desse diretório e a conexão exige um driver adapter, como @prisma/adapter-pg para PostgreSQL.
Prisma 8 já deve ser usado como versão estável?
Não neste guia. Em 22 de agosto de 2026, 7.9.1 é a versão latest no npm e a linha 8 está em release candidate. Projetos de produção devem avaliar uma versão GA, não tratar RC como atualização automática.
Migrate dev pode rodar em produção?
Não. Migrate dev cria migrations, usa shadow database e pode pedir reset; é um comando de desenvolvimento. Em staging e produção, aplique o histórico revisado com prisma migrate deploy no pipeline.

O código deste guia foi executado em Node 24.16.0, Prisma ORM 7.9.1, @prisma/adapter-pg 7.9.1, PostgreSQL 18.6 em postgres:18-alpine e Docker 29.5.3, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. npm — versões do Prisma — npmjs.com
  2. Prisma ORM — documentação oficial — prisma.io
  3. Prisma Client — geração e output — prisma.io
  4. Prisma ORM — database drivers e adapters — prisma.io
  5. Prisma Migrate — visão geral — prisma.io