← Back to Learn

Learn · Database

What Prisma Actually Does

And When You Don't Need It

Every Next.js + Postgres tutorial says “install Prisma.” You run npx prisma init, define some models, push to the database, and suddenly you can query data with TypeScript autocomplete. It feels like magic. But what is actually happening?

What It Actually Is

Prisma is an ORM — Object Relational Mapper. That's a fancy name for a translation layer. Your code thinks in JavaScript objects. Your database thinks in SQL rows and columns. Prisma sits in the middle and translates between the two.

Instead of writing SELECT * FROM users WHERE email = '[email protected]', you write prisma.user.findUnique({ where: { email: '[email protected]' } }). Prisma generates the SQL, sends it to the database, and returns a typed JavaScript object. You get autocomplete, type checking, and never write a SQL string.

The Problem It Solves

Raw SQL is error-prone

Typo in a column name? You won't know until runtime. Prisma catches this at build time with generated types.

Migrations are painful

Adding a column means writing ALTER TABLE by hand, tracking migration files, hoping nothing breaks. Prisma auto-generates migrations from your schema.

No autocomplete for SQL

Your editor can't autocomplete SQL strings. With Prisma, you get full IntelliSense for every table, column, and relation.

SQL injection risk

Concatenating user input into SQL strings is how you get hacked. Prisma parameterizes every query automatically.

The Magic Trick Revealed

When you call prisma.user.findMany(), here's what actually happens:

  Your Code (TypeScript)
    │
    │  prisma.user.findMany({ where: { active: true } })
    │
    ▼
  Prisma Client (generated library)
    │  Translates JS method calls → SQL query
    │  Validates types at compile time
    │
    ▼
  Prisma Engine (Rust binary)
    │  Connection pooling
    │  Query optimization
    │  Runs the actual SQL
    │
    ▼
  Database (Postgres, MySQL, SQLite, etc.)
    │
    ▼
  SELECT * FROM "User" WHERE "active" = true;
    │
    ▼
  Result → typed JS object with full autocomplete ✓

The key insight: Prisma Client is generated code. Every time you change your schema and run prisma generate, it rebuilds a custom client library with types that match your exact database structure. That's why autocomplete works so well — it's not guessing, it's reading your schema.

The Schema File

Everything in Prisma starts with schema.prisma. This is your single source of truth for how your database looks. It defines your models (tables), fields (columns), relations, and which database you're connecting to.

// prisma/schema.prisma

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

generator client {
  provider = "prisma-client-js"
}

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        String   @id @default(cuid())
  title     String
  content   String?
  author    User     @relation(fields: [authorId], references: [id])
  authorId  String
  published Boolean  @default(false)
}

This isn't SQL. It's Prisma's own schema language. From this single file, Prisma generates your TypeScript types, your database migrations, and your query client. Change this file and everything else updates to match.

The 3 Commands You Actually Need

npx prisma generate

Reads your schema.prisma and generates the Prisma Client library with types matching your database. Run this after every schema change.

npx prisma migrate dev

Compares your schema to the database, generates a SQL migration file, and applies it. Creates the migration history so your team stays in sync.

npx prisma studio

Opens a browser-based GUI to view and edit your database. Like phpMyAdmin but modern and auto-generated from your schema. Great for debugging.

The Honest Catch: Cold Start Latency

Prisma bundles a Rust binary (the “Prisma Engine”) that handles query execution and connection pooling. In traditional servers, this boots once and stays warm. In serverless (Vercel, AWS Lambda), every cold start means:

  1. 1.Load the Rust engine binary (~2-4MB)
  2. 2.Establish a new database connection
  3. 3.Initialize the connection pool

This adds ~50ms+ of cold start latency on top of your function's startup time. For most apps, 50ms is invisible. But if you're running high-frequency serverless functions that scale to zero, those cold starts compound. Prisma offers Prisma Accelerate (a connection pooler and cache) to mitigate this, but it's an extra paid service.

When NOT to Use Prisma

  • Edge functions (Cloudflare Workers, Vercel Edge): The Rust engine doesn't run in edge runtimes. You need Prisma Accelerate or a different ORM entirely.
  • High-frequency serverless at scale: If you're running thousands of short-lived Lambda invocations per minute, cold start latency matters. Lighter ORMs win here.
  • Simple scripts or CLIs: If you just need to run 3 SQL queries in a script, Prisma's setup overhead (schema file, generate step, engine binary) is overkill.
  • When you need raw SQL performance: Complex queries with CTEs, window functions, or database-specific features are often easier and faster written as raw SQL.

The Alternatives

Drizzle ORMBEST FOR SERVERLESS

Lighter, faster cold starts, SQL-like syntax. No engine binary — it generates SQL directly in JS. Best choice if you want type-safety without the weight.

Raw SQL via postgres.jsBEST FOR PERFORMANCE

Zero abstraction. You write SQL, you get results. Fastest possible performance. Use tagged template literals for parameterized queries to avoid injection.

Supabase ClientBEST IF ALREADY ON SUPABASE

If you're already on Supabase, their JS client queries PostgREST directly. No ORM needed. You get type generation from your database schema via the CLI.

KyselyBEST FOR SQL LOVERS

Type-safe SQL query builder. Closer to raw SQL than Prisma but still gives you TypeScript autocomplete. No schema file — types come from your database.

The Verdict

Use Prisma if you're building a standard Next.js app with Postgres and want the best developer experience — type-safe queries, auto-generated migrations, and a visual database browser. The cold start cost is real but irrelevant for most apps.

Switch to Drizzle if you're deploying to edge runtimes or need minimal cold starts. Use raw SQL if you need maximum performance or complex queries. And if you're already on Supabase, you might not need an ORM at all.