Skip to content

Connecting to Databases

Next.js runs in serverless and edge environments where database connections behave differently than traditional servers. This topic covers how to configure database connections for reliability and performance.

In serverless environments, each request may run on a different server instance. Creating a new database connection per request is slow and can exhaust connection limits. Connection pooling and proper connection management are essential.

flowchart LR
R1[Request 1] --> P[Connection Pool]
R2[Request 2] --> P
R3[Request 3] --> P
P --> C1[(Connection 1)]
P --> C2[(Connection 2)]
P --> C3[(Connection 3)]
P --> CN[(Connection N)]
C1 --> DB[(Database)]
C2 --> DB
C3 --> DB

The pool maintains a set of persistent connections. Each request borrows a connection, uses it, and returns it.

The standard pattern is to create the database client once and reuse it across requests:

lib/db.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const db = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db

This works because:

  • In development, the module is hot-reloaded — storing on globalThis prevents creating new instances on every reload
  • In production, the singleton is cached in the module scope
.env
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
lib/db.ts
import { PrismaClient } from '@prisma/client'
const db = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
})

For serverless deployments (Vercel, Netlify), use a connection pooler:

.env
# Use the pooler URL instead of the direct connection
DATABASE_URL="postgresql://user:password@pooler.example.com:6543/mydb?pgbouncer=true"
lib/prisma.ts
import { PrismaClient } from '@prisma/client'
export const db = new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query'] : [],
})

Prisma Accelerate provides global caching and connection pooling:

Terminal window
DATABASE_URL="prisma://accelerate.prisma-data.net/?api_key=YOUR_KEY"
lib/prisma.ts
import { PrismaClient } from '@prisma/extension-accelerate'
const db = new PrismaClient().$extends(accelerate)
app/actions/users.ts
"use server"
import { db } from '@/lib/db'
export async function getUsers() {
// db is reused — no new connection created per request
return db.user.findMany({ take: 10 })
}
DatabaseConnectionBest for
PostgreSQLpg + @prisma/clientProduction apps, complex queries
MySQLmysql2 + @prisma/clientMySQL-compatible hosts (PlanetScale)
SQLiteNative Prisma supportDevelopment, prototyping
MongoDBmongoose or PrismaDocument data models
Turso (libsql)@libsql/clientEdge-compatible SQLite
  • Creating new PrismaClient per request — Exhausts connection limits. Always use the singleton pattern.
  • Hardcoding connection strings — Use environment variables. Never commit secrets.
  • Ignoring connection limits — Serverless databases have connection limits. Use a pooler.
  • Not using connection timeouts — Slow queries can hold connections indefinitely.
  • Use the global singleton pattern for database clients
  • Always use environment variables for connection strings
  • Use a connection pooler for serverless deployments
  • Enable query logging only in development
  • Set connection timeouts to prevent hung connections
  • Use prisma generate after schema changes

Database connection management in Next.js requires the singleton pattern for reliability and connection pooling for serverless environments. The global PrismaClient pattern ensures you don’t exhaust connections while keeping queries fast.