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.Load the Rust engine binary (~2-4MB)
- 2.Establish a new database connection
- 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
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.
Zero abstraction. You write SQL, you get results. Fastest possible performance. Use tagged template literals for parameterized queries to avoid injection.
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.
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.
Keep exploring the stack