Prisma ORM
Prisma is a next-generation, TypeScript-first ORM that has become the default choice for many new Node projects. Its distinguishing idea: you define your data model in a single schema.prisma file, then run prisma generate to produce a fully type-safe client tailored to that schema. Queries are autocompleted and type-checked end to end — a wrong field name or type is a compile error, not a runtime surprise. This page covers the schema, the client, relations, and migrations.
The schema file — one source of truth
npm install prisma --save-dev npm install @prisma/client npx prisma init # creates prisma/schema.prisma and .env
prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
role Role @default(USER)
posts Post[] // 1-to-many relation
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
}
enum Role { USER ADMIN }Generate the client
npx prisma migrate dev --name init # create + apply a migration, then generate # or just regenerate the client after schema edits: npx prisma generate
db.js — one PrismaClient instance
import { PrismaClient } from '@prisma/client'
export const prisma = new PrismaClient({
log: process.env.NODE_ENV !== 'production' ? ['query'] : ['error'],
})
// PrismaClient manages its own connection pool — instantiate ONCE.Type-safe queries
// Create:
const user = await prisma.user.create({ data: { email: 'ada@x.com', name: 'Ada' } })
// Read — filters, selection, ordering, pagination, all autocompleted:
const users = await prisma.user.findMany({
where: { role: 'USER', email: { contains: '@x.com' } },
select: { id: true, name: true }, // typed projection
orderBy: { createdAt: 'desc' },
take: 20, skip: 40, // pagination
})
const one = await prisma.user.findUnique({ where: { id: Number(req.params.id) } })
// Update / delete:
await prisma.user.update({ where: { id: 1 }, data: { name: 'Ada L.' } })
await prisma.user.delete({ where: { id: 1 } })Relations — `include` and nested writes
// Eager-load related records:
const posts = await prisma.post.findMany({
include: { author: { select: { name: true } } },
})
// Nested write — create a user AND their first post in one call:
await prisma.user.create({
data: {
email: 'bob@x.com', name: 'Bob',
posts: { create: [{ title: 'Hello' }] },
},
})Transactions
// Sequential array — all succeed or all roll back:
await prisma.$transaction([
prisma.account.update({ where: { id: 1 }, data: { balance: { decrement: 100 } } }),
prisma.account.update({ where: { id: 2 }, data: { balance: { increment: 100 } } }),
])
// Interactive — logic between queries, with a tx client:
await prisma.$transaction(async (tx) => {
const acct = await tx.account.findUnique({ where: { id: 1 } })
if (acct.balance < 100) throw new Error('Insufficient funds') // → rollback
await tx.account.update({ where: { id: 1 }, data: { balance: { decrement: 100 } } })
})Migrations and tooling
Command | Does |
|---|---|
| Create + apply a migration in development |
| Apply pending migrations in production |
| Regenerate the typed client after schema changes |
| A GUI to browse/edit your data |
| Introspect an existing DB into a schema |
When Prisma fits
TypeScript projects wanting end-to-end type safety with minimal effort.
Teams that like a single declarative schema + first-class migrations.
Apps where developer experience and autocomplete speed up delivery.
Trade-off: less flexible for very complex/raw SQL (drop to
prisma.$queryRawwhen needed), and the generated client adds a build step.