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.
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:
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:
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:
console.log("CREATE", {
...criado,
preco: criado.preco.toString(),
});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:
const livro = await prisma.livro.findUnique({
where: { slug: "poncia-vicencio" },
select: { titulo: true, estoque: true },
});
console.log(livro);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:
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.
console.log(publicados.map((livro) => ({
titulo: livro.titulo,
preco: livro.preco.toString(),
autora: livro.autor.nome,
})));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:
const atualizado = await prisma.livro.update({
where: { slug: "poncia-vicencio" },
data: {
estoque: { decrement: 1 },
},
select: { slug: true, estoque: true },
});
console.log("UPDATE", atualizado);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:
const resultado = await prisma.livro.updateMany({
where: { estoque: 0 },
data: { status: "ESGOTADO" },
});
console.log(resultado);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:
const removido = await prisma.livro.delete({
where: { slug: "poncia-vicencio" },
select: { slug: true },
});
console.log("DELETE", removido);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:
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:
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 });
}
}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:
// 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:
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);Depois, entregue exatamente esse objeto ao Client:
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.
Perguntas frequentes
Qual método do Prisma corresponde ao SELECT?
Update do Prisma altera várias linhas?
Por que delete lança P2025?
Posso passar req.body direto em data?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
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
- Prisma Client — CRUD — prisma.io
- Prisma Client — select fields — prisma.io
- Prisma Client — transactions — prisma.io
- Prisma Client — error reference — prisma.io


