Prisma

Prisma is the most popular ORM in the TypeScript/Node.js ecosystem, built around one idea: your database schema generates a fully type-safe client. You declare models in a schema.prisma file, run prisma generate, and get a client where every query, every field, and every relation is typed — misspell a column or query a non-existent field and it's a compile error, not a runtime surprise.

That type-safety-from-schema approach, plus an unusually good developer experience (a schema you can read, a migration workflow that just works, a visual database browser), made Prisma the default database layer for a huge share of new TypeScript backends — Next.js apps, NestJS services, and standalone APIs alike.

TL;DR

Quick Example

The schema, and the type-safe queries it generates:

Core Concepts

The Schema Is the Source of Truth

schema.prisma declares your data model in a purpose-built, readable DSL — models, fields, types, relations, indexes, and constraints in one file. From it, Prisma:

  1. Generates the typed client (prisma generate) — the API your code calls.
  2. Generates migrations (prisma migrate) — the SQL DDL to evolve the database.
  3. Introspects existing databases (prisma db pull) — adopt Prisma onto a legacy schema by generating the schema from it.

The schema being a single readable artifact (versioned in git, reviewed in PRs) is a real ergonomic win over schema scattered across model-class annotations.

Type Safety Without Writing Types

This is Prisma's headline. Because the client is generated from your schema, the types are always correct and always current:

No manual interfaces, no drift between types and schema, no any. For TypeScript teams, this eliminates a whole category of runtime data-shape bugs.

Queries and Relations

Prisma's query API is method-based rather than SQL-string-based:

include loads relations in a batched way (avoiding the classic N+1); select projects columns. For the SQL Prisma can't express cleanly (complex analytics, database-specific features), $queryRaw drops to parameterized raw SQL.

The Migration Workflow

prisma migrate dev diffs your schema against migration history, generates the SQL, applies it, and regenerates the client — in one command during development. prisma migrate deploy applies pending migrations in production. The migrations are plain SQL files (reviewable, editable for tricky changes like data backfills), committed to git — standard migration discipline with excellent tooling.

Prisma vs Drizzle vs Others

The live debate is Prisma vs Drizzle. Drizzle is lighter, has no generation step, reads like SQL (appealing if you like SQL), and fits edge/serverless runtimes well. Prisma offers the more polished schema DSL, Studio, migration ergonomics, and a gentler on-ramp for those who'd rather not think in SQL. Both are excellent and type-safe — the choice is DSL-and-DX (Prisma) vs SQL-closeness-and-lightness (Drizzle). Prisma has been evolving toward a lighter, Rust-free client to close the runtime gap.

Common Mistakes

Instantiating PrismaClient Per Request

new PrismaClient() opens a connection pool. Creating one per request (or per hot-reload in Next.js dev) exhausts database connections fast. Instantiate once as a singleton and reuse it — the Next.js global-singleton pattern exists precisely for this.

Forgetting to Regenerate the Client

Change the schema but skip prisma generate and your types are stale — the client doesn't match the database. migrate dev regenerates automatically; manual schema edits need a manual generate. Wire it into your build.

N+1 Despite the Batching

Prisma batches included relations, but a manual loop of await prisma.order.findUnique() inside a .map is still N+1. Fetch with include/findMany up front rather than querying per item — the ORM N+1 rules still apply.

Ignoring Connection Limits in Serverless

Serverless functions (Lambda, edge) each open pools; hundreds of concurrent invocations overwhelm the database. Use Prisma's connection pooling / Accelerate, or a proxy (PgBouncer), and driver adapters for edge runtimes.

Over-fetching with Blind include

include: { orders: { include: { items: true } } } on a list endpoint can pull enormous nested payloads. Use select to fetch only needed fields, and paginate relations.

FAQ

Is Prisma a "real" ORM?

It's an ORM in the practical sense (maps DB to typed objects, handles relations and migrations) but architecturally closer to a type-safe query builder + client than a classic Active Record/Data Mapper — there are no model classes you call .save() on. That's a feature: it sidesteps much of the ORM complexity while keeping the ergonomics.

Prisma or Drizzle for a new project?

Both are strong. Choose Prisma for the best schema DSL, migration tooling, Studio, and a smoother path if you prefer not thinking in SQL. Choose Drizzle if you want SQL-like queries, no code-generation step, minimal runtime, and first-class edge/serverless fit. Neither is a wrong answer in 2026.

Does Prisma work with my database?

PostgreSQL, MySQL, SQLite, SQL Server, CockroachDB, and MongoDB are supported; PostgreSQL is the best-trodden path. The same client API works across them (with provider-specific features gated appropriately).

How does Prisma handle raw SQL?

$queryRaw (and $executeRaw) run parameterized raw SQL with tagged templates that keep inputs safe from injection. Use it for the queries Prisma's API can't express cleanly — analytics, window functions, database-specific operators. Common and encouraged for the 5% that needs it.

Is Prisma good for serverless and edge?

It's improved substantially — driver adapters support edge runtimes, and Accelerate handles connection pooling for serverless. Historically this was Prisma's weak spot (connection pool exhaustion, a heavy engine); it's now viable, though Drizzle remains the lighter-weight option for extreme edge constraints.

Related Topics

References