Next.js
Next.js connects to DB9 through any standard PostgreSQL driver over pgwire. DB9 is PostgreSQL-compatible, so Prisma, Drizzle, node-postgres (pg), and other drivers work without a custom adapter.
This guide shows how to set up a Next.js app with DB9 using the most common patterns: Prisma for schema-first workflows and Drizzle for TypeScript-first query building.
Prerequisites
Section titled “Prerequisites”- A DB9 database (create one)
- Node.js 18+
- Next.js 14+ (App Router recommended)
Create a DB9 Database
Section titled “Create a DB9 Database”db9 create --name nextjs-appdb9 create prints the connection string, with the admin password, once — copy it from the Connection String: line. It has this shape:
postgresql://YOUR_TENANT_ID.admin:YOUR_PASSWORD@pg.d0000.db9.io:5432/postgresYOUR_TENANT_ID is the tenant ID prefix in the generated connection string. Copy the username from the Connection String: line printed by db9 create or from db9 db connect. The examples below use pg.db9.io:5433, which reaches the same server; either host and port works. db9 db status nextjs-app shows the connection string without the password; if you lose it, db9 db reset-password nextjs-app sets a new password and prints a new connection string.
Set the connection string as an environment variable:
DATABASE_URL="postgresql://YOUR_TENANT_ID.admin:YOUR_PASSWORD@pg.db9.io:5433/postgres?sslmode=require"Prisma passes every test in DB9’s Prisma compatibility suite. See the full Prisma guide for detailed coverage.
npx create-next-app@latest nextjs-db9 --typescript --appcd nextjs-db9npm install prisma@6 @prisma/client@6npx prisma initSchema
Section titled “Schema”generator client { provider = "prisma-client-js"}
datasource db { provider = "postgresql" url = env("DATABASE_URL")}
model User { id Int @id @default(autoincrement()) email String @unique name String posts Post[] createdAt DateTime @default(now())}
model Post { id Int @id @default(autoincrement()) title String content String? published Boolean @default(false) author User @relation(fields: [authorId], references: [id]) authorId Int createdAt DateTime @default(now())}Create the tables with raw SQL (recommended over prisma migrate for DB9):
npx prisma db pushGenerate the client:
npx prisma generateSingleton client
Section titled “Singleton client”Create a shared Prisma instance to avoid connection exhaustion during development:
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') { globalForPrisma.prisma = prisma;}Server Component
Section titled “Server Component”import { prisma } from '@/lib/prisma';
export default async function UsersPage() { const users = await prisma.user.findMany({ include: { posts: true }, orderBy: { createdAt: 'desc' }, });
return ( <ul> {users.map((user) => ( <li key={user.id}> {user.name} — {user.posts.length} posts </li> ))} </ul> );}Server Action
Section titled “Server Action”'use server';
import { prisma } from '@/lib/prisma';import { revalidatePath } from 'next/cache';
export async function createUser(formData: FormData) { await prisma.user.create({ data: { email: formData.get('email') as string, name: formData.get('name') as string, }, }); revalidatePath('/users');}Route Handler
Section titled “Route Handler”import { prisma } from '@/lib/prisma';import { NextResponse } from 'next/server';
export async function GET() { const users = await prisma.user.findMany(); return NextResponse.json(users);}
export async function POST(request: Request) { const body = await request.json(); const user = await prisma.user.create({ data: body }); return NextResponse.json(user, { status: 201 });}Drizzle passes every test in DB9’s Drizzle compatibility suite. See the full Drizzle guide for detailed coverage.
npx create-next-app@latest nextjs-db9 --typescript --appcd nextjs-db9npm install drizzle-orm pgnpm install -D drizzle-kit @types/pgSchema
Section titled “Schema”import { pgTable, serial, varchar, text, boolean, integer, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', { id: serial('id').primaryKey(), email: varchar('email', { length: 255 }).unique().notNull(), name: varchar('name', { length: 100 }).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),});
export const posts = pgTable('posts', { id: serial('id').primaryKey(), title: varchar('title', { length: 500 }).notNull(), content: text('content'), published: boolean('published').default(false), authorId: integer('author_id').notNull().references(() => users.id), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),});Singleton client
Section titled “Singleton client”import { drizzle } from 'drizzle-orm/node-postgres';import { Pool } from 'pg';
const globalForDb = globalThis as unknown as { pool: Pool };
const pool = globalForDb.pool ?? new Pool({ connectionString: process.env.DATABASE_URL,});
if (process.env.NODE_ENV !== 'production') { globalForDb.pool = pool;}
export const db = drizzle(pool);Server Component
Section titled “Server Component”import { db } from '@/lib/db';import { users, posts } from '@/lib/schema';import { eq } from 'drizzle-orm';
export default async function UsersPage() { const allUsers = await db.select().from(users).orderBy(users.createdAt);
return ( <ul> {allUsers.map((user) => ( <li key={user.id}>{user.name} ({user.email})</li> ))} </ul> );}Server Action
Section titled “Server Action”'use server';
import { db } from '@/lib/db';import { users } from '@/lib/schema';import { revalidatePath } from 'next/cache';
export async function createUser(formData: FormData) { await db.insert(users).values({ email: formData.get('email') as string, name: formData.get('name') as string, }); revalidatePath('/users');}For minimal setups without an ORM:
import { Pool } from 'pg';
const globalForDb = globalThis as unknown as { pool: Pool };
const pool = globalForDb.pool ?? new Pool({ connectionString: process.env.DATABASE_URL,});
if (process.env.NODE_ENV !== 'production') { globalForDb.pool = pool;}
export default pool;import pool from '@/lib/db';import { NextResponse } from 'next/server';
export async function GET() { const { rows } = await pool.query('SELECT id, name, email FROM users ORDER BY id'); return NextResponse.json(rows);}Connection Singleton Pattern
Section titled “Connection Singleton Pattern”Next.js in development mode reloads modules on every request due to Hot Module Replacement. Without a singleton, each reload creates a new connection pool, quickly exhausting DB9’s per-tenant connection limit.
The globalForPrisma / globalForDb pattern stores the client on globalThis so it survives reloads:
const globalForPool = globalThis as unknown as { pool: Pool };const pool = globalForPool.pool ?? new Pool({ connectionString: process.env.DATABASE_URL });if (process.env.NODE_ENV !== 'production') { globalForPool.pool = pool;}In production, this is not needed — Next.js loads modules once — but the pattern is safe in both environments.
Production Notes
Section titled “Production Notes”- Server-side only: DB9 connections must happen on the server (Server Components, Server Actions, Route Handlers,
getServerSideProps). Never expose the connection string to the client. - Connection pooling: Start with 5–10 connections (
pool.max). DB9 handles per-tenant connection pooling on the server side. - TLS: Use
sslmode=requirein the connection string for DB9’s hosted service. - Host and port:
pg.db9.io:5433and thepg.d0000.db9.io:5432thatdb9 createprints reach the same server; port5432works on either host. - Edge Runtime: DB9 requires a TCP pgwire connection. The Node.js runtime is required — Edge Runtime does not support raw TCP sockets. Use
export const runtime = 'nodejs'in routes that access DB9.
- Vercel deployment: Set
DATABASE_URLin Vercel’s environment variables. The connection works from Vercel Serverless Functions (Node.js runtime).
Troubleshooting
Section titled “Troubleshooting”Connection pool exhaustion in development
Section titled “Connection pool exhaustion in development”If you see “too many connections” errors during development, ensure you’re using the singleton pattern described above. Restart the dev server to release stale connections.
ECONNREFUSED on port 5432
Section titled “ECONNREFUSED on port 5432”DB9 accepts port 5432 as well as 5433, so the client is not reaching DB9 at all. A refusal from localhost or 127.0.0.1 means the connection string never reached the driver — check that DATABASE_URL is set in the process that opens the connection.
Edge Runtime errors
Section titled “Edge Runtime errors”DB9 requires pgwire (TCP), which is not available in Edge Runtime. Add export const runtime = 'nodejs' to any route or page that queries DB9.
Run prisma db push only once
Section titled “Run prisma db push only once”prisma db push — used above — works for the initial schema creation against an empty
database. Do not run it again once the tables exist: Prisma does not recognise DB9’s spelling of
a serial column’s default and plans to recreate the sequence behind each autoincrement() id.
For a table named after its model, as here, that second push reports success while pointing id
at a new sequence that starts at 1, so inserts into a table that already has rows fail with
23505 duplicate key value. prisma migrate dev additionally fails at P3014, because DB9
rejects the hyphenated name Prisma gives its shadow database.
Create the initial schema with prisma db push, then apply later changes with raw SQL. See
A second db push fails on autoincrement() ids
for the details.
drizzle-kit push fails
Section titled “drizzle-kit push fails”drizzle-kit push works only for the first push into a database with no tables. After that its
introspection step reads pg_stat_user_indexes for each existing table, which DB9 does not
implement, so every later push exits without applying anything — on current releases without
printing an error either. Create tables and apply later changes with raw SQL, or manage the schema
from the start with drizzle-kit generate and drizzle-kit migrate. See the
Drizzle guide.
Next Pages
Section titled “Next Pages”- Prisma — full Prisma integration guide
- Drizzle — full Drizzle integration guide
- Connect — connection strings and authentication
- Production Checklist — deployment readiness
- TypeScript SDK — programmatic database management