Prisma ORM
Why Prisma feels like cheating, the N+1 trap, and where its abstraction leaks.
Prisma is a schema-first ORM for Node/TypeScript. You write a schema.prisma, run prisma generate, and get a fully typed client that knows your relations. No string-based query builder. Auto-completed where, select, include. Migrations generated from schema diffs.
The 30-second pitch
model User {
id String @id @default(cuid())
email String @unique
posts Post[]
}
model Post {
id String @id @default(cuid())
title String
authorId String
author User @relation(fields: [authorId], references: [id])
}After prisma generate:
const user = await prisma.user.findUnique({
where: { email: "yash@example.com" },
include: { posts: true },
});
// user.posts is typed as Post[], no anyWhy it wins
- Type safety end to end. Rename a column, every call site fails to compile.
- Migrations from schema diff. No manual SQL for 80 percent of changes.
- Query engine handles connection pooling, prepared statements, parameter binding.
- Studio: a built-in GUI for browsing data.
Where it leaks
- Complex queries (recursive CTEs, window functions, full-text search) need raw SQL via
$queryRaw. Type safety is partial there. - The Rust query engine adds 60-80 MB to your container image. Painful for tiny serverless functions. Prisma 5 introduced an alternative driver adapter mode to skip the engine.
- Migrations on large tables can lock for minutes. You need
CREATE INDEX CONCURRENTLYpatterns Prisma does not generate. Manually edit the migration SQL.
The N+1 trap
// BAD
const users = await prisma.user.findMany();
for (const u of users) {
u.posts = await prisma.post.findMany({ where: { authorId: u.id } });
}
// GOOD
const users = await prisma.user.findMany({ include: { posts: true } });Prisma's include and select resolve relations in one or two queries, not N. Use them. When you genuinely need a complex aggregation, drop to $queryRaw with a Prisma-typed template literal.
Learn more
- DocsPrisma DocumentationPrisma
- Docs
- DocsPostgreSQL DocsPostgreSQL