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

CRUD com Prisma Client: create, read, update e delete

Execute CRUD no PostgreSQL com Prisma Client 7, usando create, findMany, update, delete, filtros, select, transação e tratamento real do erro P2025.

Rodolfo Mori5 min de leitura

CRUD com Prisma Client usa create para inserir, findUnique ou findMany para ler, update para alterar e delete para remover registros. Nesta lição, cada operação será executada no PostgreSQL e terá uma saída observável, incluindo o erro P2025 de um registro que não existe.

Você precisa do Client gerado e conectado pelo adapter. Se o objeto prisma ainda não existe, volte à instalação do Prisma 7 e à modelagem do schema.

Quatro pedidos no balcão: o modelo mental do CRUD

Imagine o balcão do catálogo com quatro pedidos: cadastrar uma ficha, consultar fichas, corrigir uma informação e retirar uma ficha. Em inglês, create, read, update e delete formam a sigla CRUD. Prisma oferece métodos com esses nomes ou nomes mais específicos para leitura.

No mapeamento técnico, data é o conteúdo a gravar, where é o critério que localiza linhas, select escolhe a parte devolvida e o método decide a operação. O limite da analogia é que o banco trabalha com concorrência e conjuntos: duas requisições podem chegar juntas, e métodos terminados em Many podem atingir várias linhas numa chamada.

Por baixo do Client continuam existindo INSERT, SELECT, UPDATE e DELETE. Compare esta lição com o CRUD escrito em SQL para reconhecer as duas formas de expressar a mesma intenção.

Comece por uma instância compartilhada

O arquivo de conexão do projeto usa ESM e o adapter obrigatório:

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 });

Nos scripts desta aula, a última linha chama await prisma.$disconnect(). Numa API que permanece ligada, você normalmente mantém a instância durante a vida do processo. Criar um Client por requisição abre pools desnecessários e pode esgotar conexões.

Create insere os campos permitidos pelo model

Primeiro localize a autora por email único e crie um livro ligado ao id:

ts
const autora = await prisma.autor.findUniqueOrThrow({
  where: { email: "conceicao@example.com" },
});

const criado = await prisma.livro.create({
  data: {
    titulo: "Ponciá Vicêncio",
    slug: "poncia-vicencio",
    preco: "54.90",
    estoque: 6,
    status: "PUBLICADO",
    autorId: autora.id,
  },
  select: { titulo: true, slug: true, preco: true, estoque: true },
});

create exige os campos obrigatórios que não têm default. O preço entra como texto decimal para não passar por um ponto flutuante impreciso. select evita trazer id, timestamps e campos que a resposta não usa.

Para imprimir Decimal de forma previsível:

ts
console.log("CREATE", {
  ...criado,
  preco: criado.preco.toString(),
});
CREATE { titulo: 'Ponciá Vicêncio', slug: 'poncia-vicencio', preco: '54.9', estoque: 6 }

O PostgreSQL armazenou 54.90 em DECIMAL(10,2). toString() exibiu 54.9 porque não é uma função de formatação monetária. Formate duas casas na borda da interface, sem transformar o valor persistido em number por conveniência.

FindUnique procura uma chave; findMany monta uma lista

Use findUnique quando where contém @id, @unique ou uma chave única composta:

ts
const livro = await prisma.livro.findUnique({
  where: { slug: "poncia-vicencio" },
  select: { titulo: true, estoque: true },
});

console.log(livro);
{ titulo: 'Ponciá Vicêncio', estoque: 6 }

O retorno inclui null, porque nenhuma linha pode ter aquele slug. Se a ausência é erro de fluxo, findUniqueOrThrow devolve o registro ou lança P2025.

Uma listagem combina filtro, ordenação e projeção:

ts
const publicados = await prisma.livro.findMany({
  where: {
    status: "PUBLICADO",
    estoque: { gt: 0 },
  },
  orderBy: { titulo: "asc" },
  select: {
    titulo: true,
    preco: true,
    autor: { select: { nome: true } },
  },
});

gt significa greater than, “maior que”. Prisma gera os tipos dos operadores com base no campo; um filtro de string oferece contains, enquanto Int oferece comparações numéricas. A relation só voltou porque foi pedida no select.

ts
console.log(publicados.map((livro) => ({
  titulo: livro.titulo,
  preco: livro.preco.toString(),
  autora: livro.autor.nome,
})));
[ { titulo: 'Becos da memória', preco: '59.9', autora: 'Conceição Evaristo' }, { titulo: "Olhos d'água", preco: '49.9', autora: 'Conceição Evaristo' }, { titulo: 'Ponciá Vicêncio', preco: '54.9', autora: 'Conceição Evaristo' } ]

Update aceita operações atômicas

Para reduzir estoque, não leia, subtraia no JavaScript e grave depois. Duas compras poderiam ler o mesmo número. Use a operação atômica decrement:

ts
const atualizado = await prisma.livro.update({
  where: { slug: "poncia-vicencio" },
  data: {
    estoque: { decrement: 1 },
  },
  select: { slug: true, estoque: true },
});

console.log("UPDATE", atualizado);
UPDATE { slug: 'poncia-vicencio', estoque: 5 }

O cálculo acontece no banco numa única atualização. Ainda falta impedir estoque negativo quando duas vendas disputam a última unidade; isso pede condição, transação ou constraint adequada ao fluxo, não só decrement.

Para alterar várias linhas, use updateMany e confira a contagem:

ts
const resultado = await prisma.livro.updateMany({
  where: { estoque: 0 },
  data: { status: "ESGOTADO" },
});

console.log(resultado);
{ count: 0 }

Zero não é falha técnica: nenhum livro correspondia ao filtro no estado testado. O resultado torna visível quantas linhas mudaram.

Delete usa uma chave única

Remova o registro de teste e selecione apenas o identificador de negócio:

ts
const removido = await prisma.livro.delete({
  where: { slug: "poncia-vicencio" },
  select: { slug: true },
});

console.log("DELETE", removido);
DELETE { slug: 'poncia-vicencio' }

delete devolve o registro removido conforme o select. Para exclusão em massa, deleteMany aceita filtros e retorna count. Nunca troque uma operação única por deleteMany({}) só para evitar erro: objeto vazio corresponde a todas as linhas.

Uma transação agrupa operações dependentes

Quando duas mudanças precisam confirmar juntas, use $transaction. Este exemplo reduz estoque e cria uma reserva simplificada apenas se as duas operações forem válidas:

ts
const [livroAtualizado, totalPublicados] = await prisma.$transaction([
  prisma.livro.update({
    where: { slug: "olhos-dagua" },
    data: { estoque: { decrement: 1 } },
    select: { slug: true, estoque: true },
  }),
  prisma.livro.count({ where: { status: "PUBLICADO" } }),
]);

console.log(livroAtualizado, totalPublicados);

As operações em array são executadas dentro de uma transação. Se uma falha, o grupo é revertido. Para lógica que depende do resultado intermediário, Prisma também oferece interactive transaction; mantenha o bloco curto para não segurar conexão e locks por tempo desnecessário.

P2025 aparece quando a linha necessária não existe

Depois de remover poncia-vicencio, execute o mesmo delete com um slug ausente:

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

try {
  await prisma.livro.delete({
    where: { slug: "livro-que-nao-existe" },
  });
} catch (erro) {
  if (erro instanceof Prisma.PrismaClientKnownRequestError) {
    console.error({ name: erro.name, code: erro.code });
  }
}
{ name: 'PrismaClientKnownRequestError', code: 'P2025' }

O código significa que a operação dependia de registros que não foram encontrados. Numa API, traduza a ausência para o comportamento do endpoint, como HTTP 404. Não devolva a stack do adapter. Se DELETE deve ser idempotente, avalie deleteMany({ where: { slug } }) e use count para saber se algo mudou.

Dados externos ainda precisam de uma porta de entrada

Os tipos do Client protegem o código compilado, não o JSON recebido pela rede. Este atalho entrega ao ORM campos que talvez a pessoa nem pudesse alterar:

ts
// Evite: origem e autorização dos campos ficam invisíveis.
await prisma.livro.create({ data: req.body });

Valide o body em runtime e monte um objeto explícito. Aqui, entrada representa o resultado que já passou por essa validação:

ts
type EntradaLivroValidada = {
  titulo: string;
  slug: string;
  preco: string;
  estoque: number;
};

function montarDados(
  entrada: EntradaLivroValidada,
  autorIdPermitido: number,
) {
  return {
    titulo: entrada.titulo,
    slug: entrada.slug,
    preco: entrada.preco,
    estoque: entrada.estoque,
    autorId: autorIdPermitido,
  };
}

const entrada = {
  titulo: "Ponciá Vicêncio",
  slug: "poncia-vicencio",
  preco: "54.90",
  estoque: 6,
};

const data = montarDados(entrada, 1);
console.log(data);
{ titulo: 'Ponciá Vicêncio', slug: 'poncia-vicencio', preco: '54.90', estoque: 6, autorId: 1 }

Depois, entregue exatamente esse objeto ao Client:

ts
await prisma.livro.create({ data });

Esse desenho impede o cliente HTTP de escolher autorId ou status sem permissão. Prisma continua aplicando o schema; a aplicação aplica identidade, autorização e regras do caso de uso.

Missão: faça o CRUD deixar rastros verificáveis

Crie Categoria com nome @unique. Escreva um script que crie “Ficção”, leia por nome, altere para “Ficção brasileira” e remova. Em cada etapa, selecione só nome e imprima o retorno.

Seu critério de sucesso é uma sequência de quatro saídas e uma quinta prova: depois do delete, findUnique retorna null; repetir delete produz P2025 e é tratado. Acrescente um updateMany com filtro que não encontra nada e confirme { count: 0 }.

Quando o CRUD de uma tabela estiver claro, siga para relações no Prisma. É ali que include, nested write e foreign key passam a coordenar mais de um model.

  • prisma client
  • crud prisma
  • create
  • findmany
  • update
  • delete

Perguntas frequentes

Qual método do Prisma corresponde ao SELECT?
Depende do resultado esperado. findUnique procura por campo único, findFirst devolve o primeiro registro compatível e findMany devolve uma lista. Select escolhe quais campos retornam.
Update do Prisma altera várias linhas?
Update identifica um registro por condição única. Para várias linhas, use updateMany, que devolve a contagem afetada. Revise o where antes de uma alteração em massa.
Por que delete lança P2025?
Delete espera encontrar o registro único solicitado. Se ele não existe, a operação necessária não pode ser concluída e Prisma devolve P2025. Para exclusão idempotente, você pode avaliar deleteMany e sua contagem.
Posso passar req.body direto em data?
Não é uma prática segura. Valide formato e autorização e monte data apenas com campos permitidos. Os tipos do Prisma não substituem validação de dados recebidos pela rede.

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 — CRUD — prisma.io
  2. Prisma Client — select fields — prisma.io
  3. Prisma Client — transactions — prisma.io
  4. Prisma Client — error reference — prisma.io

Continue por aqui