mrkeyoor.com_
Sat 08 Aug 22:53 UTC
npmDataupdated 08 Aug 2026

@neondatabase/serverless

@neondatabase/serverless is Neon’s JavaScript and TypeScript PostgreSQL driver for runtimes where opening a normal TCP database socket is awkward or impossible. Its `neon()` tagged template sends one-shot queries and fixed batches over HTTPS. Its node-postgres-compatible `Pool` and `Client` use WebSockets when an application needs a session, prepared state, or an interactive transaction. The package is tuned for Neon, although another PostgreSQL server can work behind a separately operated WebSocket proxy.

Verdict

The direct choice for Neon from edge and serverless runtimes, especially when most work fits one HTTP statement or a fixed batch. Choose the transport consciously: `neon()` is simpler and cheaper to manage, while Pool and Client bring session semantics plus WebSocket lifecycle work.

API stability4/5The package reached 1.0 in March 2025 and keeps the familiar node-postgres Pool and Client surface, while 1.1 changed runtime type declarations without changing executed code. The major release did make meaningful breaks: Node 19 became the floor, the HTTP query function became tagged-template-only, and manual parameterized strings moved to sql.query(). Those changes improved injection safety but require edits in pre-1.0 applications.
Docs5/5The README separates HTTP, fixed transaction, Pool, and Client use before showing complete Node and Vercel Edge examples. CONFIG.md documents composition, unsafe identifiers, array and full result modes, fetch cancellation, JWT headers, transaction isolation, and every transport switch. It also labels experimental settings and explains when pure-JavaScript TLS is not suitable for production, which is the sort of warning database-driver docs often omit.
Maintenance4/5Version 1.1.0 was published on April 17, 2026 and the repository was pushed the same day. It is neither archived nor disabled, and the 1.1 changelog explains a substantial declaration cleanup backed by generated types and expanded tests. GitHub reports 52 open issues and pull requests, a visible backlog large enough to inspect for a runtime-specific edge case before adopting an advanced WebSocket configuration.
Ecosystem4/5The package recorded 3,142,489 downloads for the measured week and exposes a node-postgres-compatible API, so tools such as Kysely and Zapatos can use Pool or Client. It ships ESM, CommonJS, and inlined TypeScript declarations and is documented for Vercel and Cloudflare-style runtimes. The tradeoff is vendor gravity: HTTP endpoints are Neon-specific, and other PostgreSQL hosts need a WebSocket proxy.

Use it if

  • You query Neon from edge functions, workers, or short-lived serverless handlers that cannot open ordinary PostgreSQL TCP connections
  • Most operations are independent SQL statements and you want a parameterized tagged template over low-latency HTTPS
  • You need node-postgres-compatible Pool or Client objects for Kysely, Zapatos, sessions, or interactive transactions over WebSockets
  • You want one dependency with included TypeScript declarations and both ESM and CommonJS exports
Skip it if

Setup reality

There are no runtime dependencies, peer packages, native builds, or generated clients, but a working connection still starts in the Neon console: copy the full PostgreSQL URL into a server-side DATABASE_URL secret. Version 1.1 requires Node 19 or newer. For isolated statements, `neon(DATABASE_URL)` works through fetch with no WebSocket package. Since version 1, that function must be used as a tagged template; conventional `sql('...', values)` calls throw, and manually assembled SQL belongs in `sql.query(text, values)`. Sessions change the setup. Pool and Client use WebSockets, and Node 21 and earlier need a constructor such as the `ws` package assigned to `neonConfig.webSocketConstructor`. In edge handlers, create and close those objects within the same request because the socket cannot survive the request boundary. The fetch transaction helper accepts a ready array or a non-async callback that returns an array; application logic cannot pause between statements. HTTP timeouts are not a top-level option, so pass an AbortSignal through fetchOptions and clear your timer. Direct browser use is technically possible but version 1.0.1 prints a security warning because shipping a database connection string to untrusted client code is usually the wrong boundary. Connecting to non-Neon PostgreSQL adds a separately deployed WebSocket proxy and advanced transport configuration. Cloudflare Worker users should also compare Hyperdrive, which the project README explicitly recommends considering.

Patterns

Run a safe one-shot query over HTTPrun-parameterized-query

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

const sql = neon(process.env.DATABASE_URL);
const [post] = await sql`
  SELECT id, title
  FROM posts
  WHERE id = ${postId}
`;

Interpolated values are parameterized. The HTTP path does not create a reusable database session.

Insert a row and return itinsert-returning-row

const [created] = await sql`
  INSERT INTO posts (title, author_id)
  VALUES (${title}, ${authorId})
  RETURNING id, title, author_id
`;

By default the result is the rows array itself, not a node-postgres result object.

Compose parameterized SQL fragmentscompose-query-fragments

const filter = publishedOnly
  ? sql`WHERE published_at IS NOT NULL AND author_id = ${authorId}`
  : sql`WHERE author_id = ${authorId}`;

const rows = await sql`
  SELECT id, title FROM posts ${filter} ORDER BY id DESC
`;

Version 1 compiles fragments lazily, so parameter positions remain correct when fragments are nested.

Execute a query stored in a stringrun-query-string

const text = 'SELECT id, title FROM posts WHERE author_id = $1';
const rows = await sql.query(text, [authorId]);

Use sql.query() for text plus numbered placeholders. Calling sql(text, values) is an error in version 1.

Insert a trusted dynamic identifieruse-dynamic-identifier

const allowed = new Set(['created_at', 'title']);
if (!allowed.has(sortColumn)) throw new Error('Invalid sort column');

const rows = await sql`
  SELECT id, title FROM posts
  ORDER BY ${sql.unsafe(sortColumn)}
`;

Parameters cannot represent table or column names. sql.unsafe() performs no escaping, so enforce a fixed allowlist first.

Send a fixed transaction over HTTPrun-fixed-transaction

const [accountRows, auditRows] = await sql.transaction([
  sql`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${fromId} RETURNING balance`,
  sql`INSERT INTO audit_log (account_id, amount) VALUES (${fromId}, ${amount}) RETURNING id`,
]);

The statements are submitted together as a non-interactive transaction. You cannot inspect the first result before defining the second query.

Run a serializable read-only transactionset-transaction-isolation

const [summary, totals] = await sql.transaction(
  [
    sql`SELECT * FROM daily_summary`,
    sql`SELECT sum(amount) AS total FROM payments`,
  ],
  { isolationLevel: 'Serializable', readOnly: true, deferrable: true },
);

deferrable is meaningful only with a serializable, read-only PostgreSQL transaction.

Get fields, command, and row countreturn-full-results

const sqlWithMeta = neon(process.env.DATABASE_URL, { fullResults: true });
const result = await sqlWithMeta`SELECT id, title FROM posts`;
console.log(result.rowCount, result.command, result.fields, result.rows);

fullResults changes the return shape from a rows array to a node-postgres-style result object.

Return compact array rowsreturn-array-rows

const arraySql = neon(process.env.DATABASE_URL, { arrayMode: true });
const rows = await arraySql`SELECT id, title FROM posts ORDER BY id`;
for (const [id, title] of rows) console.log(id, title);

Array mode drops column-name keys, so keep the SELECT column order explicit and stable.

Abort a slow HTTP querycancel-slow-query

const controller = new AbortController();
const timer = setTimeout(() => controller.abort('database timeout'), 5000);

try {
  return await sql.query('SELECT * FROM slow_report WHERE team_id = $1', [teamId], {
    fetchOptions: { signal: controller.signal },
  });
} finally {
  clearTimeout(timer);
}

HTTP query cancellation is passed through fetchOptions. Always clear the timer after success or failure.

Use Pool for an interactive transactionrun-interactive-transaction

import { Pool } from '@neondatabase/serverless';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const client = await pool.connect();
try {
  await client.query('BEGIN');
  const { rows: [account] } = await client.query(
    'SELECT balance FROM accounts WHERE id = $1 FOR UPDATE',
    [accountId],
  );
  if (account.balance < amount) throw new Error('Insufficient funds');
  await client.query('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [amount, accountId]);
  await client.query('COMMIT');
} catch (error) {
  await client.query('ROLLBACK');
  throw error;
} finally {
  client.release();
  await pool.end();
}

Pool uses WebSockets. In a serverless handler, create and close it within that same request.

Provide WebSocket support on older Node releasesconfigure-node-websocket

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

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

The README requires this on Node 21 and earlier for Pool or Client. The `neon()` HTTP API does not need WebSockets.

Alternatives

PackageRegistryPick it when
pgnpmYou run a long-lived Node.js service with direct TCP access and want the standard node-postgres ecosystem
postgresnpmYou want a compact tagged-template PostgreSQL client in a conventional Node, Bun, or Deno process
@electric-sql/pglitenpmYou need embedded PostgreSQL in a browser, test, or local application rather than a remote managed database