Drizzle with Next.js
Integrate Drizzle with Next.js using Neon/Vercel Postgres, edge runtime, and connection pooling
When to Use
- Setting up Drizzle ORM in a Next.js application
- Connecting to PostgreSQL from serverless/edge environments
- Configuring connection pooling for Vercel deployments
- Using Drizzle in Server Components, Server Actions, and Route Handlers
Instructions
- Install dependencies for your database provider:
# Neon (serverless PostgreSQL)
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit
# Vercel Postgres
npm install drizzle-orm @vercel/postgres
npm install -D drizzle-kit
# node-postgres (standard)
npm install drizzle-orm pg
npm install -D drizzle-kit @types/pg
- Create the database client with singleton pattern for Next.js hot reload:
// src/db/index.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
For development with hot module reload, use a global singleton:
// src/db/index.ts
import { drizzle, NeonHttpDatabase } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const globalForDb = globalThis as unknown as {
db: NeonHttpDatabase<typeof schema>;
};
if (!globalForDb.db) {
const sql = neon(process.env.DATABASE_URL!);
globalForDb.db = drizzle(sql, { schema });
}
export const db = globalForDb.db;
- Use in Server Components — query directly:
// app/users/page.tsx
import { db } from '@/db';
import { users } from '@/db/schema';
export default async function UsersPage() {
const allUsers = await db.query.users.findMany({
orderBy: (users, { desc }) => [desc(users.createdAt)],
});
return <UserList users={allUsers} />;
}
- Use in Server Actions:
// app/users/actions.ts
'use server';
import { db } from '@/db';
import { users } from '@/db/schema';
import { eq } from 'drizzle-orm';
import { revalidatePath } from 'next/cache';
export async function updateUserName(userId: string, name: string) {
await db.update(users).set({ name }).where(eq(users.id, userId));
revalidatePath('/users');
}
- Use in Route Handlers:
// app/api/users/route.ts
import { db } from '@/db';
import { users } from '@/db/schema';
import { NextResponse } from 'next/server';
export async function GET() {
const allUsers = await db.query.users.findMany();
return NextResponse.json(allUsers);
}
- Configure drizzle-kit for migrations:
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
- Run migrations at build time or application start:
{
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio"
}
}
- Edge runtime compatibility — use the HTTP-based Neon driver for edge:
// For edge runtime (middleware, edge API routes)
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
// For Node.js runtime (standard API routes, Server Components)
import { drizzle } from 'drizzle-orm/neon-serverless';
import { Pool, neonConfig } from '@neondatabase/serverless';
Details
Next.js serverless functions create a new runtime per invocation. Without connection pooling, each invocation opens (and may not close) a database connection, exhausting the connection limit.
Driver selection:
neon-http— uses HTTP protocol, works in Edge Runtime, one query per request (no transactions), lowest latency for single queriesneon-serverless— uses WebSocket protocol, supports transactions, works in both Edge and Node.js runtimesnode-postgres— standard PostgreSQL driver, Node.js only, supports everything, requires connection pooling for serverless
Connection pooling strategies:
- Neon built-in pooler — append
?pooling=trueto the connection string. Uses PgBouncer on Neon's infrastructure - Vercel Postgres — built-in pooling via
@vercel/postgres - External PgBouncer — use a
DIRECT_URLfor migrations and a pooledDATABASE_URLfor runtime queries
Environment variables pattern:
# Pooled connection for runtime queries
DATABASE_URL="postgresql://user:pass@host/db?sslmode=require"
# Direct connection for migrations (bypasses pooler)
DIRECT_URL="postgresql://user:pass@direct-host/db?sslmode=require"
Trade-offs:
neon-httpis the fastest single-query driver but cannot run transactions — useneon-serverlessif you need multi-statement transactions- The global singleton pattern prevents connection exhaustion during development hot reload but is not needed in production
- Edge Runtime has no
fsaccess — run migrations via CLI or in a Node.js environment, not at edge request time
Source
https://orm.drizzle.team/docs/get-started-postgresql
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.