Preset

postgres preset (typical Prisma backend)

Prisma most commonly targets PostgreSQL or MySQL. The 'postgres' preset is the right starting point for both. Use 'mysql' if your schema targets MySQL.

presetp50p95p99errorRate
postgres 5ms 80ms 200ms 0.1%
Why withLatencyAll doesn't work on PrismaClient

Prisma's model delegates (prisma.user, prisma.post) are not plain objects with async methods — they're Proxy-backed delegate objects. withLatencyAll(prisma, 'postgres') won't traverse the delegate chain. Instead, wrap the individual operations or create thin wrapper functions as shown below.

Quick start

Wrap individual Prisma operations

The most direct approach: wrap a lambda that calls the Prisma operation. The lambda is a plain async function, so slowdep can wrap it normally.

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

const prisma = new PrismaClient();

// Wrap each operation as a lambda — prisma.user.findUnique is not a standalone function
const findUserById = withLatency(
  (id: string) => prisma.user.findUnique({ where: { id } }),
  'postgres'
);

const createUser = withLatency(
  (data: { email: string; name: string }) => prisma.user.create({ data }),
  'postgres'
);

const findPostsByUser = withLatency(
  (userId: string) => prisma.post.findMany({
    where: { authorId: userId },
    orderBy: { createdAt: 'desc' },
    take: 20
  }),
  'postgres'
);

// Now use them like normal async functions
const user = await findUserById('clx1234abcde');
const posts = await findPostsByUser(user.id);
Wrapping a client

A helper pattern for wrapping many operations

When you have many operations to wrap, build a factory function to avoid repetition. This keeps your repository layer clean while applying latency consistently.

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

const prisma = new PrismaClient();

// Helper: adds latency to any prisma operation in non-production environments
function slow<T extends any[], R>(fn: (...args: T) => Promise<R>) {
  return process.env.NODE_ENV === 'production'
    ? fn
    : withLatency(fn, 'postgres');
}

// Build a typed repository with consistent latency
export const userRepo = {
  findById:    slow((id: string) => prisma.user.findUnique({ where: { id } })),
  findByEmail: slow((email: string) => prisma.user.findUnique({ where: { email } })),
  create:      slow((data: { email: string; name: string }) => prisma.user.create({ data })),
  update:      slow((id: string, data: Partial<{ name: string }>) =>
                 prisma.user.update({ where: { id }, data })),
  delete:      slow((id: string) => prisma.user.delete({ where: { id } })),
  findMany:    slow((args?: { take?: number; skip?: number }) =>
                 prisma.user.findMany(args)),
};

// Clean, typed, realistic — use userRepo throughout your service
const user = await userRepo.findByEmail('alice@example.com');
Testing

Jest tests with Prisma and slowdep

The recommended pattern: mock the Prisma client, then apply slowdep to the mock. This gives you both realistic latency and controlled return values in the same test.

import { withLatency } from 'slowdep';
import { getUserProfile } from '../src/services/userService';

test('getUserProfile returns profile within 500ms', async () => {
  // Create a mock that returns a known user, with realistic latency
  const mockFindUnique = withLatency(
    jest.fn().mockResolvedValue({
      id: 'u1', email: 'alice@example.com', name: 'Alice'
    }),
    'postgres'
  );

  // Inject the slow mock into the service
  const profile = await getUserProfile('u1', {
    findUser: mockFindUnique
  });

  expect(profile.name).toBe('Alice');
  expect(mockFindUnique).toHaveBeenCalledWith({ where: { id: 'u1' } });
});
import { withLatency } from 'slowdep';
import { PrismaClient } from '@prisma/client';

test('transfer rolls back if second operation fails', async () => {
  const prisma = new PrismaClient();

  // Wrap the $transaction call itself — one latency hit for the whole transaction
  const slowTransaction = withLatency(
    prisma.$transaction.bind(prisma),
    { p50: 15, p99: 400 }  // transactions have higher latency
  );

  await expect(
    slowTransaction(async (tx) => {
      await tx.account.update({ where: { id: 'a1' }, data: { balance: { decrement: 100 } } });
      throw new Error('simulated failure mid-transaction');
    })
  ).rejects.toThrow('simulated failure');

  // Verify rollback: balance should be unchanged
  const account = await prisma.account.findUnique({ where: { id: 'a1' } });
  expect(account.balance).toBe(500);  // original value preserved

  await prisma.$disconnect();
});
Individual wrapping

Wrap $queryRaw for custom SQL

Prisma's $queryRaw and $executeRaw are plain async functions and can be wrapped directly.

import { PrismaClient, Prisma } from '@prisma/client';
import { withLatency } from 'slowdep';

const prisma = new PrismaClient();

// $queryRaw and $executeRaw are regular bound methods — wrap them directly
const slowQueryRaw   = withLatency(prisma.$queryRaw.bind(prisma), 'postgres');
const slowExecuteRaw = withLatency(prisma.$executeRaw.bind(prisma), 'postgres');

// Use the tagged template literal as normal
const results = await slowQueryRaw<{ id: string; count: bigint }[]>`
  SELECT user_id AS id, COUNT(*) AS count
  FROM orders
  WHERE created_at > NOW() - INTERVAL '30 days'
  GROUP BY user_id
  ORDER BY count DESC
  LIMIT 10
`;
Production safety

Environment-gated repository pattern

// src/db/userRepo.ts
import { PrismaClient } from '@prisma/client';
import { withLatency } from 'slowdep';

const prisma = new PrismaClient();
const isDev = process.env.NODE_ENV !== 'production';

const wrap = <T extends any[], R>(fn: (...args: T) => Promise<R>) =>
  isDev ? withLatency(fn, 'postgres') : fn;

export const userRepo = {
  findById:     wrap((id: string) => prisma.user.findUnique({ where: { id } })),
  findByEmail:  wrap((email: string) => prisma.user.findUnique({ where: { email } })),
  findMany:     wrap((take = 20, skip = 0) => prisma.user.findMany({ take, skip })),
  create:       wrap((data: { email: string; name: string }) => prisma.user.create({ data })),
  updateById:   wrap((id: string, data: { name?: string }) =>
                  prisma.user.update({ where: { id }, data })),
};
What to test

Scenarios this integration helps you verify

  • findUnique vs findFirst performance differencefindUnique uses a unique index; realistic latency helps you distinguish it from findFirst on a non-unique field
  • Eager loading cost — Prisma's include joins can be expensive; use slowdep to feel the latency before optimizing with select
  • Transaction timeout — nested transactions ($transaction) with interactive or batch mode have a default timeout of 5s; verify your code handles P2028 correctly
  • Retry on connection error — Prisma throws P1001 on connection failure; verify your service retries with exponential backoff
  • Batch query efficiencyprisma.$transaction([...]) batches queries in one round-trip; compare the latency vs sequential awaits
  • N+1 detection via timing — Prisma DevTools warns about N+1, but realistic latency makes the problem visible in test durations
TypeScript

Fully typed repository

import { PrismaClient } from '@prisma/client';
import type { User, Post } from '@prisma/client';
import { withLatency } from 'slowdep';

const prisma = new PrismaClient();

// Return type is inferred: Promise<User | null>
const findUser = withLatency(
  (id: string): Promise<User | null> => prisma.user.findUnique({ where: { id } }),
  'postgres'
);

// Return type: Promise<Post[]>
const findPosts = withLatency(
  (authorId: string): Promise<Post[]> => prisma.post.findMany({
    where: { authorId, published: true },
    orderBy: { createdAt: 'desc' }
  }),
  'postgres'
);

// TypeScript enforces correct arguments and return types throughout
const user: User | null = await findUser('clx1234');
const posts: Post[] = await findPosts('clx1234');