Library / SDK
neondatabase/serverless avatar
neondatabase/serverless

Neon serverless: a PostgreSQL driver that speaks HTTP instead of TCP

Connect to Neon PostgreSQL from serverless/worker/edge functions

550 stars85 forksTypeScriptMIT

At a glance

What is it?
The @neondatabase/serverless package is a drop-in replacement for node-postgres that carries queries over HTTPS and WebSockets so a database connection works from an edge function, with template-tag queries, a transaction helper and a node-postgres compatible Pool and Client. It suits a team deploying to Vercel Edge or Cloudflare Workers, and it changes what a connection means in a serverless runtime.
Who is it for?
Neon serverless is worth adopting if you deploy to an edge runtime, because the same node-postgres API works there without a TCP tunnel or a connection pooler, because the driver is a drop-in replacement so the query layer does not change, and because the transaction helper covers the multi-statement case that the plain query function cannot. It is the wrong tool if you run a long-lived Node server, where node-postgres is the simpler option.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 9 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why a second PostgreSQL driver exists at all

@neondatabase/serverless is Neon's PostgreSQL driver for JavaScript and TypeScript, and its three properties are stated in a short list.

It is low-latency, thanks to message pipelining and other optimizations. It is ideal for serverless and edge deployment, using HTTPS and WebSockets in place of TCP. And it is a drop-in replacement for node-postgres, the pg package, on which it is based.

That last point explains the whole design. The problem is not that PostgreSQL is hard to talk to from JavaScript, it is that a serverless function has no TCP socket to a database server. A traditional connection is a long-lived socket with a state machine, keepalives and a pool, and none of that survives a function that is started, does one thing and is torn down, possibly in a different region from the last request. Neon is a serverless PostgreSQL service, and its driver replaces the socket with HTTP requests and WebSockets so the same code works in a runtime that only has fetch.

The drop-in claim is the practical part. Because the package is based on node-postgres, the Pool and Client classes, the query interface, the parameterised query conventions and the error types are the same, so a project can switch by changing the import and the connection string. The documentation points at the node-postgres API guide for the full Pool and Client documentation, and at a separate CONFIG.md for the additional options that apply here.

The package is MIT licensed, the primary language is TypeScript, the default branch is main, and the last push to the repository was on 2026-10-14. There are no GitHub releases, and it is published to npm as @neondatabase/serverless, with the homepage pointing at the npm package page.

Installing it, and the alias trick for packages that depend on pg

The install is the usual one, and types are included so there is no separate types package to add:

bash
npm install @neondatabase/serverless

Then the connection string comes from the Neon console and is set as an environment variable, in the shape the documentation shows:

code
DATABASE_URL="postgres://username:[email protected]/neondb"

The more interesting installation detail is for a specific case. If you install with npm for use by another package that declares a dependency on pg, node-postgres, you use an alias plus an override, which looks like this in your package.json:

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

The reason is that a library which imports pg directly will pull in node-postgres and try to open a TCP socket, which is exactly what does not work in an edge runtime. Aliasing the pg specifier to this package makes the transitive dependency resolve to the serverless driver instead, and the override section forces the same substitution for the nested dependency tree, where a package's own pg requirement would otherwise win.

That is a slightly unusual thing to put in a package.json, and it is worth understanding why it is needed rather than copying it blindly. The engine requirement in the package manifest is node >=19.0.0, and the export map has two entries, a require entry pointing at ./index.js and an import entry at ./index.mjs, with matching type declaration files index.d.ts and index.d.mts shipped alongside.

The neon() function, and the one thing it cannot do

For one-shot queries, the package provides the neon function, and the usage is a tagged template literal.

javascript
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL);

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

The result is destructured as an array of rows, so the first row is the post, and the documentation notes it may be undefined. The interpolation of the value into the query string is safe from SQL injection, and the interpolation is done by the tagged template mechanism rather than by string concatenation, so the value is sent as a bind parameter.

That is the whole API for the common case, and the package documents more details and options for the neon function in CONFIG.md.

The limitation is stated immediately after the example, and it is the one thing to internalise. A query using the neon function is carried by an HTTPS fetch request, which should work and work fast from any modern JavaScript environment, but you can only send one query at a time this way, because sessions and transactions are not supported.

That constraint is the reason the package has a second API. The transaction() function is exposed as a property on the query function, and it issues multiple queries within a single, non-interactive transaction:

javascript
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL);
const showLatestN = 10;

const [posts, tags] = await sql.transaction([
  sql`SELECT * FROM posts ORDER BY posted_at DESC LIMIT ${showLatestN}`,
  sql`SELECT * FROM tags`,
]);

The results come back as an array matching the array of queries. That covers the batch-of-statements case, and it is what a data loader or an API endpoint that needs two related reads will use.

Pool and Client, and why a serverless connection has to live inside one request

The Pool and Client constructors exist for two situations: when you need session or interactive transaction support, and when you need compatibility with node-postgres, which is what makes query libraries such as Kysely or Zapatos work.

Queries using Pool and Client are carried by WebSockets, and the documentation lists two key things to know about that.

The first is that in Node.js v21 and earlier, and in some other environments, there is no built-in WebSocket support, so you have to supply a WebSocket constructor function. The example does it in two lines:

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

// only do this in Node v21 and below
import ws from 'ws';
neonConfig.webSocketConstructor = ws;

The second is the one that bites. In serverless environments such as Vercel Edge Functions or Cloudflare Workers, WebSocket connections cannot outlive a single request. That means Pool or Client objects must be connected, used and closed within a single request handler, and the documentation is emphatic: do not create them outside a request handler, do not create them in one handler and try to reuse them in another, and do not forget to close them, to avoid exhausting available connections.

That is a genuinely different mental model from a traditional server, where a pool is a module-level singleton created once at startup. In a serverless runtime, the pool has to be a local variable inside the handler, which means the connection setup cost is paid on every request, and the usual advice of hoisting the pool to module scope is actively wrong here.

There is also a Cloudflare-specific note: on Cloudflare Workers, the documentation suggests considering Cloudflare Hyperdrive instead of this driver.

One more capability is inherited from node-postgres and is worth knowing about. Pool and Client accept a function, sync or async, for the password connection parameter, which is useful for connecting with short-lived auth tokens, and it can be combined with a connection string using the parseIntoClientConfig function, which is a top-level export based on the implementation in pg-connection-string.

Deploying it, and the test matrix behind the package

The documentation walks through turning the example into a complete API endpoint on Vercel Edge Functions, and the example is a reasonable shape to copy.

The handler parses and validates a query parameter, queries the database with the neon function, returns a 400 for a bad request and a 404 for a missing row, and returns the row as JSON. The runtime configuration names the edge runtime and a region, with a comment to specify the region nearest your Neon database:

javascript
export const config = {
  runtime: 'edge',
  regions: ['iad1'],  // specify the region nearest your Neon DB
};

The deploy steps use the Vercel CLI, with a global install, adding the environment variable, running the dev server locally and then deploying.

The region point is the one to take seriously. The whole latency argument for this driver collapses if your function runs in a region far from the database, and the config example shows where the decision goes.

Behind the package there is a substantial test matrix, and it is a good indicator of what the driver is actually verified against. The test script chains a type-shims check, a node run, an edge run, a bun run, a deno run, a packages run, a cloudflare run, a vercel run and a browser run, and the individual scripts show the tooling: Vitest with the edge-runtime environment for the edge target, Bun with an env file for the bun target, and the type check uses tsc with strict mode and explicit module resolution settings against a type compatibility test file.

The repository itself is organised for publishing rather than for a big application. The built entry points sit at the root as index.js, index.mjs, index.d.ts and index.d.mts, with a build.sh and a tag-release.sh, an api-extractor.json for the public API surface, a .env.template, a prisma.config.ts, and separate CONFIG.md, DEPLOY.md and DEVELOP.md documents next to the README. The package files list ships only the entry points and the documentation, which is a small published footprint.

Where Neon serverless is the wrong tool

The first limit is that it is a driver for a specific kind of deployment. If you run a long-lived Node server, the traditional node-postgres package over TCP is simpler, has better tooling around pooling and instrumentation, and does not have the connection lifetime rules that come with WebSockets in a serverless runtime. This package is the right answer when the runtime has no socket, and an awkward answer when it does.

The second is the Neon dependency. The driver is Neon's PostgreSQL driver, and the connection string shape in the documentation points at a host.neon.tech endpoint. Using the driver against a self-hosted PostgreSQL means you have no HTTPS or WebSocket transport to talk to, and the whole premise of the package does not apply.

The third is that the query-per-request model has a cost that a connection pool does not. Every request that opens a Pool and closes it pays for the connection setup, and in a high-traffic function that setup is a network round trip to the database before the query even starts. The single-query neon function avoids the pool entirely, which is why it is the recommended path, but it also means one query per fetch request unless you use transaction().

The fourth is the Cloudflare Workers case, where the documentation itself points at a different product. If your target runtime is Cloudflare Workers, the project is telling you that this driver is not the first thing to reach for.

And the fifth is the Node version floor, node >=19.0.0, combined with the WebSocket constructor requirement on Node 21 and earlier. A project on an older runtime cannot use the package at all, and one on Node 19 to 21 has to add a dependency for the WebSocket implementation, which is a small thing but another moving part in a runtime that is supposed to need nothing.

None of these are reasons to avoid the package in its intended environment. They are reasons to check that you are in that environment before you adopt it.

Editorial conclusion

Neon serverless is worth adopting if you deploy to an edge runtime, because the same node-postgres API works there without a TCP tunnel or a connection pooler, because the driver is a drop-in replacement so the query layer does not change, and because the transaction helper covers the multi-statement case that the plain query function cannot. It is the wrong tool if you run a long-lived Node server, where node-postgres is the simpler option. Before you adopt it, check three things: that you understand the connection lifetime, because a Pool or Client in a serverless environment must be connected, used and closed inside a single request handler, and creating one outside a handler is the mistake that exhausts connections; that you have Node 19 or newer, which is the engine requirement in the package manifest, and that on Node 21 and earlier you supply a WebSocket constructor because there is no built-in support; and that you check the Cloudflare Workers case specifically, because the documentation suggests considering Cloudflare Hyperdrive instead of this driver there.

Frequently asked questions

How do I install and connect with @neondatabase/serverless?

Run npm install @neondatabase/serverless, then take the connection string from the Neon console and set it as an environment variable, for example DATABASE_URL="postgres://username:[email protected]/neondb". Types are included, and the package requires Node 19 or newer.

What is the difference between the neon() function and the Pool class?

Queries from the neon function travel over an HTTPS fetch request, so only one query can be sent at a time and sessions and transactions are not supported, although the transaction() property issues several queries in one non-interactive transaction. Pool and Client travel over WebSockets and support sessions and interactive transactions, and they keep the node-postgres API, which is what makes query libraries such as Kysely or Zapatos work.

Why do I need to close the Pool inside the request handler?

In serverless environments such as Vercel Edge Functions or Cloudflare Workers, WebSocket connections cannot outlive a single request, so a Pool or Client must be connected, used and closed within one request handler. The documentation says not to create them outside a request handler, not to reuse one across handlers, and not to forget to close them, to avoid exhausting available connections. On Cloudflare Workers it suggests considering Cloudflare Hyperdrive instead.

Official sources

  1. Issues
  2. License: MIT
  3. neondatabase/serverless on GitHub
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/neondatabase-serverless.svg)](https://hysenlabs.com/projects/neondatabase-serverless)

Was this useful?