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

db9 create prints the connection string, with the admin password, once — copy it from the Connection String: line. It has this shape:

Output
postgresql://YOUR_TENANT_ID.admin:YOUR_PASSWORD@pg.d0000.db9.io:5432/postgres

YOUR_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:

.env.local
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.

Terminal
npx create-next-app@latest nextjs-db9 --typescript --app
cd nextjs-db9
npm install prisma@6 @prisma/client@6
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 });
}

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: Use sslmode=require in the connection string for DB9’s hosted service.
  • Host and port: pg.db9.io:5433 and the pg.d0000.db9.io:5432 that db9 create prints reach the same server; port 5432 works 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_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 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.

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

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