Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

Relações no Prisma: include, connect e nested writes

Modele relações um-para-muitos no Prisma 7 e execute include, nested select, connect, filtros relacionais e exclusão protegida por foreign key.

Rodolfo Mori5 min de leitura

Relações no Prisma ligam models por campos de navegação e uma chave persistida no banco. Numa relação um-para-muitos, Autor tem livros Livro[]; Livro tem autor Autor e autorId Int. Depois disso, include, nested writes, connect e filtros relacionais trabalham sobre a foreign key do PostgreSQL.

Esta lição parte dos models e do CRUD já funcionando. Se esses passos ainda não estão no seu projeto, revise schema e modelos e CRUD com Prisma Client.

Ficha do livro e cadastro da autora: o modelo mental

Imagine duas fichas físicas. A ficha da autora tem seu número de cadastro. A ficha de cada livro guarda esse número, em vez de copiar nome e email da autora. Quando alguém pede os livros com suas autoras, o sistema cruza o número das duas fichas.

No mapa técnico, Autor.id é a chave primária, Livro.autorId é a foreign key e o cruzamento corresponde ao JOIN. Os relation fields autor e livros dão ao Prisma uma forma de navegar e escrever esse vínculo. O limite da analogia: a lista livros não é armazenada como uma pilha dentro da linha de autora; ela é obtida consultando as linhas de Livro que apontam para o id.

Volte ao comportamento do banco sempre que a sintaxe parecer mágica. A lição de JOIN no PostgreSQL mostra o cruzamento em SQL que existe por baixo das leituras relacionais.

Os dois lados precisam aparecer no schema

Modele a relação um-para-muitos:

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
  slug     String @unique
  autorId  Int   @map("autor_id")
  autor    Autor @relation(fields: [autorId], references: [id], onDelete: Restrict)

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

autorId vira autor_id no PostgreSQL. fields diz qual escalar guarda a chave; references aponta para Autor.id. onDelete: Restrict recusa apagar a autora enquanto livros a referenciam. O índice ajuda as consultas que chegam aos livros por autora.

livros Livro[] não aceita ?: uma lista sem relacionados é vazia, não nula. A relação do lado Livro é obrigatória porque autorId e autor também não são opcionais.

A migration prova a foreign key

Depois de prisma migrate dev, confira o SQL:

sql
ALTER TABLE "livros"
ADD CONSTRAINT "livros_autor_id_fkey"
FOREIGN KEY ("autor_id")
REFERENCES "autores"("id")
ON DELETE RESTRICT
ON UPDATE CASCADE;

Essa constraint é a garantia persistida. Mesmo que outro script escreva SQL sem Prisma, o PostgreSQL impede um autor_id órfão. Prisma gera API e tipos; a foreign key protege todas as portas de entrada do banco.

Nested create grava o conjunto ou nada

Crie autora e livro numa única operação:

ts
const autora = await prisma.autor.create({
  data: {
    nome: "Machado de Assis",
    email: "machado@example.com",
    livros: {
      create: {
        titulo: "Memórias Póstumas de Brás Cubas",
        slug: "memorias-postumas",
        preco: "44.90",
        estoque: 10,
        status: "PUBLICADO",
      },
    },
  },
  include: { livros: true },
});

O nome técnico é nested write. Em português direto, uma escrita inclui outra pela relation. Prisma executa o conjunto com garantia transacional: se o slug do livro violar uma constraint, a autora dessa operação não fica cadastrada sem o livro.

ts
console.log("NESTED CREATE", {
  autor: autora.nome,
  livros: autora.livros.map((livro) => livro.titulo),
});
NESTED CREATE { autor: 'Machado de Assis', livros: [ 'Memórias Póstumas de Brás Cubas' ] }

Include traz a relação; select controla a forma

Sem include ou select relacional, Prisma retorna campos escalares do model e não carrega relações automaticamente. Peça os livros, a contagem e uma ordem:

ts
const autores = await prisma.autor.findMany({
  orderBy: { nome: "asc" },
  include: {
    livros: {
      orderBy: { titulo: "asc" },
      select: { titulo: true },
    },
    _count: { select: { livros: true } },
  },
});

include acrescenta dados relacionados à forma padrão de Autor. Dentro dele, o nested select limita Livro a título. _count pede contagem sem devolver os objetos apenas para contar no JavaScript.

ts
console.log(autores.map((autor) => ({
  autor: autor.nome,
  total: autor._count.livros,
  livros: autor.livros.map((livro) => livro.titulo),
})));
[ { autor: 'Conceição Evaristo', total: 2, livros: [ 'Becos da memória', "Olhos d'água" ] }, { autor: 'Machado de Assis', total: 1, livros: [ 'Memórias Póstumas de Brás Cubas' ] } ]

Não use select e include no mesmo nível. Se precisa de poucos campos do model principal e da relation, faça um select principal e aninhe a relation nele.

Filtros relacionais respondem perguntas sobre o outro lado

Para relações em lista, some, every e none expressam quantificadores. Busque autoras com pelo menos um livro acima de cinquenta e cinco reais:

ts
const comLivroCaro = await prisma.autor.findMany({
  where: {
    livros: {
      some: { preco: { gt: "55.00" } },
    },
  },
  select: { nome: true },
});

console.log("FILTER", comLivroCaro);
FILTER [ { nome: 'Conceição Evaristo' } ]

some significa “existe pelo menos um”. every exige que todos os relacionados atendam ao filtro; numa lista vazia, essa condição é matematicamente verdadeira, o que pode surpreender. none exige que nenhum atenda. Escolha o quantificador pela pergunta do domínio.

Connect aponta para um registro já existente

Transfira o livro de Machado para Conceição localizando a autora por email único:

ts
const reconectado = await prisma.livro.update({
  where: { slug: "memorias-postumas" },
  data: {
    autor: {
      connect: { email: "conceicao@example.com" },
    },
  },
  select: {
    titulo: true,
    autor: { select: { nome: true } },
  },
});

console.log("CONNECT", reconectado);
CONNECT { titulo: 'Memórias Póstumas de Brás Cubas', autor: { nome: 'Conceição Evaristo' } }

connect não cria autora; ela precisa existir e o where precisa ser único. Tecnicamente, o update altera autor_id. Num sistema real, “transferir autoria” provavelmente não seria permitido: o exemplo revela o mecanismo, não recomenda a regra de negócio.

O lado oposto ausente falha na validação

Retire livros Livro[] de Autor, mantendo Livro.autor. prisma validate reproduz:

bash
npx prisma validate
Error code: P1012 error: Error validating field `autor` in model `Livro`: The relation field `autor` on model `Livro` is missing an opposite relation field on the model `Autor`. Either run `prisma format` or add it manually.

Validation Error Count: 1 Prisma CLI Version : 7.9.1

O erro não pede outra coluna em Autor. Ele pede o relation field oposto para o Prisma descrever a navegação. Recoloque livros Livro[], formate e valide.

Restrict impede apagar o pai referenciado

Com três livros apontando para Conceição, tente remover a autora:

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

try {
  await prisma.autor.delete({
    where: { email: "conceicao@example.com" },
  });
} catch (erro) {
  if (erro instanceof Prisma.PrismaClientKnownRequestError) {
    console.error("DELETE PARENT", {
      code: erro.code,
      modelName: erro.meta?.modelName,
    });
  }
}
DELETE PARENT { code: 'P2039', modelName: 'Autor' }

No teste com Prisma 7.9.1 e adapter-pg, o detalhe do driver informa que a operação viola RESTRICT da constraint livros_autor_id_fkey. Não trate apagando livros em cascata automaticamente. Decida se o domínio pede impedir, transferir, anonimizar, tornar opcional ou apagar dependentes. Depois represente a decisão em onDelete e no fluxo da aplicação.

Missão: ligue pedido, item e livro

Modele Pedido e ItemPedido. Um pedido tem muitos itens; cada item aponta para um livro e guarda quantidade e precoUnitario. Crie índices nas duas foreign keys e uma unique composta que impeça o mesmo livro duas vezes no mesmo pedido.

text
@@unique([pedidoId, livroId])

Seu critério de sucesso: um nested create grava pedido com dois itens; uma leitura devolve itens e títulos com nested select; some encontra pedidos com determinado livro; repetir o livro no mesmo pedido falha pela unique; apagar um livro usado é recusado pela foreign key.

Depois confira a migration SQL e escreva o JOIN equivalente. Quando a relação estiver visível nos dois níveis, siga para migrations no Prisma e aprenda a alterar essa estrutura sem perder os registros existentes.

  • prisma relations
  • relacoes prisma
  • include prisma
  • nested writes
  • connect prisma
  • foreign key

Perguntas frequentes

Relation field vira coluna no PostgreSQL?
Não diretamente. Em Livro, autor é um campo de navegação do Prisma e autorId é o campo escalar que mapeia para a coluna de foreign key. No lado Autor, livros é uma lista virtual de registros relacionados.
Qual a diferença entre include e select?
Include acrescenta relações aos campos escalares padrão. Select escolhe explicitamente os campos retornados e também pode selecionar relações de forma aninhada. Não use ambos no mesmo nível da query.
Nested create usa transação?
Sim. Nested writes têm garantia transacional: se a criação de uma parte falha, as alterações relacionadas daquela operação são revertidas.
Connect cria o registro relacionado?
Não. Connect localiza um registro existente por condição única e cria ou altera o vínculo. Para criar o registro no mesmo comando, use create aninhado ou connectOrCreate quando ele representa o fluxo desejado.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo 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. Prisma Client — relation queries — prisma.io
  2. Prisma Schema — relations — prisma.io
  3. Prisma Schema — referential actions — prisma.io
  4. Prisma Client — select fields — prisma.io

Continue por aqui