Express / Hono
Express and Hono connect to DB9 through any standard PostgreSQL driver over pgwire. DB9 is PostgreSQL-compatible, so no special adapter is needed.
Prerequisites
Section titled “Prerequisites”- A DB9 database (create one)
- Node.js 18+
Create a DB9 Database
Section titled “Create a DB9 Database”db9 create --name express-appGet the connection string:
db9 db status express-appSet the connection string as an environment variable:
DATABASE_URL="postgresql://express-app.admin:YOUR_PASSWORD@pg.db9.io:5433/postgres?sslmode=require"mkdir express-db9 && cd express-db9npm init -ynpm install express @prisma/client dotenvnpm install -D prisma typescript @types/express @types/node ts-nodenpx prisma initnpx tsc --initSet DATABASE_URL in .env (see above), then define your 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 createdAt DateTime @default(now()) @map("created_at")
@@map("users")}Push the schema and generate the client:
npx prisma db pushnpx prisma generatemkdir express-db9 && cd express-db9npm init -ynpm install express pg dotenvnpm install -D typescript @types/express @types/pg @types/node ts-nodenpx tsc --initCreate the connection pool:
import { Pool } from 'pg';import 'dotenv/config';
export const pool = new Pool({ connectionString: process.env.DATABASE_URL,});Create the table:
import { pool } from './db';
async function setup() { await pool.query(` CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, name VARCHAR(100) NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() ) `); console.log('Table created'); await pool.end();}
setup();npx ts-node src/setup.tsmkdir hono-db9 && cd hono-db9npm init -ynpm install hono @hono/node-server drizzle-orm pg dotenvnpm install -D drizzle-kit @types/pg typescript @types/node ts-nodenpx tsc --initDefine the schema:
import { pgTable, serial, varchar, 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(),});Create the DB client:
import { drizzle } from 'drizzle-orm/node-postgres';import { Pool } from 'pg';import 'dotenv/config';
const pool = new Pool({ connectionString: process.env.DATABASE_URL,});
export const db = drizzle(pool);Create the table with raw SQL (recommended over drizzle-kit push for DB9):
import { Pool } from 'pg';import 'dotenv/config';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
async function setup() { await pool.query(` CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, name VARCHAR(100) NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() ) `); console.log('Table created'); await pool.end();}
setup();npx ts-node src/setup.tsCRUD Routes
Section titled “CRUD Routes”import express from 'express';import { PrismaClient } from '@prisma/client';import 'dotenv/config';
const app = express();const prisma = new PrismaClient();
app.use(express.json());
app.get('/users', async (_req, res) => { const users = await prisma.user.findMany({ orderBy: { createdAt: 'desc' }, }); res.json(users);});
app.post('/users', async (req, res) => { const user = await prisma.user.create({ data: { email: req.body.email, name: req.body.name }, }); res.status(201).json(user);});
app.get('/users/:id', async (req, res) => { const user = await prisma.user.findUnique({ where: { id: Number(req.params.id) }, }); if (!user) return res.status(404).json({ error: 'Not found' }); res.json(user);});
app.delete('/users/:id', async (req, res) => { await prisma.user.delete({ where: { id: Number(req.params.id) } }); res.status(204).end();});
app.listen(3000, () => console.log('Listening on http://localhost:3000'));import express from 'express';import { pool } from './db';import 'dotenv/config';
const app = express();app.use(express.json());
app.get('/users', async (_req, res) => { const { rows } = await pool.query( 'SELECT * FROM users ORDER BY created_at DESC' ); res.json(rows);});
app.post('/users', async (req, res) => { const { email, name } = req.body; const { rows } = await pool.query( 'INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *', [email, name] ); res.status(201).json(rows[0]);});
app.get('/users/:id', async (req, res) => { const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [ req.params.id, ]); if (!rows.length) return res.status(404).json({ error: 'Not found' }); res.json(rows[0]);});
app.delete('/users/:id', async (req, res) => { await pool.query('DELETE FROM users WHERE id = $1', [req.params.id]); res.status(204).end();});
app.listen(3000, () => console.log('Listening on http://localhost:3000'));import { Hono } from 'hono';import { serve } from '@hono/node-server';import { db } from './db';import { users } from './schema';import { eq } from 'drizzle-orm';
const app = new Hono();
app.get('/users', async (c) => { const allUsers = await db.select().from(users).orderBy(users.createdAt); return c.json(allUsers);});
app.post('/users', async (c) => { const body = await c.req.json(); const [user] = await db .insert(users) .values({ email: body.email, name: body.name }) .returning(); return c.json(user, 201);});
app.get('/users/:id', async (c) => { const id = Number(c.req.param('id')); const [user] = await db.select().from(users).where(eq(users.id, id)); if (!user) return c.json({ error: 'Not found' }, 404); return c.json(user);});
app.delete('/users/:id', async (c) => { const id = Number(c.req.param('id')); await db.delete(users).where(eq(users.id, id)); return c.body(null, 204);});
serve({ fetch: app.fetch, port: 3000 }, () => { console.log('Listening on http://localhost:3000');});Error Handling
Section titled “Error Handling”import { Prisma } from '@prisma/client';import { Request, Response, NextFunction } from 'express';
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => { if (err instanceof Prisma.PrismaClientKnownRequestError) { if (err.code === 'P2002') { return res.status(409).json({ error: 'Duplicate entry' }); } } console.error(err); res.status(500).json({ error: 'Internal server error' });});import { Request, Response, NextFunction } from 'express';
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => { if ((err as any).code === '23505') { return res.status(409).json({ error: 'Duplicate entry' }); } console.error(err); res.status(500).json({ error: 'Internal server error' });});app.onError((err, c) => { if ((err as any).code === '23505') { return c.json({ error: 'Duplicate entry' }, 409); } console.error(err); return c.json({ error: 'Internal server error' }, 500);});Production Notes
Section titled “Production Notes”- Port 5433: DB9 uses port 5433, not the default PostgreSQL port 5432. Double-check your
DATABASE_URL. - TLS required: Always include
sslmode=requirein the connection string for DB9’s hosted service. - Connection pooling: Start with 5-10 connections (
maxoption inpg.Pool, or Prisma’sconnection_limitURL parameter). DB9 handles per-tenant pooling server-side. - Graceful shutdown: Close the database pool when the process exits to avoid leaked connections:
process.on('SIGTERM', async () => { await prisma.$disconnect(); process.exit(0);});process.on('SIGTERM', async () => { await pool.end(); process.exit(0);});import { Pool } from 'pg';
// keep a reference to the pool (export it from db.ts)process.on('SIGTERM', async () => { await pool.end(); process.exit(0);});Troubleshooting
Section titled “Troubleshooting”ECONNREFUSED on port 5432
Section titled “ECONNREFUSED on port 5432”DB9 uses port 5433, not 5432. Verify your DATABASE_URL includes :5433.
Connection pool exhaustion
Section titled “Connection pool exhaustion”If you see “too many connections” errors, reduce the pool size and confirm you are not creating a new pool on every request. Reuse a single pool instance across the application.
TLS / SSL errors
Section titled “TLS / SSL errors”DB9 requires TLS. Make sure your connection string includes ?sslmode=require. If you configure pg.Pool options directly, set ssl: true or ssl: { rejectUnauthorized: false } for development.
prisma db push or drizzle-kit push fails
Section titled “prisma db push or drizzle-kit push fails”DB9 has limited information_schema support. Use prisma db push for Prisma (works in most cases) or manage tables with raw SQL for Drizzle. See the Prisma guide and Drizzle guide for details.
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