Prisma ORM patterns for TypeScript backends — schema design, query optimization, transactions, pagination, and critical traps like updateMany returning count not records, $transaction timeouts, migrate dev resetting the DB, @updatedAt skipped on bulk writes, and serverless connection exhaustion.
Copy the install command and let the AI configure it · recommended for beginners
Please install the "prisma-patterns" skill from askskill: 1. Download https://raw.githubusercontent.com/affaan-m/ECC/main/skills/prisma-patterns/SKILL.md 2. Save it as ~/.claude/skills/prisma-patterns/SKILL.md 3. Reload skills and tell me it's ready
Production patterns and non-obvious traps for Prisma ORM in TypeScript backends. Tested against Prisma 5.x and 6.x. Some behaviors differ from Prisma 4.
Check the Prisma version before applying version-specific patterns:
npx prisma --version
Prisma 5 introduced relationJoins, which can load relations via JOIN rather than separate queries depending on query strategy and configuration. The omit field modifier and prisma.$extends Client Extensions API were also added. Note: relationJoins can cause row explosion on large 1:N relations or deep nested include — benchmark both approaches when relations may return many rows per parent.
updateMany, deleteMany, or any bulk operation| Strategy | Use When | Avoid When |
|---|---|---|
@default(cuid()) | Default choice — URL-safe, sortable, no collisions | Sequential IDs needed for external systems |
@default(uuid()) | Interoperability with non-Prisma systems required | High-write tables (random UUIDs fragment B-tree indexes) |
@default(autoincrement()) | Internal join tables, audit logs | Public-facing IDs (exposes record count) |
model User {
id String @id @default(cuid())
email String @unique // @unique already creates an index — no @@index needed
name String
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([createdAt])
@@index([deletedAt, createdAt]) // composite for soft-delete + sort queries
}
@@index on every foreign key and column used in WHERE or ORDER BY.deletedAt DateTime? upfront when soft delete is a foreseeable requirement — adding it later requires a migration on a live table.updatedAt @updatedAt is set automatically by Prisma on update and upsert only (see Anti-Patterns for bulk update trap).include vs selectinclude | select | |
|---|---|---|
| Returns | All scalar fields + specified relations | Only specified fields |
| Use when | You need most fields plus a relation | Hot paths, large tables, avoiding over-fetch |
| Performance | May over-fetch on wide tables | Minimal payload, faster on large datasets |
| Prisma 5 note | Uses JOIN by default (relationJoins) | Same |
// include — all columns + relation
const user = await prisma.user.findUnique({
where: { id },
include: { posts: { select: { id: true, title: true } } },
});
// select — explicit allowlist
const user = await prisma.user.findUnique({
where: { id },
select: { id: true, email: true, name: true },
});
Never return raw Prisma entities from API responses — map to response DTOs to control exposed fields:
// BAD: leaks passwordHash, deletedAt, internal fields
return await prisma.user.findUniqueOrThrow({ where: { id } });
// GOOD: explicit DTO mapping
const user = await prisma.user.findUniqueOrThrow({ where: { id } });
return { id: user.id, name: user.name, email: user.email };
| Situation | Use |
|---|---|
| Independent operations, no inter-dependency | Array form |
| Later step depends on earlier result | Interactive form |
| External calls (email, HTTP) involved | Outside transaction entirely |
// Array form — batched in one round trip
const [user, post] = await prisma.$transaction([
prisma.user.update({ where: { id }, data: { name } }),
prisma.post.create({ data: { title, authorId: id } }),
]);
// Interactive form — use tx client only, never the outer prisma client
…
Conduct systematic literature reviews with search planning, screening, synthesis, and citation checks.
Checks homelab network readiness before VLAN, DNS, firewall, or VPN changes.
Improve interface polish through spacing, typography, motion, and interaction details.
Design safer EMR/EHR workflows, prescribing modules, and accessible clinical data-entry interfaces.
Autonomously build higher-quality apps through iterative generator-evaluator agent workflows.
Research current facts, compare options, and produce evidence-based recommendations.