Erros Comuns no Prisma ORM: O que Evitar para Evitar Bugs e Melhorar o Desempenho

Erros Comuns no Prisma ORM: O que Evitar para Evitar Bugs e Melhorar o Desempenho





Erros Comuns em Prisma ORM que Você Deve Evitar

Prática e qualidade no backend

Erros comuns em Prisma ORM que você deve evitar

Se você usa Prisma no dia a dia, sabe que ele acelera muito. O problema é quando decisões pequenas (e repetidas)
viram custo, bugs difíceis e consultas desnecessárias. Abaixo eu separo os erros mais frequentes e como contornar.

1Ignorar modelagem e deixar o schema “genérico demais”

Um schema fraco custa caro: vira validação no lugar errado, consultas mais complexas e consistência
dependente da aplicação. Isso aparece quando você:

  • Permite null em campos que deveriam ser obrigatórios (ou o inverso: força obrigatório
    onde a regra de negócio não garante).
  • Não define relacionamentos com cardinalidade e campos explícitos (relation fields), o que
    dificulta queries e dificulta entender o domínio.
  • Deixa índices e unicidades para “depois”, e o banco começa a sofrer com filtros e joins frequentes.
Dica prática: trate o schema.prisma como parte do código de domínio.
Se a regra existe, ela deve refletir no banco (e não somente em validações do frontend/rota).

2Ficar sem controle de N+1 e carregar dados demais

O erro aqui não é “usar Prisma”. É montar o fluxo de dados de um jeito que o banco recebe
muitas consultas ou retorna colunas e relações desnecessárias. Os sinais mais comuns:

  • Loop no código chamando findUnique/findFirst para cada item de uma lista.
    Isso gera N consultas em vez de 1 (ou poucas).
  • Uso excessivo de include sem select e sem critério.
    Você carrega “tudo”, mesmo quando só precisa de poucos campos.
  • Consultas com filtros aplicados em memória após buscar os dados.
    O banco faz muito melhor quando o filtro vai no where.
Regra de ouro: pense na consulta como SQL. Se o resultado precisa ser reduzido,
reduza no banco; se precisa de relações, traga somente as relações e campos necessários.

3Atualizações parciais e inconsistentes (upsert sem pensar no conjunto)

É comum “resolver rápido” com upsert, mas isso exige atenção ao que compõe a integridade.
Os problemas aparecem quando:

  • O where do upsert não usa uma chave realmente única
    (ou seja: a identificação está fraca).
  • Você atualiza alguns campos, mas ignora campos derivados, contadores ou colunas que dependem do estado.
  • Você não considera concorrência: duas requisições ao mesmo tempo podem criar inconsistências
    caso a modelagem não garanta unicidade e atomicidade.
  • Você faz update em cascata manual (ex.: atualiza tabela A e depois B) sem transação.
  • Você tenta corrigir a regra após o fato, com “ajustes” que passam testes locais, mas falham em produção.
  • Falta um unique constraint (no schema) para garantir o que o código assume.

Correção típica: garanta unicidade no schema e use transação quando houver múltiplas escritas
que precisam ficar coerentes (ex.: registro principal + auditoria + atualização de estatísticas).

4Transactions e erros de tipo: confiar demais em runtime

Dois pontos que aparecem em manutenção: (1) transações sem estratégia e (2) confundir “tipo” e “valor”.
No Prisma, você ganha tipagem do client, mas ainda assim precisa garantir:

  • Tratamento correto de falha por inexistência (ex.: quando o registro não existe).
    Não presuma que update vai criar; ela falha.
  • Decisões com base em condições que podem mudar entre leitura e escrita (race conditions).
  • Uso de transações (prisma.$transaction) quando existem múltiplas operações dependentes.
    Sem isso, você pode gravar parte da alteração e deixar o restante inconsistente.
  • Cuidado com campos de data: comparações de DateTime exigem consistência de timezone
    e validação do formato.
Exemplo: atualizar carrinho e registrar evento
TypeScript
import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); export async function finalizarPedido(userId: string, total: number) { return prisma.$transaction(async (tx) => { // 1) Garanta que o carrinho/estado existe antes (e considere “lock” via estratégia no banco quando necessário) const carrinho = await tx.carrinho.findUnique({ where: { userId }, // precisa ser uma chave única real select: { id: true, status: true } }); if (!carrinho) { throw new Error("Carrinho não encontrado."); } if (carrinho.status !== "ABERTO") { throw new Error("Carrinho não está disponível para finalização."); } // 2) Atualize o registro principal const pedido = await tx.pedido.create({ data: { userId, total, status: "PAGO" }, select: { id: true } }); // 3) Registre evento/auditoria (escrita dependente) await tx.evento.create({ data: { pedidoId: pedido.id, tipo: "PEDIDO_FINALIZADO", detalhes: { total } } }); // 4) Atualize estado do carrinho await tx.carrinho.update({ where: { id: carrinho.id }, data: { status: "FINALIZADO" } }); return pedido; }); }
O que esse exemplo evita: gravar o pedido sem registrar o evento, ou finalizar carrinho sem manter o histórico.
Tudo fica coerente porque as operações dependem do mesmo “bloco” transacional.

Quer melhorar ainda mais seu Prisma?

Se você curtiu esse conteúdo e quer evoluir com práticas que evitam retrabalho, vale a pena ler os próximos posts: