Back to Articles
Prisma ORM Performance Guide for Node.js

Prisma ORM Performance Guide for Node.js

High-performance database operations with Prisma ORM


Table of Contents

  1. Query Batching
  2. Relation Loading
  3. Fluent API
  4. Bulk Operations
  5. Raw Queries
  6. Connection Pool Configuration
  7. Prisma Accelerate
  8. Transactions
  9. Middleware
  10. Quick Reference

Setup & Schema Quick Reference

Install & Initialize

npm install prisma --save-dev
npm install @prisma/client
npx prisma init

Datasource & Generator

// schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

Model Definition

model User {
  id    Int     @id @default(autoincrement())
  name  String
  email String  @unique
}

Field types: Int, String, Boolean, DateTime, Float, Decimal, Json.

Attributes: @id (primary key), @default (default value), @unique (unique constraint), @relation (model relations).

Relationships

// One-to-One
model User {
  id      Int      @id @default(autoincrement())
  profile Profile?
}
model Profile {
  id     Int  @id @default(autoincrement())
  userId Int  @unique
  user   User @relation(fields: [userId], references: [id])
}

// One-to-Many
model User {
  id    Int    @id @default(autoincrement())
  posts Post[]
}
model Post {
  id       Int  @id @default(autoincrement())
  authorId Int
  author   User @relation(fields: [authorId], references: [id])
}

// Many-to-Many
model Post {
  id         Int        @id @default(autoincrement())
  categories Category[]
}
model Category {
  id    Int    @id @default(autoincrement())
  posts Post[]
}

Migrate & Generate

npx prisma migrate dev --name init
npx prisma migrate deploy
npx prisma generate

Client Query Quick Reference

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Read with filter, relations, pagination, ordering
const users = await prisma.user.findMany({
  where: {
    isActive: true,
    AND: [{ email: { contains: '@example.com' } }],
  },
  include: { posts: true },
  orderBy: { createdAt: 'desc' },
  skip: 10,
  take: 5,
});

// Aggregation
const roleStats = await prisma.user.groupBy({
  by: ['role'],
  _count: { id: true },
  _avg: { age: true },
  _sum: { salary: true },
});

// Upsert
const user = await prisma.user.upsert({
  where: { email: 'alice@example.com' },
  update: { name: 'Alice Updated' },
  create: { email: 'alice@example.com', name: 'Alice' },
});

1. Query Batching

Prisma automatically batches queries in the same tick.

// Sent as single round-trip when possible
const [users, posts, comments] = await Promise.all([
  prisma.user.findMany(),
  prisma.post.findMany(),
  prisma.comment.findMany(),
]);

// DataLoader pattern built-in
const users = await Promise.all(
  userIds.map(id => prisma.user.findUnique({ where: { id } }))
);
// Batched into: SELECT * FROM users WHERE id IN (1, 2, 3, ...)

2. Relation Loading

Avoiding N+1

// BAD: N+1 (Lazy loading)
const users = await prisma.user.findMany();
for (const user of users) {
  const posts = await prisma.post.findMany({
    where: { authorId: user.id }
  });
}

// GOOD: include (Eager load relations)
const users = await prisma.user.findMany({
  include: {
    posts: true,
    profile: true,
  },
});

// GOOD: select with relations (only needed fields)
const users = await prisma.user.findMany({
  select: {
    id: true,
    name: true,
    posts: {
      select: {
        id: true,
        title: true,
      },
      take: 5,
    },
  },
});

Nested Includes

const users = await prisma.user.findMany({
  include: {
    posts: {
      include: {
        comments: {
          include: {
            author: true,
          },
        },
      },
    },
  },
});

3. Fluent API

// BAD: Causes N+1 if accessing posts later
const user = await prisma.user.findUnique({
  where: { id: 1 },
});
const posts = await prisma.post.findMany({
  where: { authorId: user.id },
});

// GOOD: Fluent API chains in single query
const posts = await prisma.user
  .findUnique({ where: { id: 1 } })
  .posts();

// With filtering
const publishedPosts = await prisma.user
  .findUnique({ where: { id: 1 } })
  .posts({ where: { published: true } });

4. Bulk Operations

createMany

const users = await prisma.user.createMany({
  data: [
    { name: 'User1', email: 'u1@example.com' },
    { name: 'User2', email: 'u2@example.com' },
    { name: 'User3', email: 'u3@example.com' },
  ],
  skipDuplicates: true,  // Ignore conflicts
});

updateMany

await prisma.user.updateMany({
  where: { active: false },
  data: { deletedAt: new Date() },
});

deleteMany

await prisma.post.deleteMany({
  where: {
    authorId: userId,
    published: false,
  },
});

Upsert

const user = await prisma.user.upsert({
  where: { email: 'user@example.com' },
  update: { name: 'Updated Name' },
  create: { email: 'user@example.com', name: 'New User' },
});

5. Raw Queries

// Raw query with type
interface UserWithCount {
  id: number;
  name: string;
  post_count: bigint;
}

const users = await prisma.$queryRaw<UserWithCount[]>`
  SELECT u.id, u.name, COUNT(p.id) as post_count
  FROM users u
  LEFT JOIN posts p ON u.id = p.author_id
  GROUP BY u.id
  HAVING COUNT(p.id) > 5
`;

// Raw execute (for INSERT, UPDATE, DELETE)
await prisma.$executeRaw`
  UPDATE users SET last_login = NOW() WHERE id = ${userId}
`;

// Parameterized queries
const email = 'user@example.com';
const user = await prisma.$queryRaw`
  SELECT * FROM users WHERE email = ${email}
`;

6. Connection Pool Configuration

Schema Configuration

// schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

// Connection URL with pool settings
// postgresql://user:pass@host:5432/db?connection_limit=20&pool_timeout=10

Programmatic Configuration

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient({
  datasources: {
    db: {
      url: process.env.DATABASE_URL,
    },
  },
  // Log slow queries
  log: [
    { level: 'query', emit: 'event' },
    { level: 'warn', emit: 'stdout' },
    { level: 'error', emit: 'stdout' },
  ],
});

// Log slow queries
prisma.$on('query', (e) => {
  if (e.duration > 100) {  // > 100ms
    console.log(`Slow query (${e.duration}ms): ${e.query}`);
  }
});

7. Prisma Accelerate

Edge caching with Prisma Accelerate.

// schema.prisma
datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")
  directUrl = env("DIRECT_DATABASE_URL")
}
// Caching with stale-while-revalidate
const users = await prisma.user.findMany({
  cacheStrategy: {
    ttl: 60,      // Cache for 60 seconds
    swr: 120,     // Serve stale for 120s while revalidating
  },
});

// Invalidate cache
await prisma.$accelerate.invalidate({
  tags: ['users'],
});

8. Transactions

Interactive Transaction

const result = await prisma.$transaction(async (tx) => {
  const user = await tx.user.create({
    data: { name: 'New User', email: 'new@example.com' },
  });

  await tx.post.create({
    data: { title: 'Welcome', authorId: user.id },
  });

  return user;
});

Batch Transaction

// Single round-trip
const [users, posts] = await prisma.$transaction([
  prisma.user.findMany(),
  prisma.post.findMany(),
]);

Transaction Options

await prisma.$transaction(
  async (tx) => {
    // ...
  },
  {
    maxWait: 5000,      // Max time to acquire connection
    timeout: 10000,     // Max transaction duration
    isolationLevel: 'Serializable',
  }
);

9. Middleware

Logging/Metrics

prisma.$use(async (params, next) => {
  const before = Date.now();
  const result = await next(params);
  const after = Date.now();

  console.log(`${params.model}.${params.action} took ${after - before}ms`);

  return result;
});

Soft Delete

prisma.$use(async (params, next) => {
  if (params.action === 'delete') {
    params.action = 'update';
    params.args.data = { deletedAt: new Date() };
  }

  if (params.action === 'deleteMany') {
    params.action = 'updateMany';
    params.args.data = { deletedAt: new Date() };
  }

  return next(params);
});

10. Quick Reference

N+1 Prevention

// Include relations
prisma.user.findMany({
  include: {
    posts: true,
    profile: true,
  },
});

// Fluent API
prisma.user.findUnique({ where: { id: 1 } }).posts();

ORM Comparison

┌─────────────────┬─────────────────────────────────────────────────┐
│ Feature         │ Prisma                                          │
├─────────────────┼─────────────────────────────────────────────────┤
│ N+1 Prevention  │ include: {}                                     │
│ Partial Select  │ select: {}                                      │
│ Bulk Insert     │ createMany()                                    │
│ Bulk Update     │ updateMany()                                    │
│ Prepared Stmt   │ Automatic                                       │
│ Raw SQL         │ $queryRaw``                                     │
│ Connection Pool │ URL params                                      │
│ Type Safety     │ Full                                            │
│ Bundle Size     │ ~300KB                                          │
└─────────────────┴─────────────────────────────────────────────────┘

Optimization Checklist

PRISMA PERFORMANCE:
├── [ ] Use include/select to avoid N+1
├── [ ] Use select for partial field loading
├── [ ] Use bulk operations (createMany, updateMany)
├── [ ] Configure connection pool in URL
├── [ ] Log slow queries for monitoring
├── [ ] Use transactions for multi-step operations
├── [ ] Consider Prisma Accelerate for caching
├── [ ] Use fluent API for relation queries
└── [ ] Add indexes on filtered/joined columns

Query Time Budget

TARGET: 5ms per query

BREAKDOWN:
├── Connection acquisition:  0.1ms (from pool)
├── Query parsing:           0.1ms
├── Planning:                0.2ms
├── Execution:               4.0ms
├── Result transfer:         0.6ms
└── TOTAL:                   5.0ms

Connection URL Parameters

postgresql://user:pass@host:5432/db?
  connection_limit=20&     # Max pool connections
  pool_timeout=10&         # Wait time for connection
  connect_timeout=5&       # Connection timeout
  statement_timeout=30000  # Query timeout (ms)