Communitygithub.com

prisma-patterns

Padrões do Prisma ORM para backends TypeScript — design de schema, otimização de consultas, transações, paginação e armadilhas críticas como updateMany retornando contagem em vez de registros, timeouts do $transaction, migrate dev resetando o banco, @updatedAt ignorado em escritas em massa e exaustão de conexão em serverless. Use ao escrever um schema ou consulta Prisma, ou ao depurar transações, migrações ou limites de conexão serverless.

O que é prisma-patterns?

Este skill fornece um guia abrangente de padrões de produção e armadilhas ocultas para o Prisma ORM em backends TypeScript. Ele cobre desde o design de schema, estratégias de ID e defaults, até a escolha entre `include` e `select` para otimização de consultas. O guia detalha a seleção correta de transações (forma array vs. interativa), a implementação do singleton do PrismaClient para evitar múltiplas conexões, e a paginação baseada em cursor para feeds e grandes conjuntos de dados. Além disso, inclui exemplos práticos de soft delete, tratamento de erros com códigos específicos do Prisma, e configuração de pool de conexões para ambientes serverless como Vercel e AWS Lambda. Os antipadrões abordados são cruciais: `updateMany` retorna apenas uma contagem (`{ count: n }`), não os registros; transações interativas têm um timeout padrão de 5 segundos; `migrate dev` pode redefinir o banco de dados; e `@updatedAt` não é atualizado em operações em massa. O skill também alerta sobre a edição manual de arquivos de migração que quebram deploys futuros e a necessidade de migrações em várias etapas para alterações de schema que quebram a compatibilidade. Por fim, destaca que `findUniqueOrThrow` não filtra registros com soft delete, recomendando `findFirstOrThrow` como alternativa.

Funciona com~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/prisma-patterns

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Padrões Prisma

Padrões de produção e armadilhas não óbvias para o Prisma ORM em backends TypeScript.

Verifique sua versão antes de aplicar os padrões. A superfície da API do Prisma evoluiu através de grandes lançamentos:

npx prisma --version

Diferenças notáveis na API entre versões:

  • relationJoins pode carregar relações via JOIN em vez de consultas separadas, mas pode causar explosão de linhas em grandes relações 1:N ou include profundos — compare ambas as abordagens
  • O modificador de campo omit e a API de Extensões do Cliente prisma.$extends foram adicionados
  • Instalações mais novas: o pacote pode ser nomeado prisma em vez de @prisma/client; PrismaClient pode exigir um adaptador de driver (por exemplo, @prisma/adapter-pg); datasource.url pode ficar em prisma.config.ts em vez de schema.prisma
  • Comandos CLI (migrate dev, migrate deploy, generate) permanecem inalterados entre versões

Quando Ativar

  • Projetando ou modificando modelos e relações do schema Prisma
  • Escrevendo consultas, transações ou lógica de paginação
  • Usando updateMany, deleteMany ou qualquer operação em massa
  • Executando ou planejando migrações de banco de dados
  • Implantando em ambientes serverless (Vercel, Lambda, Cloudflare Workers)
  • Implementando soft delete ou filtragem de linha multi-inquilino

Conceitos Principais

Estratégia de ID

EstratégiaUse QuandoEvite Quando
@default(cuid())Escolha padrão — seguro para URL, ordenável, sem colisõesIDs sequenciais necessários para sistemas externos
@default(uuid())Interoperabilidade com sistemas não Prisma é necessáriaTabelas com muitas gravações (UUIDs aleatórios fragmentam índices B-tree)
@default(autoincrement())Tabelas de junção internas, logs de auditoriaIDs voltados para o público (expõe a contagem de registros)

Defaults de Schema

model User {
  id        String    @id @default(cuid())
  email     String    @unique  // @unique já cria um índice — nenhum @@index necessário
  name      String
  role      Role      @default(USER)
  posts     Post[]
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt
  deletedAt DateTime?

  @@index([createdAt])
  @@index([deletedAt, createdAt]) // composto para consultas de soft delete + ordenação
}
  • Adicione @@index em cada chave estrangeira e coluna usada em WHERE ou ORDER BY.
  • Declare deletedAt DateTime? antecipadamente quando soft delete for um requisito previsível — adicioná-lo depois requer uma migração em uma tabela ativa.
  • updatedAt @updatedAt é definido automaticamente pelo Prisma apenas em update e upsert (veja Antipadrões para a armadilha de atualização em massa).

include vs select

includeselect
RetornaTodos os campos escalares + relações especificadasApenas campos especificados
Use quandoVocê precisa da maioria dos campos mais uma relaçãoCaminhos críticos, tabelas grandes, evitando excesso de busca
PerformancePode buscar demais em tabelas largasPayload mínimo, mais rápido em grandes conjuntos de dados
Nota Prisma 5Usa JOIN por padrão (relationJoins)Mesmo
// include — todas as colunas + relação
const user = await prisma.user.findUnique({
  where: { id },
  include: { posts: { select: { id: true, title: true } } },
});

// select — lista de permissões explícita
const user = await prisma.user.findUnique({
  where: { id },
  select: { id: true, email: true, name: true },
});

Nunca retorne entidades Prisma brutas de respostas de API — mapeie para DTOs de resposta para controlar os campos expostos:

// RUIM: vaza passwordHash, deletedAt, campos internos
return await prisma.user.findUniqueOrThrow({ where: { id } });

// BOM: mapeamento explícito de DTO
const user = await prisma.user.findUniqueOrThrow({ where: { id } });
return { id: user.id, name: user.name, email: user.email };

Seleção de Forma de Transação

SituaçãoUse
Operações independentes, sem interdependênciaForma array
Etapa posterior depende do resultado anteriorForma interativa
Chamadas externas (email, HTTP) envolvidasFora da transação completamente
// Forma array — agrupada em uma única viagem de ida e volta
const [user, post] = await prisma.$transaction([
  prisma.user.update({ where: { id }, data: { name } }),
  prisma.post.create({ data: { title, authorId: id } }),
]);

// Forma interativa — use apenas o cliente tx, nunca o cliente prisma externo
const post = await prisma.$transaction(async (tx) => {
  const user = await tx.user.findUniqueOrThrow({ where: { id } });
  if (user.role !== 'ADMIN') throw new Error('Forbidden');
  return tx.post.create({ data: { title, authorId: user.id } });
});

Singleton do PrismaClient

Cada instância de PrismaClient abre seu próprio pool de conexões. Instancie uma vez.

// lib/prisma.ts

// Opção A — inicialização baseada em adaptador (necessária para instalações mais novas do Prisma)
import { PrismaClient } from '@prisma/client'; // ou o caminho do cliente gerado para sua configuração
import { PrismaPg } from '@prisma/adapter-pg';

function createPrismaClient() {
  const adapter = new PrismaPg({
    connectionString: process.env.DATABASE_URL!,
  });
  return new PrismaClient({
    adapter,
    log: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],
  });
}

const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };

export const prisma = globalForPrisma.prisma ?? createPrismaClient();

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;

// Opção B — inicialização direta (instalações mais antigas, nenhum adaptador necessário)
// import { PrismaClient } from '@prisma/client';
// export const prisma = globalForPrisma.prisma ?? new PrismaClient({ ... });

Use a Opção A se sua instalação do Prisma exigir um argumento adapter no construtor PrismaClient. Use a Opção B se new PrismaClient() funcionar sem argumentos. Deixe o compilador lhe dizer qual é a correta.

O padrão globalThis evita instâncias duplicadas durante recarga a quente (Next.js, nodemon, ts-node-dev).

Problema N+1

Carregar relações dentro de um loop emite uma consulta por linha.

// RUIM: N+1 — uma consulta extra por usuário
const users = await prisma.user.findMany();
for (const user of users) {
  const posts = await prisma.post.findMany({ where: { authorId: user.id } });
}

// BOM: consulta única
const users = await prisma.user.findMany({ include: { posts: true } });

Com relationJoins do Prisma 5+, a forma include usa um único JOIN. Em grandes conjuntos 1:N isso pode aumentar o tamanho do conjunto de resultados — compare ambas as abordagens se a relação puder retornar muitas linhas por pai.

Exemplos de Código

Paginação por Cursor (preferida para feeds e grandes conjuntos de dados)

async function getPosts(cursor?: string, limit = 20) {
  const items = await prisma.post.findMany({
    where: { published: true },
    orderBy: [
      { createdAt: 'desc' },
      { id: 'desc' }, // ordenação secundária evita paginação instável em carimbos de data/hora duplicados
    ],
    take: limit + 1,
    ...(cursor && { cursor: { id: cursor }, skip: 1 }),
  });

  const hasNextPage = items.length > limit;
  if (hasNextPage) items.pop();

  return { items, nextCursor: hasNextPage ? items[items.length - 1].id : null };
}

Busque limit + 1 e remova o último — maneira canônica de detectar hasNextPage sem uma consulta de contagem extra. Sempre inclua um campo único (por exemplo, id) como um orderBy secundário para evitar paginação instável quando várias linhas compartilham o mesmo carimbo de data/hora. Use paginação por deslocamento apenas quando os usuários precisam pular para páginas arbitrárias (tabelas de admin).

Soft Delete

// Sempre filtre explicitamente — não confie em middleware (esconde o comportamento, difícil de depurar)
const activeUsers = await prisma.user.findMany({ where: { deletedAt: null } });

await prisma.user.update({ where: { id }, data: { deletedAt: new Date() } });
await prisma.user.update({ where: { id }, data: { deletedAt: null } }); // restaurar

Tratamento de Erros

import { Prisma } from '@prisma/client'; // ou o caminho do cliente gerado para sua configuração

try {
  await prisma.user.create({ data: { email } });
} catch (e) {
  if (e instanceof Prisma.PrismaClientKnownRequestError) {
    if (e.code === 'P2002') throw new ConflictError('Email já existe');
    if (e.code === 'P2025') throw new NotFoundError('Registro não encontrado');
    if (e.code === 'P2003') throw new BadRequestError('O registro referenciado não existe');
  }
  throw e;
}

Códigos comuns: P2002 violação de unique · P2025 não encontrado · P2003 violação de chave estrangeira.

Capture no limite do serviço e traduza para erros de domínio. Nunca exponha mensagens brutas do Prisma para consumidores da API.

Pool de Conexões — Serverless

Incorpore os parâmetros de conexão diretamente em DATABASE_URL — concatenação de strings quebra se a URL já tiver parâmetros de consulta (por exemplo, ?schema=public):

# .env — preferido: incorporar parâmetros na URL
DATABASE_URL="postgresql://user:pass@host/db?connection_limit=1&pool_timeout=20"

# Com um pooler externo (PgBouncer, Supabase pooler)
DATABASE_URL="postgresql://user:pass@host/db?pgbouncer=true&connection_limit=1"
// Vercel, AWS Lambda e runtimes serverless similares:
// limitar o pool a 1 por instância; connection_limit e pool_timeout controlados via DATABASE_URL

// Configuração baseada em adaptador (se sua instalação do Prisma exigir um adaptador):
import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';

const prisma = new PrismaClient({
  adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
});

// Configuração direta (se sua instalação do Prisma não exigir um adaptador):
// const prisma = new PrismaClient();

Antipadrões

updateMany retorna uma contagem, não registros

// RUIM: resultado é { count: 2 } — users[0] é undefined
const users = await prisma.user.updateMany({ where: { role: 'GUEST' }, data: { role: 'USER' } });

// BOM: capture os IDs primeiro, depois atualize, então busque apenas as linhas afetadas
const targets = await prisma.user.findMany({
  where: { role: 'GUEST' },
  select: { id: true },
});
const ids = targets.map((u) => u.id);
await prisma.user.updateMany({ where: { id: { in: ids } }, data: { role: 'USER' } });
const updated = await prisma.user.findMany({ where: { id: { in: ids } } });

O mesmo se aplica a deleteMany — retorna { count: n }, nunca as linhas excluídas.

Forma interativa de $transaction expira após 5 segundos

// RUIM: chamada externa dentro da transação excede o padrão de 5s → "Transaction already closed"
await prisma.$transaction(async (tx) => {
  const user = await tx.user.findUniqueOrThrow({ where: { id } });
  await sendWelcomeEmail(user.email); // chamada externa
  await tx.user.update({ where: { id }, data: { emailSent: true } });
});

// BOM: chamadas externas fora da transação
const user = await prisma.user.findUniqueOrThrow({ where: { id } });
await sendWelcomeEmail(user.email);
await prisma.user.update({ where: { id }, data: { emailSent: true } });

// Aumente o timeout apenas quando o processamento em massa genuinamente precisar dele
await prisma.$transaction(async (tx) => { ... }, { timeout: 30_000 });

migrate dev pode redefinir o banco de dados

migrate dev detecta desvio de schema e pode solicitar a redefinição do banco, descartando todos os dados.

# NUNCA em ambientes de desenvolvimento compartilhado, staging ou produção
npx prisma migrate dev --name add_column

# Seguro em qualquer lugar, exceto desenvolvimento local solo
npx prisma migrate deploy

# Verifique o desvio sem aplicar
npx prisma migrate diff \
  --from-migrations ./prisma/migrations \
  --to-schema-datamodel ./prisma/schema.prisma \
  --shadow-database-url "$SHADOW_DATABASE_URL"

Editar manualmente um arquivo de migração quebra implantações futuras

O Prisma faz checksum de cada arquivo de migração. Editar após aplicar causa P3006 checksum mismatch em todos os ambientes onde a migração original já foi executada. Crie uma nova migração.

Alterações de schema que quebram requerem migração em várias etapas

Adicionar NOT NULL a uma coluna existente ou renomear uma coluna em uma migração bloqueará a tabela ou descartará dados. Use expandir-e-contratar:

# Passo 1: crie a migração localmente, depois implante
npx prisma migrate dev --name add_new_column   # apenas local
npx prisma migrate deploy                       # staging / produção
// Passo 2: preencha os dados (execute em um script ou job de migração, não no shell)
await prisma.user.updateMany({ data: { newColumn: derivedValue } });
# Passo 3: crie a migração da restrição NOT NULL localmente, depois implante
npx prisma migrate dev --name make_new_column_required  # apenas local
npx prisma migrate deploy                               # staging / produção

@updatedAt não é acionado em updateMany

@updatedAt é definido automaticamente apenas em update e upsert. Escritas em massa o deixam desatualizado.

// RUIM: updatedAt permanece com seu valor antigo
await prisma.post.updateMany({ where: { authorId }, data: { published: true } });

// BOM
await prisma.post.updateMany({
  where: { authorId },
  data: { published: true, updatedAt: new Date() },
});

Soft delete + findUniqueOrThrow vaza registros excluídos

findUniqueOrThrow lança P2025 apenas quando a linha não existe no banco. Linhas com soft delete ainda existem e são retornadas sem erro.

findUniqueOrThrow requer um campo de restrição unique em where — adicionar deletedAt: null junto com id quebra o tipo porque { id, deletedAt } não é uma restrição unique composta. Use findFirstOrThrow em vez disso.

// RUIM: retorna usuário com soft delete
const user = await prisma.user.findUniqueOrThrow({ where: { id } });

// RUIM: erro de tipo do Prisma — { id, deletedAt } não é uma restrição unique
const user = await prisma.user.findUniqueOrThrow({ where: { id, deletedAt: null } });

// BOM: findFirstOrThrow suporta condições where arbitrárias
const user = await prisma.user.findFirstOrThrow({ where: { id, deletedAt: null } });

deleteMany sem where exclui todas as linhas

// RUIM: limpa silenciosamente a tabela
await prisma.post.deleteMany();

// BOM
await prisma.post.deleteMany({ where: { authorId: userId } });

Melhores Práticas

RegraRazão
migrate deploy em CI/CD, migrate dev apenas localmentemigrate dev pode redefinir o banco em desvio
Mapear entidades para DTOs de respostaPrevine vazamento de campos internos
... [truncado para tradução]

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

Habilidades Relacionadas