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 driver adapter. This page covers client instantiation, lifecycle management, logging, and client extensions.
Run prisma generate after every schema change
The generated client is a build artifact. It must stay in sync with schema.prisma.
{
"scripts": {
"postinstall": "prisma generate"
}
}- Wire
prisma generateintopostinstallso CI and freshnpm installalways emit a current client. - The Prisma 7
prisma-clientgenerator writes to a requiredoutputdirectory in your source tree (for examplesrc/generated/prisma), notnode_modules. Add that directory to.gitignore; do not commit generated code. - Adjust
postinstalland your import path to match theoutputyou set. See 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.
// 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-pgfor postgres,@prisma/adapter-libsqlfor SQLite or Turso. See prisma-driver-adapters. - Import
prismafrom this module everywhere. Never callnew 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.
// 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
globalThistrick 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.
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
$connectbefore the first query when you want to fail fast on connection errors. - Always
$disconnectinfinallyso the container does not hold a connection across idle time. - For high-throughput Lambdas, use a pooler instead. See 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.
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 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.
const prisma = base.$extends({
query: {
post: {
findMany({ args, query }) {
args.where = { ...args.where, deletedAt: null }
return query(args)
},
},
},
})$extendsreturns 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 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.
import { Prisma } from "./generated/prisma/client"
type UserWithOrders = Prisma.UserGetPayload<{
include: { orders: true }
}>GetPayloadnarrows the return type to exactly the selected fields, including nested includes.- Avoid
anycasts on query results. The generated types are the contract between the schema and the application. See typescript for general type safety rules.