Skip to content

Latest commit

 

History

History
348 lines (265 loc) · 13.3 KB

File metadata and controls

348 lines (265 loc) · 13.3 KB
title Serverless Driver
icon Zap
tag TypeScript
description Use @neondatabase/serverless to connect from edge runtimes and serverless functions through PgBeam with HTTP or WebSocket transports.

Connect from edge runtimes and serverless functions using the @neondatabase/serverless driver. PgBeam supports both the HTTP query endpoint and the WebSocket wire protocol. Your neon() / Pool call sites and every query stay exactly as they are.

Changing the connection-string host is **not** enough on its own. By default `@neondatabase/serverless` derives its endpoint by rewriting the host: it replaces the first label with `api.`, so `abc.proxy.pgbeam.app` would resolve to `https://api.proxy.pgbeam.app/sql`, dropping the `abc` subdomain PgBeam needs to route your project. Point the driver at PgBeam once, globally, with `neonConfig` before any query runs:
import { neonConfig } from "@neondatabase/serverless";

// HTTP /sql endpoint: keep the project subdomain, append /sql
neonConfig.fetchEndpoint = (host) => `https://${host}/sql`;
// WebSocket /v2 endpoint (Pool / Client)
neonConfig.wsProxy = (host) => `${host}/v2`;

These two lines are the only addition. Your query call sites do not change. (On Neon's own *.neon.tech hosts the defaults work; PgBeam needs the override because its hostnames are not *.neon.tech.)

Setup

### Install the package
<Tabs items={["npm", "pnpm", "yarn", "bun"]}>
  <Tab value="npm">
    ```bash
    npm install @neondatabase/serverless
    ```
  </Tab>

  <Tab value="pnpm">
    ```bash
    pnpm add @neondatabase/serverless
    ```
  </Tab>

  <Tab value="yarn">
    ```bash
    yarn add @neondatabase/serverless
    ```
  </Tab>

  <Tab value="bun">
    ```bash
    bun add @neondatabase/serverless
    ```
  </Tab>
</Tabs>

For Node.js environments using WebSocket transport, also install `ws`:

```bash
npm install ws
```
### Point the driver at PgBeam
Set `neonConfig` once, before any query runs (see the warning above for why
this is required). In Node.js, also set the WebSocket constructor.

```ts title="neon-config.ts"
import { neonConfig } from "@neondatabase/serverless";

neonConfig.fetchEndpoint = (host) => `https://${host}/sql`;
neonConfig.wsProxy = (host) => `${host}/v2`;

// Required for the WebSocket Pool/Client paths against PgBeam: PgBeam
// authenticates with SCRAM, but the driver's default pipelining optimistically
// sends a cleartext password before it sees the auth challenge, which hangs the
// connection. Disabling pipelining makes the driver complete the SCRAM handshake.
neonConfig.pipelineConnect = false;

// Node.js only: provide a WebSocket implementation for the Pool/Client paths
// import ws from "ws";
// neonConfig.webSocketConstructor = ws;
```
### HTTP transport: `neon()` tagged template
Best for one-shot queries from edge/serverless functions. Each call is a single
HTTP request. No persistent connection required.

```ts title="query.ts"
import "./neon-config"; // applies neonConfig.fetchEndpoint
import { neon } from "@neondatabase/serverless";

const sql = neon("postgresql://user:pass@abc.proxy.pgbeam.app/mydb");
const rows = await sql`SELECT * FROM users WHERE active = true`;
```
### WebSocket transport: `Pool` / `Client`
Best for interactive transactions or session-level features. Uses the
PostgreSQL wire protocol over WebSocket.

<Callout type="warn" title="Set neonConfig.pipelineConnect = false for WebSocket">
  With the driver's default configuration, the WebSocket `Pool` / `Client`
  connection **hangs** against PgBeam. PgBeam authenticates with SCRAM, and the
  driver's default `pipelineConnect: "password"` optimistically pipelines a
  cleartext password before it reads the server's auth challenge, so the SCRAM
  handshake never completes. Set `neonConfig.pipelineConnect = false` (already
  in the `neon-config.ts` above) so the driver waits for the challenge and
  completes SCRAM. The HTTP `neon()` path is unaffected; if you only need
  one-shot queries, it works with just `fetchEndpoint` set.
</Callout>

<Tabs items={["Edge runtime", "Node.js"]}>
  <Tab value="Edge runtime">
    ```ts title="db.ts"
    import "./neon-config"; // applies neonConfig.wsProxy
    import { Pool } from "@neondatabase/serverless";

    const pool = new Pool({
      connectionString: "postgresql://user:pass@abc.proxy.pgbeam.app/mydb",
    });
    const { rows } = await pool.query("SELECT * FROM users");
    ```
  </Tab>

  <Tab value="Node.js">
    ```ts title="db.ts"
    import { Pool, neonConfig } from "@neondatabase/serverless";
    import ws from "ws";

    neonConfig.fetchEndpoint = (host) => `https://${host}/sql`;
    neonConfig.wsProxy = (host) => `${host}/v2`;
    neonConfig.pipelineConnect = false; // required for SCRAM (see callout above)
    neonConfig.webSocketConstructor = ws;

    const pool = new Pool({
      connectionString: "postgresql://user:pass@abc.proxy.pgbeam.app/mydb",
    });
    const { rows } = await pool.query("SELECT * FROM users");
    ```
  </Tab>
</Tabs>
### Run a test query
```ts
const result = await sql`SELECT 1 AS ok`;
console.log(result); // [{ ok: 1 }]
```

If this returns results, the serverless driver is connected through PgBeam.

HTTP vs WebSocket

HTTP (neon()) WebSocket (Pool / Client)
Best for One-shot queries, edge functions Transactions, session features
Connection Stateless HTTP request Persistent WebSocket
Cold start None WebSocket handshake + TLS
Transactions sql.transaction([...]) BEGIN / COMMIT via client
Pipelining Automatic (batched in one request) PostgreSQL wire protocol
Max payload ~10 MB response Unlimited streaming

Edge runtime compatibility

The serverless driver works in any runtime with fetch (for HTTP) or WebSocket (for WS):

Runtime HTTP WebSocket Notes
Vercel Edge Functions Yes Yes Built-in WebSocket support
Cloudflare Workers Yes Yes Built-in WebSocket support
Deno Deploy Yes Yes Built-in WebSocket support
Bun Yes Yes Built-in WebSocket support
Node.js Yes Yes Requires ws package

Drizzle ORM integration

Use the serverless driver adapters with Drizzle for type-safe queries:

The same neonConfig setup applies. Drizzle wraps the Neon driver, so once the endpoint is pointed at PgBeam, your Drizzle schema and queries are unchanged.

<Tabs items={["HTTP (drizzle-orm/neon-http)", "WebSocket (drizzle-orm/neon-serverless)"]}> ```ts title="db.ts" import "./neon-config"; // applies neonConfig.fetchEndpoint import { neon } from "@neondatabase/serverless"; import { drizzle } from "drizzle-orm/neon-http";

const sql = neon("postgresql://user:pass@abc.proxy.pgbeam.app/mydb");
const db = drizzle(sql);

const users = await db.select().from(usersTable);
```
```ts title="db.ts" import "./neon-config"; // applies neonConfig.wsProxy (+ webSocketConstructor on Node) import { Pool } from "@neondatabase/serverless"; import { drizzle } from "drizzle-orm/neon-serverless";
const pool = new Pool({
  connectionString: "postgresql://user:pass@abc.proxy.pgbeam.app/mydb",
});
const db = drizzle(pool);

const users = await db.select().from(usersTable);
```

Transactions

<Tabs items={["HTTP", "WebSocket"]}> Use sql.transaction() to run multiple statements in a single HTTP request:

```ts title="HTTP transactions"
import { neon } from "@neondatabase/serverless";

const sql = neon("postgresql://user:pass@abc.proxy.pgbeam.app/mydb");

const results = await sql.transaction([
  sql`UPDATE accounts SET balance = balance - 100 WHERE id = 1`,
  sql`UPDATE accounts SET balance = balance + 100 WHERE id = 2`,
]);
```
Use standard `BEGIN` / `COMMIT` with a dedicated client:
```ts title="WebSocket transactions"
import { Pool } from "@neondatabase/serverless";

const pool = new Pool({
  connectionString: "postgresql://user:pass@abc.proxy.pgbeam.app/mydb",
});

const client = await pool.connect();
try {
  await client.query("BEGIN");
  await client.query("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
  await client.query("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
  await client.query("COMMIT");
} catch (e) {
  await client.query("ROLLBACK");
  throw e;
} finally {
  client.release();
}
```

Data type notes

PgBeam matches the @neondatabase/serverless type contract over the HTTP endpoint. In particular, bigint (int8), numeric, and money are returned as strings, not numbers: a JavaScript Number cannot represent integers above 2^53 or arbitrary-precision decimals without silent loss. Wrap them with BigInt(...) or a decimal library as needed.

const [row] = await sql`SELECT 9223372036854775807::bigint AS big`;
row.big; // "9223372036854775807" (string)
BigInt(row.big); // 9223372036854775807n

json / jsonb are parsed into objects, and arrays into JS arrays, exactly as the Neon driver does.

Caching and replicas

SQL annotations work the same with the serverless driver:

import { neon } from "@neondatabase/serverless";

const sql = neon("postgresql://user:pass@abc.proxy.pgbeam.app/mydb");

// Cache for 5 minutes
const categories =
  await sql`/* @pgbeam:cache maxAge=300 */ SELECT * FROM categories`;

// Route to read replica
const stats = await sql`/* @pgbeam:replica */ SELECT count(*) FROM orders`;

// Combine both
const products =
  await sql`/* @pgbeam:replica */ /* @pgbeam:cache maxAge=60 */ SELECT * FROM products`;

See Caching and Read Replicas for details.

Common issues

Issue Cause Fix
Requests hit api.proxy.pgbeam.app / 404 / wrong project neonConfig.fetchEndpoint not set; driver rewrote the host to api. Set neonConfig.fetchEndpoint so requests target the /sql path (see Setup step 2)
WebSocket is not defined Node.js missing WS constructor Install ws and set neonConfig.webSocketConstructor = ws
WebSocket Pool / Client connects then hangs, no error Default pipelineConnect: "password" pipelines a cleartext password, but PgBeam uses SCRAM Set neonConfig.pipelineConnect = false. If you only need one-shot queries, use the HTTP neon() path instead
fetch failed on HTTP endpoint Network/firewall blocking HTTPS, or fetchEndpoint not pointed at /sql Verify the proxy hostname resolves, port 443 is open, and fetchEndpoint is set
Slow cold starts with WebSocket TLS + WS handshake on each invocation Use HTTP transport for stateless queries
connection terminated Idle timeout exceeded Use connection pooling or reconnect on error

Further reading