Prisma
Prisma ORM for type-safe database access with auto-generated client, migrations, and schema-first modeling in TypeScript/Node.js
You are an expert in Prisma ORM for database access and data modeling. You help developers design schemas, write efficient queries, manage migrations, and follow Prisma best practices.
## Key Points
- **Prisma Schema** (`schema.prisma`) — single source of truth for your data model
- **Prisma Client** — auto-generated, type-safe query builder
- **Prisma Migrate** — declarative migration system
- **Prisma Studio** — GUI for browsing and editing data
- **Use a singleton** for the Prisma Client to avoid exhausting database connections in development (hot reload).
- **Select only needed fields** with `select` instead of returning entire objects when you don't need all columns.
- **Add `@@index`** on foreign keys and frequently filtered/sorted columns in your schema.
- **Use interactive transactions** (`$transaction(async (tx) => {...})`) for multi-step operations that must be atomic.
- **Run `prisma generate`** in your CI/CD pipeline after installing dependencies so the client matches the schema.
- **Use `@updatedAt`** for automatic timestamp management instead of manually setting dates.
- **Keep schema migrations in version control** and use `prisma migrate deploy` in production (never `migrate dev`).
- **N+1 queries**: Forgetting `include` or `select` with relations causes separate queries per record. Always eagerly load relations you need.
## Quick Example
```bash
npm install prisma --save-dev
npm install @prisma/client
npx prisma init
```
```bash
npx prisma migrate dev --name init
npx prisma migrate deploy # production
npx prisma generate # regenerate client after schema changes
```skilldb get orm-skills/PrismaFull skill: 220 linesPrisma — Database Toolkit
You are an expert in Prisma ORM for database access and data modeling. You help developers design schemas, write efficient queries, manage migrations, and follow Prisma best practices.
Overview
Prisma is a next-generation ORM for Node.js and TypeScript that provides a declarative schema language, auto-generated type-safe client, and a powerful migration system. It supports PostgreSQL, MySQL, SQLite, SQL Server, MongoDB, and CockroachDB.
Key components:
- Prisma Schema (
schema.prisma) — single source of truth for your data model - Prisma Client — auto-generated, type-safe query builder
- Prisma Migrate — declarative migration system
- Prisma Studio — GUI for browsing and editing data
Setup & Configuration
Installation
npm install prisma --save-dev
npm install @prisma/client
npx prisma init
Schema Definition (prisma/schema.prisma)
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
profile Profile?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
}
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[]
createdAt DateTime @default(now())
@@index([authorId])
}
model Profile {
id Int @id @default(autoincrement())
bio String
user User @relation(fields: [userId], references: [id])
userId Int @unique
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}
Migrations
npx prisma migrate dev --name init
npx prisma migrate deploy # production
npx prisma generate # regenerate client after schema changes
Core Patterns
Client Instantiation (singleton)
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'warn', 'error'] : ['error'],
});
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
CRUD Operations
// Create
const user = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
posts: {
create: { title: 'First post', content: 'Hello world' },
},
},
include: { posts: true },
});
// Read with filtering and pagination
const users = await prisma.user.findMany({
where: {
email: { contains: '@example.com' },
posts: { some: { published: true } },
},
include: { posts: { where: { published: true } } },
orderBy: { createdAt: 'desc' },
skip: 0,
take: 10,
});
// Update
const updated = await prisma.user.update({
where: { id: 1 },
data: { name: 'Alice Updated' },
});
// Upsert
const upserted = await prisma.user.upsert({
where: { email: 'alice@example.com' },
update: { name: 'Alice' },
create: { email: 'alice@example.com', name: 'Alice' },
});
// Delete
await prisma.user.delete({ where: { id: 1 } });
Transactions
// Interactive transaction
const result = await prisma.$transaction(async (tx) => {
const user = await tx.user.create({ data: { email: 'bob@example.com', name: 'Bob' } });
const post = await tx.post.create({
data: { title: 'Hello', authorId: user.id, published: true },
});
return { user, post };
});
// Batch transaction
const [users, posts] = await prisma.$transaction([
prisma.user.findMany(),
prisma.post.findMany({ where: { published: true } }),
]);
Raw Queries
const result = await prisma.$queryRaw`
SELECT u.name, COUNT(p.id) as post_count
FROM "User" u
LEFT JOIN "Post" p ON p."authorId" = u.id
GROUP BY u.id
HAVING COUNT(p.id) > ${minPosts}
`;
Core Philosophy
Prisma inverts the traditional ORM workflow. Instead of defining models in application code and hoping they match the database, Prisma starts from a declarative schema file (schema.prisma) that is the single source of truth for both the database structure and the generated TypeScript client. When you change the schema and run prisma generate, the client is regenerated with types that exactly match the new schema. This eliminates the drift between database and application types that plagues traditional ORMs.
The generated Prisma Client is where the magic lives. Every model in your schema becomes a property on the client with fully typed methods for create, read, update, delete, and relation traversal. The types are not handwritten or approximate — they are mechanically derived from the schema, including nullability, default values, and relation cardinality. This means TypeScript catches schema-query mismatches at compile time, not at runtime in production. The tradeoff is that the client must be regenerated after every schema change, which is a build step that must be integrated into the development workflow and CI pipeline.
Prisma Migrate provides a declarative migration system that diffs the schema against the database and generates SQL migration files. These files are deterministic, reviewable, and version-controlled. The development workflow uses prisma migrate dev (which also applies the migration and regenerates the client), while production uses prisma migrate deploy (which only applies pending migrations). Confusing these two commands — running migrate dev in production — can reset the database, which is why understanding the distinction is critical.
Anti-Patterns
-
Creating multiple PrismaClient instances — each instance creates its own connection pool. In development with hot reload, this quickly exhausts database connections. Use the singleton pattern with
globalThisto ensure one client per process. -
Using
prisma migrate devin production —migrate devcan reset the database and regenerate migration history. Production deployments must useprisma migrate deploy, which only applies pending migrations without any destructive operations. -
Fetching entire objects when only a few fields are needed — Prisma returns all scalar fields by default. For large tables or high-frequency queries, use
selectto return only the fields you need, reducing data transfer and memory usage. -
String-concatenated
$queryRawcalls — using$queryRaw("SELECT * FROM users WHERE id = " + id)bypasses parameterization and creates SQL injection vulnerabilities. Always use tagged template literals:$queryRaw\SELECT * FROM users WHERE id = ${id}``. -
Ignoring N+1 queries from missing
include— accessing a relation property withoutincludein the original query triggers a separate database call per record. Always eagerly load relations you know you will access.
Best Practices
- Use a singleton for the Prisma Client to avoid exhausting database connections in development (hot reload).
- Select only needed fields with
selectinstead of returning entire objects when you don't need all columns. - Add
@@indexon foreign keys and frequently filtered/sorted columns in your schema. - Use interactive transactions (
$transaction(async (tx) => {...})) for multi-step operations that must be atomic. - Run
prisma generatein your CI/CD pipeline after installing dependencies so the client matches the schema. - Use
@updatedAtfor automatic timestamp management instead of manually setting dates. - Keep schema migrations in version control and use
prisma migrate deployin production (nevermigrate dev).
Common Pitfalls
- N+1 queries: Forgetting
includeorselectwith relations causes separate queries per record. Always eagerly load relations you need. - Connection pool exhaustion: Creating multiple
PrismaClientinstances (common with hot reload) starves the pool. Use the singleton pattern. - Implicit many-to-many naming: Prisma auto-creates join tables for implicit many-to-many relations. Use explicit join models if you need extra fields on the relation.
- String-based
$queryRawinjection: Always use tagged template literals ($queryRaw\...``) — never string concatenation — to prevent SQL injection. - Forgetting
migrate deployin production: Runningmigrate devin production resets the database. Always usemigrate deploy. - Large batch operations:
createManydoesn't return created records on all databases. For bulk inserts needing return values, use$transactionwith individual creates or raw SQL.
Install this skill directly: skilldb add orm-skills
Related Skills
Drizzle
Drizzle ORM for lightweight, type-safe SQL in TypeScript with zero dependencies, SQL-like syntax, and schema-as-code
Knex
Knex.js SQL query builder for Node.js with schema building, migrations, seed files, and support for PostgreSQL, MySQL, and SQLite
Kysely
Kysely type-safe SQL query builder for TypeScript with zero overhead, full autocompletion, and no code generation
Mikro ORM
MikroORM for TypeScript with Unit of Work, Identity Map, decorator-based entities, and support for SQL and MongoDB
Mongoose
Mongoose ODM for MongoDB with schema validation, middleware hooks, population, virtuals, and TypeScript support in Node.js
Sequelize
Sequelize ORM for promise-based SQL database access with model definitions, associations, migrations, and transaction support in Node.js