Skip to main content
Technology & EngineeringOrm220 lines

Prisma

Prisma ORM for type-safe database access with auto-generated client, migrations, and schema-first modeling in TypeScript/Node.js

Quick Summary32 lines
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 lines
Paste into your CLAUDE.md or agent config

Prisma — 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 globalThis to ensure one client per process.

  • Using prisma migrate dev in productionmigrate dev can reset the database and regenerate migration history. Production deployments must use prisma 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 select to return only the fields you need, reducing data transfer and memory usage.

  • String-concatenated $queryRaw calls — 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 without include in 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 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).

Common Pitfalls

  • N+1 queries: Forgetting include or select with relations causes separate queries per record. Always eagerly load relations you need.
  • Connection pool exhaustion: Creating multiple PrismaClient instances (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 $queryRaw injection: Always use tagged template literals ($queryRaw\...``) — never string concatenation — to prevent SQL injection.
  • Forgetting migrate deploy in production: Running migrate dev in production resets the database. Always use migrate deploy.
  • Large batch operations: createMany doesn't return created records on all databases. For bulk inserts needing return values, use $transaction with individual creates or raw SQL.

Install this skill directly: skilldb add orm-skills

Get CLI access →