NodeJSPrisma ORM

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

Bash
npm install prisma --save-dev
npm install @prisma/client
npx prisma init       # creates prisma/schema.prisma and .env

prisma/schema.prisma

Text
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 }
The schema drives types, client, and migrations alike
Unlike code-first ORMs, Prisma centralizes everything in `schema.prisma`: it's the input for the generated client's **types**, for **migrations**, and for documentation. Change a field here, regenerate, and TypeScript immediately flags every query that no longer matches. This single-source-of-truth model is the core of Prisma's appeal.
Generate the client

Bash
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

JS
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.
Instantiate `PrismaClient` once — many instances exhaust connections
Each `new PrismaClient()` opens its own connection pool. Creating one per request (or, in dev with hot-reload, many on each reload) quickly **exhausts the database's connection limit**. Export a single shared instance. In serverless/hot-reload environments, cache it on `globalThis` so reloads reuse the same client. This is the Prisma version of the universal [pool-once rule](/nodejs/connection-pooling).
Type-safe queries

JS
// 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 } })
The client is generated, so everything is autocompleted and checked
`prisma.user` exists because you declared a `User` model; its `where`, `select`, and `data` shapes are typed to that model. Misspell a field or pass a wrong type and the TypeScript compiler rejects it before you run anything. `select` returns a precisely-typed subset. This compile-time safety across the whole data layer is what [Sequelize](/nodejs/sequelize)/[TypeORM](/nodejs/typeorm) approximate but Prisma delivers natively.
Relations — `include` and nested writes

JS
// 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' }] },
  },
})
`include` to load relations; nested writes to create them together
`include` pulls related records (Prisma issues efficient queries under the hood and assembles the typed result). Nested writes let you create/connect related rows in a single, atomic call — Prisma wraps them in a transaction automatically. Use `select` within `include` to avoid over-fetching, the same discipline as everywhere: fetch only what you need.
Transactions

JS
// 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

prisma migrate dev

Create + apply a migration in development

prisma migrate deploy

Apply pending migrations in production

prisma generate

Regenerate the typed client after schema changes

prisma studio

A GUI to browse/edit your data

prisma db pull

Introspect an existing DB into a schema

Run `prisma generate` after every schema change
The client is **generated code** — if you edit `schema.prisma` but forget to regenerate (`prisma generate`, which `migrate dev` runs for you), your types and queries reflect the *old* schema and you'll get confusing mismatches. Commit migrations to version control, use `migrate deploy` (never `migrate dev`) in production, and treat the schema file as the authoritative spec. [Migrations](/nodejs/database-migrations) covers the workflow.
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.$queryRaw when needed), and the generated client adds a build step.

Escape hatch: raw SQL when the query API can't express it
Prisma's query API covers the vast majority of needs, but for complex analytics or database-specific features it can't model, use `prisma.$queryRaw` with **tagged-template parameterization** (which stays injection-safe). You're not locked in — the relational power of [Postgres](/nodejs/postgresql) is always one raw query away.
Next
A decorator-based, TypeScript-native ORM: [TypeORM](/nodejs/typeorm).