Use Neon serverless Postgres with DaloyJS
Neon is a serverless Postgres host with database branching, scale-to-zero, and an HTTP/WebSocket driver that runs in everyruntime DaloyJS targets — including Cloudflare Workers and Vercel Edge where raw TCP isn't available.
1. Provision
Create a project at console.neon.tech and copy the connection string. Set it as DATABASE_URL in your environment.
2. Install
pnpm add @neondatabase/serverlessFor a pooled connection on Node, use the Pool export. For one-shot queries on edge runtimes, use the lightweight HTTP neon()client. Neon's GA driver requires Node.js 19 or newer when you run it in Node.
3. HTTP client (edge-friendly)
// src/db/neon.ts
import { neon } from "@neondatabase/serverless";
import type { App } from "@daloyjs/core";
export const sql = neon(process.env.DATABASE_URL!);
export const neonPlugin = {
name: "neon",
register(app: App) {
app.decorate("sql", sql);
},
};4. Pooled WebSocket client (Node)
// src/db/neon-pool.ts
import { Pool } from "@neondatabase/serverless";
import type { App } from "@daloyjs/core";
const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
export const neonPoolPlugin = {
name: "neon-pool",
async register(app: App) {
app.decorate("db", pool);
app.onClose(async () => {
await pool.end();
});
},
};5. Augment app state
// src/types/state.d.ts
import type { Pool } from "@neondatabase/serverless";
import type { neon } from "@neondatabase/serverless";
declare module "@daloyjs/core" {
interface AppState {
sql: ReturnType<typeof neon>;
db: Pool;
}
}6. Use it in a route
import { z } from "zod";
import { App, secureHeaders } from "@daloyjs/core";
import { neonPlugin } from "./db/neon";
const app = new App();
app.use(secureHeaders());
app.register(neonPlugin);
const UserSchema = z.object({ id: z.string().uuid(), email: z.string().email() });
app.route({
method: "GET",
path: "/users/:id",
operationId: "getUser",
request: { params: z.object({ id: z.string().uuid() }) },
responses: {
200: { description: "Found", body: UserSchema },
404: { description: "Not found" },
},
handler: async ({ params, state }) => {
const rows = await state.sql`select id, email from users where id = ${params.id} limit 1`;
const user = rows[0];
return user
? { status: 200, body: user }
: { status: 404, body: { type: "about:blank", title: "Not found", status: 404 } };
},
});Cloudflare Workers
Use the HTTP neon() client and pass the connection string from the worker environment instead of process.env. Because this example reads env, wrap the Workerfetch handler and call app.fetch(req) after decorating state:
import { neon } from "@neondatabase/serverless";
export default {
async fetch(req: Request, env: { DATABASE_URL: string }) {
const sql = neon(env.DATABASE_URL);
app.decorate("sql", sql);
return app.fetch(req);
},
};With Drizzle ORM
pnpm add drizzle-orm
// src/db/drizzle.ts
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle({ client: sql });With Prisma
Use the Neon Driver Adapter so Prisma can run on edge runtimes (GA since Prisma 6.16.0):
pnpm add @prisma/adapter-neon
// src/db/prisma.ts
import { PrismaClient } from "@prisma/client";
import { PrismaNeon } from "@prisma/adapter-neon";
const adapter = new PrismaNeon({ connectionString: process.env.DATABASE_URL! });
export const prisma = new PrismaClient({ adapter });Use Neon's pooled connection string (host ends in -pooler) for DATABASE_URL, and a separate DIRECT_URL for Prisma CLI commands like prisma migrate and prisma db pull.
Branching for preview environments
Pair Neon's branching with Vercel preview deployments or GitHub PR previews. Create a branch per PR and pass its connection string to the deployment's DATABASE_URL. This is a natural fit for the Vercel adapter.
See also PlanetScale, Supabase, and the database hosting overview.