📑 Contents Overview / TL;DR The Prisma 7 Shift Schema Modeling prisma.config.ts Driver Adapters Migrations Workflow Prisma Client API Client Extensions TypedSQL N+1 & Performance Postgres, Accelerate, Pulse Prisma vs Drizzle vs Kysely Upgrading v6 → v7 When to Use Prisma References

1. Overview / TL;DR

Prisma is a TypeScript ORM built around three pieces that work together: a declarative schema file (schema.prisma) that describes your data model, a migration engine that turns schema changes into versioned SQL migrations, and a fully generated, type-safe client that gives you autocomplete for every model, field, and relation in your database.

💡

What changed most recently: Prisma ORM 7 (November 2025) removed the Rust query engine that had powered every Prisma Client since v1. Queries now run through a TypeScript/WebAssembly compiler in-process — no more spawning a separate native binary. This is the single biggest architectural change in Prisma's history and it changes several things you set up by hand: driver adapters are now mandatory, config moved to a dedicated prisma.config.ts file, and the generator provider name itself changed.

PieceFile / CommandJob
Schemaprisma/schema.prismaSingle source of truth for models, fields, relations, enums, and datasource config
Migrateprisma migrate dev / deployGenerates and applies versioned SQL migration files from schema diffs
Clientprisma generate@prisma/clientType-safe query builder generated straight from your schema
Config v7+prisma.config.tsConsolidates schema path, migration settings, seed command, env loading

2. The Prisma 7 Shift — Rust-Free Architecture

From v1 through v6, every Prisma Client query was serialized, sent to a separate Rust binary (the "query engine"), executed there, and serialized back. This gave Prisma a fast, battle-tested core, but it also meant shipping a multi-megabyte native binary per platform/architecture — a real pain point on serverless and edge runtimes (Vercel Edge, Cloudflare Workers) that don't support native binaries at all.

What changed

  • The query engine was rewritten in TypeScript, with a WebAssembly query compiler running directly on the JS main thread — no separate engine process to spawn or communicate with.
  • Queries now go straight from Prisma Client to a JavaScript database driver (via a driver adapter) instead of round-tripping through Rust.
  • The generator provider changed from prisma-client-js to prisma-client, and generated output now defaults to a path in your source tree rather than node_modules/.prisma.

Measured impact

Bundle size

~14 MB → ~1.6 MB (roughly 90% smaller) — the native Rust binary is gone entirely from the deployed artifact.

Query throughput

Up to 3–3.4x faster query execution, from eliminating cross-language (JS ⇄ Rust) serialization overhead on every call.

Type-checking

Schema type evaluation cut by ~98%, query-evaluation types by ~45%, for roughly a 70% faster full tsc check — the result of a collaboration with the ArkType author on Prisma's generated types.

Deployability

No native binary means simpler, smaller deploys to Vercel Edge and Cloudflare Workers, which historically needed workarounds (or Prisma Accelerate) to run Prisma at all.

The Rust-free engine shipped as preview in 6.9.0, reached production-ready status by 6.16.0, and became the default (only) architecture in Prisma ORM 7.0.0.

3. Schema Modeling

Everything starts with schema.prisma — models map to tables, fields map to columns, and relations are declared on both sides so the client can generate nested types.

prisma/schema.prisma
generator client {
  provider = "prisma-client" // rust-free, ESM by default in v7
  output   = "./generated/prisma"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  role      Role     @default(USER)
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  tags      Tag[]    @relation("PostTags")
}

model Tag {
  id    Int    @id @default(autoincrement())
  name  String @unique
  posts Post[] @relation("PostTags")
}

enum Role {
  USER
  ADMIN
}

This one schema encodes all three standard relation shapes:

  • One-to-manyUserPost via authorId, the foreign key + @relation(fields:, references:) pair.
  • Many-to-manyPostTag via a named relation; Prisma manages the join table implicitly unless you model it explicitly.
  • EnumsRole becomes a native Postgres enum type (or a check-constrained column on databases without enum support).

4. prisma.config.ts new in v7

Settings that used to be scattered across schema.prisma, package.json, and CLI flags now live in one TypeScript config file at the project root — which also means they can be computed dynamically (e.g. loaded via dotenv, branched per environment).

prisma.config.ts
import "dotenv/config"
import { defineConfig, env } from "prisma/config"

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
    seed: "tsx prisma/seed.ts",
  },
  datasource: {
    url: env("DATABASE_URL"),
  },
})
⚠️

Environment variables are no longer auto-loaded from .env by the CLI in v7 — you now load them explicitly (as above, via dotenv/config) in prisma.config.ts.

5. Driver Adapters

With the Rust engine gone, Prisma Client no longer talks to the database on its own — it delegates the actual connection to a standard JavaScript database driver, wrapped in a thin driver adapter. This is now a required setup step, not an opt-in preview feature.

AdapterDatabaseWraps
@prisma/adapter-pgPostgreSQLpg (node-postgres)
@prisma/adapter-ppgPrisma PostgresPrisma's own serverless driver
@prisma/adapter-mariadbMySQL / MariaDBmariadb
@prisma/adapter-better-sqlite3SQLitebetter-sqlite3
@prisma/adapter-libsqlSQLite (Turso)libSQL
@prisma/adapter-node-mssqlSQL Servernode-mssql
Neon adapterPostgreSQL (serverless)Neon's HTTP driver
PlanetScale adapterMySQL (serverless)PlanetScale WebSocket driver
Cloudflare D1 adapterSQLite (edge)D1 binding API
Instantiating the client with an adapter
import { PrismaClient } from "./generated/prisma/client"
import { PrismaPg } from "@prisma/adapter-pg"

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

export const prisma = new PrismaClient({ adapter })
⚠️

Connection pool behavior now comes from the underlying Node.js driver, not from Prisma's old Rust-engine pool defaults — if you see new timeout errors after upgrading, tune the pool settings on the adapter/driver itself (e.g. pg's max, idleTimeoutMillis).

6. Migrations Workflow

CommandUse case
prisma migrate devLocal dev: diff schema vs. database, generate a new SQL migration file, apply it, regenerate the client
prisma migrate deployCI/CD & production: apply pending migrations only — never generates new ones
prisma migrate resetDrop the database, reapply all migrations from scratch, re-run seed
prisma db pushPrototype mode: push schema changes straight to the DB with no migration history — good for early prototyping, not for tracked schema history
prisma db seedRun the configured seed script directly
⚠️

v7 breaking change: migrate dev no longer auto-seeds the database and no longer accepts --skip-generate / --skip-seed flags — run prisma db seed as an explicit separate step.

7. Prisma Client Query API

CRUD basics

// Create
const user = await prisma.user.create({
  data: { email: "ada@example.com", name: "Ada" }
})

// Read, with a relation included
const withPosts = await prisma.user.findUnique({
  where: { id: user.id },
  include: { posts: true }
})

// Filtered read, only selecting some fields
const admins = await prisma.user.findMany({
  where: { role: "ADMIN" },
  select: { id: true, email: true },
  orderBy: { createdAt: "desc" },
  take: 20,
  skip: 0,
})

// Update
await prisma.user.update({
  where: { id: user.id },
  data: { name: "Ada Lovelace" }
})

// Delete
await prisma.user.delete({ where: { id: user.id } })

Transactions

The $transaction API batches multiple operations atomically — either every statement in the array succeeds, or none do:

const [post, updatedUser] = await prisma.$transaction([
  prisma.post.create({ data: { title: "Hello", authorId: user.id } }),
  prisma.user.update({ where: { id: user.id }, data: { postCount: { increment: 1 } } }),
])

For logic that needs to branch on an intermediate result, use the interactive form instead — pass an async callback that receives a transactional client:

await prisma.$transaction(async (tx) => {
  const account = await tx.account.findUnique({ where: { id: fromId } })
  if (account.balance < amount) throw new Error("Insufficient funds")
  await tx.account.update({ where: { id: fromId }, data: { balance: { decrement: amount } } })
  await tx.account.update({ where: { id: toId },   data: { balance: { increment: amount } } })
})

8. Client Extensions ($extends)

The older $use middleware API is deprecated. Client extensions replace it with a more granular, fully type-safe mechanism split into components: query hooks, result field/method additions, and client-level method additions.

const prismaWithSoftDelete = prisma.$extends({
  query: {
    post: {
      async delete({ args, query }) {
        // turn every post.delete() into a soft-delete update instead
        return prisma.post.update({ ...args, data: { deletedAt: new Date() } })
      },
    },
  },
  result: {
    post: {
      excerpt: {
        needs: { content: true },
        compute(post) {
          return post.content?.slice(0, 140) ?? ""
        },
      },
    },
  },
})

Query components intercept the lifecycle of a specific model's operations (or all models); result components add computed, type-safe fields to what a query returns; client components add brand-new top-level methods to the extended client instance.

9. TypedSQL — Raw SQL With Type Safety

Not every query maps cleanly to the fluent API — window functions, recursive CTEs, or vendor-specific SQL often need to be hand-written. TypedSQL lets you keep raw .sql files under prisma/sql/ and get generated, fully-typed functions for them.

prisma/sql/getTopAuthors.sql
-- @param {Int} $1:limit
SELECT u.id, u.name, COUNT(p.id) AS post_count
FROM "User" u
JOIN "Post" p ON p."authorId" = u.id
GROUP BY u.id
ORDER BY post_count DESC
LIMIT $1;
Usage after prisma generate
import { getTopAuthors } from "./generated/prisma/sql"

const topAuthors = await prisma.$queryRawTyped(getTopAuthors(10))
// topAuthors is fully typed: { id: number, name: string, post_count: bigint }[]

For ad-hoc parameterized SQL without a dedicated file, Prisma.sql is the safe-templating helper — it's what client-extension query components typically wrap raw SQL in to avoid string-concatenation injection risk.

10. N+1 Queries & Performance

The classic ORM footgun still applies: fetching a list, then lazily fetching each item's relation in a loop, produces N+1 round trips instead of one.

❌ N+1 pattern

for (const u of users) { u.posts = await prisma.post.findMany({ where: { authorId: u.id } }) } — one query per user, plus the original list query.

✅ Single query with include

prisma.user.findMany({ include: { posts: true } }) — Prisma batches this into efficient joined/batched SQL under the hood in one round trip.

Beyond query shape, the v7 rewrite itself is the biggest recent performance lever — removing the JS↔Rust serialization step cuts per-query overhead independent of how the query is written.

11. Prisma Postgres, Accelerate & Pulse

Beyond the open-source ORM, Prisma ships a hosted data platform with three pieces that solve different problems:

Prisma Postgres

A managed, serverless Postgres offering with no cold starts, provisioned via npm create db. As of v7 it also speaks the standard Postgres wire protocol, so it works with generic tools (TablePlus, Retool, Cloudflare Hyperdrive) — not just Prisma Client.

Accelerate

A global connection-pooling + query-caching layer, useful when a serverless/edge function would otherwise open (and exhaust) too many direct database connections. Uses a distinct prisma:// / prisma+postgres:// connection string.

Pulse

Change-data-capture as an API: subscribe to inserts/updates/deletes on a table and react to them in real time, without standing up your own logical-replication listener.

⚠️

Don't mix connection strings: a driver adapter like PrismaPg expects a plain Postgres connection string and will fail if handed a prisma:// or prisma+postgres:// Accelerate URL — Accelerate is applied as a separate client extension on top, not as a driver adapter.

12. Prisma vs Drizzle vs Kysely

🔺 Prisma
  • Schema-first (DSL, not SQL)
  • Full migration engine built in
  • Generated client + Prisma Studio GUI
  • Heaviest tooling, most "batteries included"
💧 Drizzle
  • TypeScript-first schema (code, not DSL)
  • SQL-like query builder, thinner abstraction
  • No generated client step — types come straight from your schema code
  • Smaller runtime footprint, closer to raw SQL
🏗️ Kysely
  • Not an ORM — a type-safe SQL query builder
  • You own the schema/migrations tooling separately
  • Maximum control over generated SQL
  • Best fit when you want Prisma-level type safety but zero abstraction magic

Prisma's pitch is developer experience and batteries-included tooling (schema DSL, migrations, Studio); Drizzle and Kysely trade some of that convenience for a thinner runtime and SQL that stays closer to what actually gets sent to the database.

13. Upgrading from v6 to v7

ChangeAction needed
Generator providerprisma-client-jsprisma-client, add explicit output path
Client import pathUpdate to the new generated output location instead of @prisma/client
Driver adaptersNow required for every database — install and wire up the matching @prisma/adapter-* package
Config fileAdd prisma.config.ts; move schema path, migration settings, seed command there
Env loading.env is no longer auto-loaded — load explicitly via dotenv/config
SeedingAuto-seed on migrate dev removed — run prisma db seed manually
Middleware ($use)Removed — port logic to Client Extensions ($extends)
Metrics preview featureRemoved entirely
Node.js / TypeScriptMinimum Node 20.19.0 (22.x recommended), TypeScript 5.4.0+ (5.9.x recommended)
npm install @prisma/client@7
npm install -D prisma@7

14. When to Use Prisma

✅ Reach for Prisma when…

You want a schema-first workflow with a built-in migration history, strong autocomplete without hand-writing types, a GUI (Prisma Studio) for poking at data, and you're fine with a generated-client build step in your toolchain.

Consider Drizzle/Kysely when…

You want the schema and types to live directly in TypeScript with no codegen step, need the smallest possible runtime, or want SQL output that stays maximally close to what you wrote.

Watch for after upgrading to v7…

Connection-pool tuning (now driver-owned, not Prisma-owned), and any code still using $use middleware or relying on auto-seeding / auto-loaded .env.

On serverless/edge…

The v7 rewrite plus driver adapters (Neon HTTP, PlanetScale WebSocket, D1) makes Prisma meaningfully more edge-friendly than it was pre-2026 — worth revisiting if you ruled it out for Workers/Edge Functions in the past.

References