Skip to content

Prisma

Databases & CachingDatabase/ORMTypeScript

What it is

Prisma is a next-generation ORM with a declarative schema, generated type-safe client, and a migration system that produces reviewable SQL.

Model the database in schema.prisma, run a migration, and Prisma generates a fully typed client. Relations, filters, ordering and pagination are all type-checked.

Installation

npm install prisma --save-dev && npm install @prisma/client

Getting started

The smallest useful thing you can do with it, and what each part means.

Schema and generated client
model Book {
  id        Int      @id @default(autoincrement())
  title     String
  year      Int
  author    Author   @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())

  @@index([year])
}
The schema is the source of truth for both the migration and the generated types, so the client can never describe a column the database does not have.
Typed queries with relations
const books = await prisma.book.findMany({
  where: { year: { gte: 1990 }, title: { contains: 'Dune' } },
  include: { author: true },      // avoids a second query
  orderBy: { year: 'desc' },
  take: 10,
});

// books[0].author.name is typed; books[0].publisher is a compile error

const created = await prisma.book.create({
  data: { title: 'Dune', year: 1965, author: { connect: { id: 1 } } },
});
`include` shapes the return type, so accessing a relation you did not include is caught at compile time rather than returning undefined at runtime.

Advanced usage

Where the library earns its place over a simpler alternative.

Transactions and avoiding N+1
// Interactive transaction — rolls back if the callback throws.
const order = await prisma.$transaction(async (tx) => {
  const stock = await tx.stock.update({
    where: { sku },
    data: { quantity: { decrement: qty } },
  });
  if (stock.quantity < 0) throw new Error('insufficient stock');

  return tx.order.create({ data: { sku, qty } });
});

// Prisma batches relation loads, but nested loops still cost queries.
// Prefer one query with include over N queries in a map().
Using `decrement` keeps the update atomic in SQL. Reading the value into JavaScript and writing it back would lose concurrent decrements.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Unique constraint failed on the fields
Catch PrismaClientKnownRequestError and check error.code === 'P2002'; the meta field names the offending column.
Too many database connections in development
Hot reload creates a new client per reload. Cache it on globalThis outside production.

Best practices

  • Instantiate PrismaClient once and reuse it; a client per request exhausts the connection pool.
  • Use include or select to load relations rather than querying inside a loop.
  • Review the generated migration SQL before applying it in production.
  • Use select to fetch only needed columns on wide tables — it narrows the return type too.

Background

Why it exists, and what it was reacting to.

Prisma replaced its earlier GraphQL-centric product with a schema-first ORM. Generating the client from the schema means autocompletion reflects the actual database, which is why it became the default for TypeScript back ends.