Prisma is the most-used TypeScript ORM in 2026 with roughly 5 million weekly npm downloads. If you are building a Next.js or Node.js app that talks to a database, Prisma turns SQL tables into typed TypeScript objects, generates migrations automatically, and gives you an autocomplete-friendly query API. This guide walks you from a blank folder to a working Prisma project with a real model, migration, and typed queries. No prior ORM experience required.
Quick 2026 verdict
Prisma is beginner-friendly because its schema DSL reads like plain text, autocomplete shows every relation as you type, and prisma migrate handles SQL for you. Best choice in 2026 for developers new to ORMs, especially on Next.js server-side setups. If you deploy to serverless or edge, expect cold-start friction and read the Drizzle comparison too.
Prerequisites
- Node.js 20 or newer
- Basic TypeScript familiarity (variable types, interfaces)
- A database: PostgreSQL, MySQL, or SQLite. SQLite is fine for learning and requires no server.
- A text editor: VS Code recommended (Prisma has an official extension)
Install Prisma
mkdir my-prisma-app cd my-prisma-app npm init -y npm install -D prisma typescript ts-node @types/node npm install @prisma/client npx prisma init --datasource-provider sqlite
The prisma init command creates two files: prisma/schema.prisma (the schema DSL) and .env (for the DATABASE_URL environment variable). SQLite uses a local file, so the default URL points to ./prisma/dev.db and requires no separate server.
Anatomy of a Prisma schema
Open prisma/schema.prisma. It starts with two blocks:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}The generator block tells Prisma what client code to produce (usually the JavaScript / TypeScript client). The datasource block declares your database type and where to find the connection string.
Define your first model
Add a User model at the bottom of the schema file:
model User {
id String @id @default(cuid())
email String @unique
name String?
createdAt DateTime @default(now())
posts Post[]
}
model Post {
id String @id @default(cuid())
title String
body String
published Boolean @default(false)
authorId String
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
}Notes on the syntax:
@idmarks the primary key@default(cuid())auto-generates a unique ID (cuid is safer than uuid for URL-friendliness)@uniquecreates a unique indexString?means nullable (email is required, name is optional)UserandPosthave a one-to-many relationship: one User has many Posts@relation(fields: [authorId], references: [id])tells Prisma the foreign key setup
Run your first migration
npx prisma migrate dev --name init
Prisma does three things: creates the SQL migration file in prisma/migrations/, applies it to your SQLite database (creating User and Post tables), and generates the Prisma Client with typed access to those tables. If you look at the terminal output, you will see the raw SQL Prisma produced.
Write your first queries
Create src/index.ts:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
// CREATE a user
const user = await prisma.user.create({
data: {
email: '[email protected]',
name: 'Alice',
},
});
console.log('Created user:', user);
// CREATE a post for that user
const post = await prisma.post.create({
data: {
title: 'Hello Prisma',
body: 'This is my first Prisma post.',
authorId: user.id,
published: true,
},
});
console.log('Created post:', post);
// READ all users with their posts
const users = await prisma.user.findMany({
include: { posts: true },
});
console.log('All users with posts:', JSON.stringify(users, null, 2));
// UPDATE a post
const updated = await prisma.post.update({
where: { id: post.id },
data: { title: 'Hello Prisma (edited)' },
});
console.log('Updated post:', updated);
// DELETE a post
await prisma.post.delete({ where: { id: post.id } });
console.log('Post deleted');
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());Run it:
npx ts-node src/index.ts
You just wrote CRUD (Create, Read, Update, Delete) in typed TypeScript with no SQL. Notice that prisma.user.create(), prisma.post.findMany(), and so on are all fully typed based on your schema. Autocomplete works. If you rename title to headline in the schema and re-run prisma migrate dev, TypeScript will flag every place you still reference title.
Explore data with Prisma Studio
npx prisma studio
Prisma Studio opens a browser-based table editor at http://localhost:5555 for exploring your data. You can browse rows, filter, edit values, and add new records without writing SQL. This is the fastest way to inspect what your code did during development.
Common queries you will use often
// Find by unique field
await prisma.user.findUnique({ where: { email: '[email protected]' } });
// Find with a filter
await prisma.post.findMany({
where: { published: true, author: { name: 'Alice' } },
orderBy: { createdAt: 'desc' },
take: 10,
});
// Count
await prisma.post.count({ where: { published: true } });
// Upsert (insert or update if exists)
await prisma.user.upsert({
where: { email: '[email protected]' },
create: { email: '[email protected]', name: 'Bob' },
update: { name: 'Bob (updated)' },
});
// Transactions
await prisma.$transaction([
prisma.user.create({ data: { email: '[email protected]' } }),
prisma.post.create({ data: { title: 'Hi', body: '', authorId: '...' } }),
]);
// Raw SQL when Prisma cannot express it
const results = await prisma.$queryRaw`SELECT COUNT(*) FROM Post WHERE published = ${true}`;Switch to PostgreSQL for production
When you deploy, most projects switch from SQLite to PostgreSQL. In schema.prisma:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}Update .env:
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb?schema=public"
Run npx prisma migrate dev --name switch_to_postgres. Prisma recreates the migration for Postgres syntax. Existing SQLite data is not migrated automatically; you handle that separately (dump SQLite, transform to Postgres, load).
Common beginner gotchas
- Forgetting to run
prisma generateafter schema changes. This is called automatically byprisma migrate, but if you edit the schema without migrating (which you should not do in production), the client stays outdated. Runnpx prisma generatemanually if in doubt. - Nullable vs required.
Stringis required.String?is nullable. Reviewers and IDEs will error on mismatch, so pay attention when adding new columns. - Relations without foreign keys.
author User @relation(fields: [authorId], references: [id])requires theauthorId Stringfield on the same model. Prisma cannot infer the foreign key column without it. - Reused PrismaClient in dev. In Next.js dev mode, hot reload creates new PrismaClient instances on every file change, which exhausts DB connections. Use a global singleton pattern in
lib/prisma.ts. - Ignoring migration files in Git. Migration files under
prisma/migrations/must be committed. They are your database history. Do not gitignore them. - Using db push in production.
prisma db pushis for prototyping; it can drop columns silently. Always useprisma migratefor anything shared.
Frequently Asked Questions
Do I need to know SQL to use Prisma?
Basic SQL helps but is not required. Prisma’s query API is high-level: findMany, create, update. For 90% of queries you never touch SQL. When you do need it, prisma.$queryRaw is available.
Can I use Prisma with an existing database?
Yes. Run npx prisma db pull to introspect an existing database and generate a schema.prisma from it. Then npx prisma generate gives you the typed client. You can continue with migrations from that point.
Does Prisma work with Next.js server actions?
Yes, natively. Import the Prisma client in a Server Action, run queries, return data. Just make sure your Prisma client is a singleton (import from lib/prisma.ts) to avoid connection exhaustion in dev.
How does Prisma handle migrations in production?
Use npx prisma migrate deploy in your production release pipeline. This applies any pending migrations without creating new ones. Never use prisma migrate dev or prisma db push in production; both can lead to data loss.
Is Prisma good for serverless (Vercel, AWS Lambda)?
Prisma works but has cold-start issues due to its Rust query engine. Use Prisma Accelerate or the Data Proxy for smooth serverless. Alternatively, consider Drizzle ORM if bundle size and cold starts are critical for your deployment.
How do I add validation on top of Prisma?
Prisma validates types (String, Int, etc.) but not business rules (email format, password strength). Layer Zod on top: validate incoming data with Zod, then pass validated data to Prisma. This is the standard 2026 Next.js pattern.
Related Modern Web Dev tutorials
- Prisma vs Drizzle vs Kysely 2026 (TypeScript ORM Comparison)
- Next.js 15 Complete Beginner Guide 2026 (First App)
- TypeScript Complete Beginner Tutorial 2026
- Supabase vs Firebase 2026 (Backend-as-a-Service Comparison)
