# Use Neon serverless Postgres with DaloyJS

[Neon](https://neon.com) is a serverless Postgres host with database branching, scale-to-zero, and an HTTP/WebSocket driver that runs in *every* runtime DaloyJS targets, including Cloudflare Workers where raw TCP isn't available.

**Diagram: Two drivers, one connection string**

- **DATABASE_URL** (source, @neondatabase/serverless) - neon(...) or new Pool(...)
- **HTTP neon() client** (edge-friendly) - One-shot queries · Workers
- **Pooled Pool client** (node) - WebSocket · reused connections · pool.end()

Both clients ship in the same package. Use the lightweight HTTP neon() client on edge runtimes and the pooled WebSocket Pool for long-lived Node processes.

## 1. Provision

Create a project at [console.neon.tech](https://console.neon.tech) and copy the connection string. Set it as `DATABASE_URL` in your environment.

## 2. Install

```ts
pnpm add @neondatabase/serverless
```

For 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)

```ts
// 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)

```ts
// 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

Add the `declare module` block to the same module that creates the clients, not to a separate `.d.ts` file. Declaration files are exempt from type-checking when `skipLibCheck` is on (the scaffolded default), so a broken import inside a `.d.ts` fails silently and `state.sql` / `state.db` degrade to `any`.

```ts
// src/db/neon.ts (same module as the HTTP plugin above)
import type { Pool } from "@neondatabase/serverless";

declare module "@daloyjs/core" {
  interface AppState {
    sql: ReturnType<typeof neon>;
    db: Pool;
  }
}
```

## 6. Use it in a route

```ts
import { z } from "zod";
import { App, secureHeaders } from "@daloyjs/core";
import { neonPlugin } from "./db/neon.ts";

const app = new App();
app.use(secureHeaders());
app.register(neonPlugin);

const UserSchema = z.object({ id: z.uuid(), email: z.email() });

app.get(
  "/users/:id",
  {
    operationId: "getUser",
    request: { params: z.object({ id: z.uuid() }) },
    responses: {
      200: { description: "Found", body: UserSchema },
      404: { description: "Not found" },
    },
  },
  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 Worker `fetch` handler and call `app.fetch(req)` after decorating state:

```ts
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

```ts
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](https://www.prisma.io/docs/orm/overview/databases/neon) so Prisma can run on edge runtimes (GA since Prisma `6.16.0`):

```ts
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](/docs/adapters).

See also [PlanetScale](/docs/databases/planetscale), [Supabase](/docs/orm/supabase), and the [database hosting overview](/docs/databases).

---

Source: https://daloyjs.dev/docs/databases/neon