obah sylva

Connecting Next.js to a Database: A Practical Guide

Share this article

Connect Next.js to a database through an ORM (Prisma or Drizzle) paired with a connection pooler, not a direct connection string. Serverless deployments spin up a new function instance for every burst of traffic, and each instance opening its own database connection is exactly how a Postgres database with a default 100-connection limit gets exhausted during a normal traffic spike.

Why This Is Harder Than It Looks

A traditional server keeps a persistent connection pool alive: 10–20 connections serve thousands of requests because they’re reused. Next.js deployed to Vercel, AWS Lambda, or similar platforms doesn’t get that luxury — each function invocation is stateless, connection pools can’t be shared across invocations, and under load, hundreds of simultaneous invocations can each try to open a fresh connection at once. This is the single most common production incident for teams deploying Next.js with a traditional database driver.

Step 1: Pick an ORM

Prisma and Drizzle are the two dominant choices for a Next.js project in 2026, and the decision mostly comes down to deployment target and bundle size:

  • Prisma generates a type-safe client from a schema definition language, manages migrations through its own CLI, and offers Prisma Accelerate for HTTP-based global connection pooling on serverless and edge deployments. It’s the gentler learning curve and the more batteries-included option, at the cost of a considerably larger client bundle.
  • Drizzle writes SQL-like queries directly in TypeScript with no query-interpretation layer in between, ships a client roughly a tenth the size of Prisma’s, and has stronger native support for serverless-first databases like Neon, Turso, and Cloudflare D1. For teams deploying to the edge or optimizing cold-start time, it’s increasingly the lighter default.

Both support PostgreSQL, MySQL, and SQLite; Prisma adds native support for MongoDB and SQL Server, while Drizzle leans harder into edge-native databases. Neither choice is wrong — they optimize for different constraints.

Step 2: Use a Singleton Client

In development, Next.js’s hot-reload can re-instantiate your database client on every file save unless you guard against it, quietly leaking local connections. The standard fix is a global singleton:

const globalForPrisma = globalThis;
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== "production") {
  globalForPrisma.prisma = prisma;
}

This ensures the same client instance is reused across hot-reloads locally, and a fresh instance is created cleanly per cold start in production.

Step 3: Put a Pooler Between Next.js and the Database

This is the step that actually solves the connection exhaustion problem, and it’s not optional in a serverless deployment:

  • PgBouncer is the traditional standalone connection pooler — it sits between your application and Postgres, queuing and reusing connections so hundreds of function invocations share a much smaller real connection count.
  • Managed poolers from your database provider (Supabase’s session pooler, Neon’s built-in pooling) achieve the same result without self-hosting infrastructure — generally the simpler starting point.
  • HTTP-based drivers like @neondatabase/serverless sidestep the problem differently, connecting over HTTP instead of a long-lived TCP connection, which removes the pooling requirement entirely for compatible databases.

For Prisma specifically, keep both a pooled connection string (for the running application) and a direct connection string (for running migrations) in your environment variables — migrations should bypass the pooler.

Step 4: Fetch Data From Server Components, Mutate With Server Actions

The App Router’s model changes where database code actually lives. Reads typically happen directly inside Server Components, which can query the database during render without an API layer in between. Writes go through Server Actions, which handle the mutation and revalidation in one function instead of a separate API route plus a client-side fetch.

Production Checklist

  • Use migrate deploy (Prisma) or generated SQL migrations (Drizzle) in production — never db push or schema sync commands meant for local development.
  • Set an explicit, conservative connection limit for serverless (Prisma’s guidance starts at connection_limit=1 and scales up from measured need, not a guess).
  • Index for your actual query patterns, not preemptively for every column — unused indexes cost write performance for no read benefit.
  • Monitor pool utilization in production; connection exhaustion under real traffic looks fine in every local test and only shows up under concurrent load.

The Bottom Line

Connecting Next.js to a database isn’t just an ORM choice — it’s a connection-management problem that only shows up under real traffic. Pick Prisma or Drizzle based on your deployment target and bundle constraints, put a pooler or an HTTP driver between your app and the database from day one, and keep reads in Server Components and writes in Server Actions so the App Router’s model works with you instead of around a bolted-on API layer.

Prisma vs Drizzle: Quick Comparison

PrismaDrizzle
Client bundle sizeLargerRoughly a tenth the size
Query styleGenerated type-safe clientSQL-like queries in TypeScript
Serverless/edge poolingPrisma Accelerate (HTTP-based)Strong native support for Neon, Turso, Cloudflare D1
Database supportPostgres, MySQL, SQLite, MongoDB, SQL ServerPostgres, MySQL, SQLite, edge-native databases
Best fitTeams wanting a batteries-included, gentler learning curveEdge deployments and teams optimizing cold-start time

Should I use Prisma or Drizzle for a new Next.js project?

Prisma if you want a gentler learning curve and batteries-included tooling. Drizzle if you are deploying to the edge or optimizing cold-start time, since its client is roughly a tenth the size of Prisma’s.

Why does my database run out of connections in production but not locally?

Local testing rarely produces the concurrent load that causes connection exhaustion. In production, serverless platforms spin up many function instances under real traffic, and each one opening its own database connection is what exhausts the limit — a connection pooler or HTTP-based driver fixes this.

Do I need a connection pooler if I use an HTTP-based driver?

No. HTTP-based drivers like @neondatabase/serverless connect over HTTP instead of a long-lived TCP connection, which removes the pooling requirement entirely for compatible databases.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top