Files
wehub-resource-sync bb5c75ce05
Component Security Validation / Security Audit (push) Has been cancelled
Deploy to Cloudflare Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:58 +08:00

6.3 KiB

Neon Serverless Driver

Patterns and best practices for connecting to Neon databases in serverless environments using the @neondatabase/serverless driver. The driver connects over HTTP for fast, single queries or WebSockets for node-postgres compatibility and interactive transactions.

For official documentation:

curl -H "Accept: text/markdown" https://neon.tech/docs/serverless/serverless-driver

Installation

# Using npm
npm install @neondatabase/serverless

# Using JSR
bunx jsr add @neon/serverless

Note: Version 1.0.0+ requires Node.js v19 or later.

For projects that depend on pg but want to use Neon's WebSocket-based connection pool:

"dependencies": {
  "pg": "npm:@neondatabase/serverless@^0.10.4"
},
"overrides": {
  "pg": "npm:@neondatabase/serverless@^0.10.4"
}

Connection String

Always use environment variables:

// For HTTP queries
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL!);

// For WebSocket connections
import { Pool } from "@neondatabase/serverless";
const pool = new Pool({ connectionString: process.env.DATABASE_URL! });

Never hardcode credentials:

// AVOID
const sql = neon("postgres://username:password@host.neon.tech/neondb");

HTTP Queries with neon function

Ideal for simple, "one-shot" queries in serverless/edge environments. Uses HTTP fetch - fastest method for single queries.

Parameterized Queries

Use tagged template literals for safe parameter interpolation:

const [post] = await sql`SELECT * FROM posts WHERE id = ${postId}`;

For manually constructed queries:

const [post] = await sql.query("SELECT * FROM posts WHERE id = $1", [postId]);

Never concatenate user input:

// AVOID: SQL Injection Risk
const [post] = await sql("SELECT * FROM posts WHERE id = " + postId);

Configuration Options

// Return rows as arrays instead of objects
const sqlArrayMode = neon(process.env.DATABASE_URL!, { arrayMode: true });
const rows = await sqlArrayMode`SELECT id, title FROM posts`;
// rows -> [[1, "First Post"], [2, "Second Post"]]

// Get full results including row count and field metadata
const sqlFull = neon(process.env.DATABASE_URL!, { fullResults: true });
const result = await sqlFull`SELECT * FROM posts LIMIT 1`;
// result -> { rows: [...], fields: [...], rowCount: 1, ... }

WebSocket Connections with Pool and Client

Use for node-postgres compatibility, interactive transactions, or session support.

WebSocket Configuration

For Node.js v21 and earlier:

import { Pool, neonConfig } from "@neondatabase/serverless";
import ws from "ws";

// Required for Node.js < v22
neonConfig.webSocketConstructor = ws;

const pool = new Pool({ connectionString: process.env.DATABASE_URL! });

Serverless Lifecycle Management

Create, use, and close the pool within the same invocation:

// Vercel Edge Functions example
export default async (req: Request, ctx: ExecutionContext) => {
  const pool = new Pool({ connectionString: process.env.DATABASE_URL! });

  try {
    const { rows } = await pool.query("SELECT * FROM users");
    return new Response(JSON.stringify(rows));
  } catch (err) {
    console.error(err);
    return new Response("Database error", { status: 500 });
  } finally {
    ctx.waitUntil(pool.end());
  }
};

Avoid creating a global Pool instance outside the handler.

Transactions

HTTP Transactions

For running multiple queries in a single, non-interactive transaction:

const [newUser, newProfile] = await sql.transaction(
  [
    sql`INSERT INTO users(name) VALUES(${name}) RETURNING id`,
    sql`INSERT INTO profiles(user_id, bio) VALUES(${userId}, ${bio})`,
  ],
  {
    isolationLevel: "ReadCommitted",
    readOnly: false,
  },
);

Interactive Transactions

For complex transactions with conditional logic:

const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
const client = await pool.connect();
try {
  await client.query("BEGIN");
  const {
    rows: [{ id }],
  } = await client.query("INSERT INTO users(name) VALUES($1) RETURNING id", [
    name,
  ]);
  await client.query("INSERT INTO profiles(user_id, bio) VALUES($1, $2)", [
    id,
    bio,
  ]);
  await client.query("COMMIT");
} catch (err) {
  await client.query("ROLLBACK");
  throw err;
} finally {
  client.release();
  await pool.end();
}

Environment-Specific Optimizations

// For Vercel Edge Functions, specify nearest region
export const config = {
  runtime: "edge",
  regions: ["iad1"], // Region nearest to your Neon DB
};

// For Cloudflare Workers, consider using Hyperdrive
// https://neon.tech/blog/hyperdrive-neon-faq

ORM Integration

For Drizzle ORM integration with the serverless driver, see neon-drizzle.md.

Prisma

import { neonConfig } from "@neondatabase/serverless";
import { PrismaNeon, PrismaNeonHTTP } from "@prisma/adapter-neon";
import { PrismaClient } from "@prisma/client";
import ws from "ws";

const connectionString = process.env.DATABASE_URL;
neonConfig.webSocketConstructor = ws;

// HTTP adapter
const adapterHttp = new PrismaNeonHTTP(connectionString!, {});
export const prismaClientHttp = new PrismaClient({ adapter: adapterHttp });

// WebSocket adapter
const adapterWs = new PrismaNeon({ connectionString });
export const prismaClientWs = new PrismaClient({ adapter: adapterWs });

Kysely

import { Pool } from "@neondatabase/serverless";
import { Kysely, PostgresDialect } from "kysely";

const dialect = new PostgresDialect({
  pool: new Pool({ connectionString: process.env.DATABASE_URL }),
});

const db = new Kysely({ dialect });

NOTE: Do not pass the neon() function to ORMs that expect a node-postgres compatible Pool.

Error Handling

// Pool error handling
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
pool.on("error", (err) => {
  console.error("Unexpected error on idle client", err);
  process.exit(-1);
});

// Query error handling
try {
  const [post] = await sql`SELECT * FROM posts WHERE id = ${postId}`;
  if (!post) {
    return new Response("Not found", { status: 404 });
  }
} catch (err) {
  console.error("Database query failed:", err);
  return new Response("Server error", { status: 500 });
}