---
title: "Prisma Client"
slug: "prisma-client"
category: "backend"
tags: ["backend", "prisma", "typescript", "orm", "client", "lifecycle", "serverless"]
status: "stable"
last_updated: 2026-05-29
summary: "Instantiate PrismaClient as a singleton with a driver adapter, use generated types for safety, and manage cold starts in Lambda."
related:
  [
    "[[backend/prisma]]",
    "[[backend/prisma-schema]]",
    "[[backend/prisma-migrations]]",
    "[[backend/prisma-pooling]]",
    "[[backend/prisma-transactions]]",
    "[[backend/prisma-driver-adapters]]",
    "[[backend/prisma-client-extensions]]",
    "[[comparisons/prisma-vs-drizzle]]",
    "[[coding/typescript]]",
  ]
---

## Overview

The generated Prisma client is the fully typed query builder that Prisma emits from your `schema.prisma`. The client exposes one method per model (`prisma.user`, `prisma.order`, etc.) and infers return types from the query shape at compile time. You never write a DTO by hand for a standard query. In Prisma 7 you import `PrismaClient` from the generator's `output` path and construct it with a [[backend/prisma-driver-adapters|driver adapter]]. This page covers client instantiation, lifecycle management, logging, and [[backend/prisma-client-extensions|client extensions]].

## Run `prisma generate` after every schema change

The generated client is a build artifact. It must stay in sync with `schema.prisma`.

```json
{
  "scripts": {
    "postinstall": "prisma generate"
  }
}
```

- Wire `prisma generate` into `postinstall` so CI and fresh `npm install` always emit a current client.
- The Prisma 7 `prisma-client` generator writes to a required `output` directory in your source tree (for example `src/generated/prisma`), not `node_modules`. Add that directory to `.gitignore`; do not commit generated code.
- Adjust `postinstall` and your import path to match the `output` you set. See [[backend/prisma-schema]] for generator config.

## Use a singleton in long-running servers

In a standard Node server, instantiate `PrismaClient` once and reuse it for the lifetime of the process. Multiple instances create multiple connection pools.

```ts
// lib/prisma.ts
import { PrismaClient } from "../generated/prisma/client"
import { PrismaPg } from "@prisma/adapter-pg"

const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL })
const prisma = new PrismaClient({ adapter })

export default prisma
```

- In Prisma 7 (Rust engine removed), `new PrismaClient()` without a driver adapter throws. Pass the adapter for your database: `@prisma/adapter-pg` for [[backend/postgres]], `@prisma/adapter-libsql` for SQLite or Turso. See [[backend/prisma-driver-adapters]].
- Import `prisma` from this module everywhere. Never call `new PrismaClient()` inside a request handler or model function.

## Prevent hot-reload exhaustion in development

Next.js and Vite dev servers hot-reload modules on save, which creates a new `PrismaClient` instance on every reload. The old instance's connection pool stays open. Under a few minutes of active development you exhaust the Postgres connection limit.

```ts
// lib/prisma.ts
import { PrismaClient } from "../generated/prisma/client"
import { PrismaPg } from "@prisma/adapter-pg"

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }

const makeClient = () =>
  new PrismaClient({ adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }) })

export const prisma = globalForPrisma.prisma ?? makeClient()

if (process.env.NODE_ENV !== "production") {
  globalForPrisma.prisma = prisma
}
```

- Stash the instance on `globalThis`, which survives HMR.
- Never use the `globalThis` trick in production. A real production process does not hot-reload.

## Call `$connect` and `$disconnect` explicitly in Lambda

In serverless environments, each invocation may get a cold container. The client connects lazily by default, but long-running Lambda handlers benefit from explicit lifecycle control.

```ts
export const handler = async (event: APIGatewayEvent) => {
  await prisma.$connect()
  try {
    const result = await prisma.user.findUnique({ where: { id: event.pathParameters?.id } })
    return { statusCode: 200, body: JSON.stringify(result) }
  } finally {
    await prisma.$disconnect()
  }
}
```

- Use `$connect` before the first query when you want to fail fast on connection errors.
- Always `$disconnect` in `finally` so the container does not hold a connection across idle time.
- For high-throughput Lambdas, use a pooler instead. See [[backend/prisma-pooling]].

## Configure logging for query visibility

Prisma can log queries, query parameters, warnings, and errors. Enable selectively; logging every query in production generates noise and leaks parameter values to stdout.

```ts
const prisma = new PrismaClient({
  adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
  log: [
    { emit: "event", level: "query" },
    { emit: "stdout", level: "error" },
    { emit: "stdout", level: "warn" },
  ],
})

prisma.$on("query", (e) => {
  console.log(`${e.query} (${e.duration}ms)`)
})
```

- Use `emit: "event"` to route query logs to your observability pipeline rather than stdout.
- Log query duration to identify slow paths. Pair with [[backend/postgres-explain]] to diagnose them.
- Disable `"query"` logs in production unless you are actively debugging. The emitted parameters may contain PII.

## Use client extensions for cross-cutting concerns

`$extends` is the mechanism for soft-delete filters, audit logging, and multi-tenant row isolation. The `$use` middleware API was deprecated in v4.16.0 and removed in v6.14.0; it does not exist in Prisma 7. Use `$extends({ query: { ... } })` to intercept queries.

```ts
const prisma = base.$extends({
  query: {
    post: {
      findMany({ args, query }) {
        args.where = { ...args.where, deletedAt: null }
        return query(args)
      },
    },
  },
})
```

- `$extends` returns a new client; assign it and use that instance. The base client is unchanged.
- Do not use a query extension for caching. It intercepts every query, making cache invalidation unpredictable. Use the result component or a dedicated cache instead.
- For the full set of components (`query`, `result`, `model`, `client`), see [[backend/prisma-client-extensions]].

## Derive types from the generated client

Prisma generates payload types for every model and query shape. Use them instead of hand-written interfaces.

```ts
import { Prisma } from "./generated/prisma/client"

type UserWithOrders = Prisma.UserGetPayload<{
  include: { orders: true }
}>
```

- `GetPayload` narrows the return type to exactly the selected fields, including nested includes.
- Avoid `any` casts on query results. The generated types are the contract between the schema and the application. See [[coding/typescript]] for general type safety rules.

## Related

- [[backend/prisma]]
- [[backend/prisma-schema]]
- [[backend/prisma-migrations]]
- [[backend/prisma-pooling]]
- [[backend/prisma-transactions]]
- [[backend/prisma-driver-adapters]]
- [[backend/prisma-client-extensions]]
- [[comparisons/prisma-vs-drizzle]]
- [[coding/typescript]]
