Skip to content
Discord Get Started

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.

  • A DB9 database (create one)
  • Node.js 18+
  • Next.js 14+ (App Router recommended)
Terminal
db9 create --name nextjs-app

Get the connection string:

Terminal
db9 db status nextjs-app

Set the connection string as an environment variable:

.env.local
DATABASE_URL="postgresql://nextjs-app.admin:YOUR_PASSWORD@pg.db9.io:5433/postgres?sslmode=require"

Prisma passes 100% of DB9 compatibility tests (89/89). See the full Prisma guide for detailed coverage.

Terminal
npx create-next-app@latest nextjs-db9 --typescript --app
cd nextjs-db9
npm install prisma @prisma/client
npx prisma init
prisma/schema.prisma
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):

Terminal
npx prisma db push

Generate the client:

Terminal
npx prisma generate

Create a shared Prisma instance to avoid connection exhaustion during development:

lib/prisma.ts
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;
}
app/users/page.tsx
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>
);
}
app/users/actions.ts
'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');
}
app/api/users/route.ts
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 100% of DB9 compatibility tests (75/75). See the full Drizzle guide for detailed coverage.

Terminal
npx create-next-app@latest nextjs-db9 --typescript --app
cd nextjs-db9
npm install drizzle-orm pg
npm install -D drizzle-kit @types/pg
lib/schema.ts
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(),
});
lib/db.ts
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);
app/users/page.tsx
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>
);
}
app/users/actions.ts
'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:

lib/db.ts
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;
app/api/users/route.ts
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);
}

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:

TypeScript
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.

  • 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 required: Use sslmode=require in the connection string for DB9’s hosted service.
  • Port 5433: DB9 uses port 5433, not the default PostgreSQL port 5432.
  • 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_URL in Vercel’s environment variables. The connection works from Vercel Serverless Functions (Node.js runtime).

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.

DB9 uses port 5433, not 5432. Verify your DATABASE_URL includes the correct port.

DB9 requires pgwire (TCP), which is not available in Edge Runtime. Add export const runtime = 'nodejs' to any route or page that queries DB9.

DB9 has limited information_schema support. Use prisma db push for schema sync or manage tables with raw SQL. See the Prisma guide for details.

Similar to Prisma, use raw SQL for DDL operations. See the Drizzle guide for details.