---
title: Neon serverless driver
enableTableOfContents: true
subtitle: Connect to Neon from serverless environments over HTTP or WebSockets
updatedOn: '2025-10-10T13:19:39.268Z'
---
The [Neon serverless driver](https://github.com/neondatabase/serverless) is a low-latency Postgres driver for JavaScript and TypeScript that allows you to query data from serverless and edge environments over **HTTP** or **WebSockets** in place of TCP. The driver's low-latency capability is due to [message pipelining and other optimizations](/blog/quicker-serverless-postgres).
The GA version of the Neon serverless driver, v1.0.0 and higher, requires Node.js version 19 or higher. It also includes a **breaking change** but only if you're calling the HTTP query template function as a conventional function. For details, please see the [1.0.0 release notes](https://github.com/neondatabase/serverless/pull/149) or read the [blog post](/blog/serverless-driver-ga).
When to query over HTTP vs WebSockets:
- **HTTP**: Querying over an HTTP [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) request is faster for single, non-interactive transactions, also referred to as "one-shot queries". Issuing [multiple queries](#issue-multiple-queries-with-the-transaction-function) via a single, non-interactive transaction is also supported. See [Use the driver over HTTP](#use-the-driver-over-http).
- **WebSockets**: If you require session or interactive transaction support or compatibility with [node-postgres](https://node-postgres.com/) (the popular **npm** `pg` package), use WebSockets. See [Use the driver over WebSockets](#use-the-driver-over-websockets).
Working with AI coding assistants? Check out our [AI rules for the Neon Serverless Driver](/docs/ai/ai-rules-neon-serverless) to help your AI assistant generate better code for serverless database connections.
## Install the Neon serverless driver
You can install the driver with your preferred JavaScript package manager. For example:
```shell
npm install @neondatabase/serverless
```
The driver includes TypeScript types (the equivalent of `@types/pg`). No additional installation is required.
The Neon serverless driver is also available as a [JavaScript Registry (JSR)](https://jsr.io/docs/introduction) package: [https://jsr.io/@neon/serverless](https://jsr.io/@neon/serverless). The JavaScript Registry (JSR) is a package registry for JavaScript and TypeScript. JSR works with many runtimes (Node.js, Deno, browsers, and more) and is backward compatible with `npm`.
## Configure your Neon database connection
You can obtain a connection string for your database by clicking the **Connect** button on your **Project Dashboard**. Your Neon connection string will look something like this:
```shell
DATABASE_URL=postgresql://[user]:[password]@[neon_hostname]/[dbname]
```
The examples that follow assume that your database connection string is assigned to a `DATABASE_URL` variable in your application's environment file.
## Use the driver over HTTP
The Neon serverless driver uses the [neon](https://github.com/neondatabase/serverless/blob/main/CONFIG.md#neon-function) function for queries over HTTP. The function returns a query function that can only be used as a template function for improved safety against SQL injection vulnerabilities.
For example:
```javascript
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL);
const id = 1;
// Safe and convenient template function usage
const result = await sql`SELECT * FROM table WHERE id = ${id}`;
// For manually parameterized queries, use the query() function
const result = await sql.query('SELECT * FROM table WHERE id = $1', [id]);
// For interpolating trusted strings (like column or table names), use the unsafe() function
const table = condition ? 'table1' : 'table2'; // known-safe string values
const result = await sql`SELECT * FROM ${sql.unsafe(table)} WHERE id = ${id}`;
// Alternatively, use template literals for known-safe values
const table = condition ? sql`table1` : sql`table2`;
const result = await sql`SELECT * FROM ${table} WHERE id = ${id}`;
```
SQL template queries are fully composable, including those with parameters:
```javascript
const name = 'Olivia';
const limit = 1;
const whereClause = sql`WHERE name = ${name}`;
const limitClause = sql`LIMIT ${limit}`;
// Parameters are numbered appropriately at query time
const result = await sql`SELECT * FROM table ${whereClause} ${limitClause}`;
```
You can use raw SQL queries or tools such as [Drizzle-ORM](https://orm.drizzle.team/docs/quick-postgresql/neon), [kysely](https://github.com/kysely-org/kysely), [Zapatos](https://jawj.github.io/zapatos/), and others for type safety.
```javascript
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL);
const posts = await sql`SELECT * FROM posts WHERE id = ${postId}`;
// or using query() for parameterized queries
const posts = await sql.query('SELECT * FROM posts WHERE id = $1', [postId]);
// `posts` is now [{ id: 12, title: 'My post', ... }] (or undefined)
```
```typescript
import { drizzle } from 'drizzle-orm/neon-http';
import { eq } from 'drizzle-orm';
import { neon } from '@neondatabase/serverless';
import { posts } from './schema';
export default async () => {
  const postId = 12;
  const sql = neon(process.env.DATABASE_URL!);
  const db = drizzle(sql);
  const [onePost] = await db.select().from(posts).where(eq(posts.id, postId));
  return new Response(JSON.stringify({ post: onePost }));
};
```
```javascript
import { neon } from '@neondatabase/serverless';
export default async (req: Request) => {
  const sql = neon(process.env.DATABASE_URL);
  const posts = await sql`SELECT * FROM posts WHERE id = ${postId}`;
  // or using query() for parameterized queries
  const posts = await sql.query('SELECT * FROM posts WHERE id = $1', [postId]);
  return new Response(JSON.stringify(posts));
}
export const config = {
  runtime: 'edge',
};
```
```ts
import { neon } from '@neondatabase/serverless';
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(request: NextApiRequest, res: NextApiResponse) {
  const sql = neon(process.env.DATABASE_URL!);
  const posts = await sql`SELECT * FROM posts WHERE id = ${postId}`;
  // or using query() for parameterized queries
  const posts = await sql.query('SELECT * FROM posts WHERE id = $1', [postId]);
  return res.status(200).json(posts);
}
```
The maximum request size and response size for queries over HTTP is 64 MB.
### neon function configuration options
The `neon(...)` function returns a query function that can be used as a template function, with additional properties for special cases:
```javascript
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL);
// Use as a template function (recommended)
const rows = await sql`SELECT * FROM posts WHERE id = ${postId}`;
// Use query() for manually parameterized queries
const rows = await sql.query('SELECT * FROM posts WHERE id = $1', [postId]);
// Use unsafe() for trusted string interpolation
const table = 'posts'; // trusted value
const rows = await sql`SELECT * FROM ${sql.unsafe(table)} WHERE id = ${postId}`;
```
By default, the query function returns only the rows resulting from the provided SQL query, and it returns them as an array of objects where the keys are column names. For example:
```javascript
const rows = await sql`SELECT * FROM posts WHERE id = ${postId}`;
// -> [{ id: 12, title: "My post", ... }]
```
You can customize the return format using the configuration options `fullResults` and `arrayMode`. These options are available both on the `neon(...)` function and on the query function it returns.
- `arrayMode: boolean`, `false` by default
  The default `arrayMode` value is `false`. When it is true, rows are returned as an array of arrays instead of an array of objects:
  ```javascript
  const sql = neon(process.env.DATABASE_URL, { arrayMode: true });
  const rows = await sql`SELECT * FROM posts WHERE id = ${postId}`;
  // -> [[12, "My post", ...]]
  ```
  Or, with the same effect when using query():
  ```javascript
  const sql = neon(process.env.DATABASE_URL);
  const rows = await sql.query('SELECT * FROM posts WHERE id = $1', [postId], { arrayMode: true });
  // -> [[12, "My post", ...]]
  ```
- `fullResults: boolean`
  The default `fullResults` value is `false`. When it is `true`, additional metadata is returned alongside the result rows, which are then found in the `rows` property of the return value. The metadata matches what would be returned by `node-postgres`:
  ```javascript
  const sql = neon(process.env.DATABASE_URL, { fullResults: true });
  const results = await sql`SELECT * FROM posts WHERE id = ${postId}`;
  /* -> {
    rows: [{ id: 12, title: "My post", ... }],
    fields: [
      { name: "id", dataTypeID: 23, ... },
      { name: "title", dataTypeID: 25, ... },
      ...
    ],
    rowCount: 1,
    rowAsArray: false,
    command: "SELECT"
  } 
  */
  ```
  Or, with the same effect when using query():
  ```javascript
  const sql = neon(process.env.DATABASE_URL);
  const results = await sql.query('SELECT * FROM posts WHERE id = $1', [postId], {
    fullResults: true,
  });
  // -> { ... same as above ... }
  ```
- `fetchOptions: Record`
  The `fetchOptions` option can also be passed to either `neon(...)` or the `query` function. This option takes an object that is merged with the options to the `fetch` call.
  For example, to increase the priority of every database `fetch` request:
  ```javascript
  import { neon } from '@neondatabase/serverless';
  const sql = neon(process.env.DATABASE_URL, { fetchOptions: { priority: 'high' } });
  const rows = await sql`SELECT * FROM posts WHERE id = ${postId}`;
  ```
  Or to implement a `fetch` timeout:
  ```javascript
  import { neon } from '@neondatabase/serverless';
  const sql = neon(process.env.DATABASE_URL);
  const abortController = new AbortController();
  const timeout = setTimeout(() => abortController.abort('timed out'), 10000);
  const rows = await sql('SELECT * FROM posts WHERE id = $1', [postId], {
    fetchOptions: { signal: abortController.signal },
  }); // throws an error if no result received within 10s
  clearTimeout(timeout);
  ```
For additional details, see [Options and configuration](https://github.com/neondatabase/serverless/blob/main/CONFIG.md#options-and-configuration).
### Issue multiple queries with the transaction() function
The `transaction(queriesOrFn, options)` function is exposed as a property on the query function. It allows multiple queries to be executed within a single, non-interactive transaction.
The first argument to `transaction()`, `queriesOrFn`, is either an array of queries or a non-async function that receives a query function as its argument and returns an array of queries.
The array-of-queries case looks like this:
```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`],
  {
    isolationLevel: 'RepeatableRead',
    readOnly: true,
  }
);
```
Or as an example of the function case:
```javascript
const [authors, tags] = await neon(process.env.DATABASE_URL).transaction((txn) => [
  txn`SELECT * FROM authors`,
  txn`SELECT * FROM tags`,
]);
```
The optional second argument to `transaction()`, `options`, has the same keys as the options to the ordinary query function — `arrayMode`, `fullResults` and `fetchOptions` — plus three additional keys that concern the transaction configuration. These transaction-related keys are: `isolationMode`, `readOnly` and `deferrable`.
Note that options **cannot** be supplied for individual queries within a transaction. Query and transaction options must instead be passed as the second argument of the `transaction()` function. For example, this `arrayMode` setting is ineffective (and TypeScript won't compile it): `await sql.transaction([sql('SELECT now()', [], { arrayMode: true })])`. Instead, use `await sql.transaction([sql('SELECT now()')], { arrayMode: true })`.
- `isolationMode`
  This option selects a Postgres [transaction isolation mode](https://www.postgresql.org/docs/current/transaction-iso.html). If present, it must be one of `ReadUncommitted`, `ReadCommitted`, `RepeatableRead`, or `Serializable`.
- `readOnly`
  If `true`, this option ensures that a `READ ONLY` transaction is used to execute the queries passed. This is a boolean option. The default value is `false`.
- `deferrable`
  If `true` (and if `readOnly` is also `true`, and `isolationMode` is `Serializable`), this option ensures that a `DEFERRABLE` transaction is used to execute the queries passed. This is a boolean option. The default value is `false`.
For additional details, see [transaction(...) function](https://github.com/neondatabase/serverless/blob/main/CONFIG.md#transaction-function).
### Using transactions with JWT self-verification
When using Row-Level Security (RLS) to secure backend SQL with the Neon serverless driver, you may need to set JWT claims within a transaction context. This is particularly useful for custom JWT verification flows in backend APIs, where you want to ensure user-specific access to rows according to RLS policies.
Here's an example of how to use the `transaction()` function with self-verified JWT claims:
```javascript
import { neon } from '@neondatabase/serverless';
// Example JWT verification function, typically in a separate auth utilitiy file (implement according to your auth provider)
async function verifyJWT(jwtToken, jwksURL) {
  // Your JWT verification logic here
  // This should return the decoded payload
  return { payload: { sub: 'user123', email: 'user@example.com' } };
}
const sql = neon(process.env.DATABASE_URL);
// Get JWT token from request headers or context
const jwtToken = req.headers.authorization?.replace('Bearer ', '');
const jwksURL = process.env.JWKS_URL; // Your JWKS endpoint
// Verify the JWT and extract claims
const { payload } = await verifyJWT(jwtToken, jwksURL);
const claims = JSON.stringify(payload);
// Use transaction to set JWT claims and query data
const [, my_table] = await sql.transaction([
  sql`SELECT set_config('request.jwt.claims', ${claims}, true)`,
  sql`SELECT * FROM my_table`,
]);
```
When using JWT self-verification with RLS, ensure your database connection string uses a role that does **not** have the `BYPASSRLS` attribute. Avoid using the `neondb_owner` role in your connection string, as it bypasses Row-Level Security policies.
This pattern allows you to:
- Verify JWTs using your own authentication logic
- Set the JWT claims in the database session context
- Access JWT claims in your RLS policies
- Execute multiple queries within a single transaction while maintaining the auth context
## Use the driver over WebSockets
The Neon serverless driver supports the [Pool and Client](https://github.com/neondatabase/serverless?tab=readme-ov-file#pool-and-client) constructors for querying over WebSockets.
The `Pool` and `Client` constructors, provide session and transaction support, as well as `node-postgres` compatibility. You can find the API guide for the `Pool` and `Client` constructors in the [node-postgres](https://node-postgres.com/) documentation.
Consider using the driver with `Pool` or `Client` in the following scenarios:
- You already use `node-postgres` in your code base and would like to migrate to using `@neondatabase/serverless`.
- You are writing a new code base and want to use a package that expects a `node-postgres-compatible` driver.
- Your backend service uses sessions / interactive transactions with multiple queries per connection.
You can use the Neon serverless driver in the same way you would use `node-postgres` with `Pool` and `Client`. Where you usually import `pg`, import `@neondatabase/serverless` instead.
```javascript
import { Pool } from '@neondatabase/serverless';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const posts = await pool.query('SELECT * FROM posts WHERE id =$1', [postId]);
pool.end();
```
```typescript
import { Pool, neonConfig } from '@neondatabase/serverless';
import { PrismaNeon } from '@prisma/adapter-neon';
import { PrismaClient } from '@prisma/client';
import dotenv from 'dotenv';
import ws from 'ws';
dotenv.config();
neonConfig.webSocketConstructor = ws;
const connectionString = `${process.env.DATABASE_URL}`;
const pool = new Pool({ connectionString });
const adapter = new PrismaNeon(pool);
const prisma = new PrismaClient({ adapter });
async function main() {
  const posts = await prisma.post.findMany();
}
main();
```
```typescript
import { drizzle } from 'drizzle-orm/neon-serverless';
import { eq } from 'drizzle-orm';
import { Pool } from '@neondatabase/serverless';
import { posts } from './schema';
export default async () => {
  const postId = 12;
  const pool = new Pool({ connectionString: process.env.DATABASE_URL });
  const db = drizzle(pool);
  const [onePost] = await db.select().from(posts).where(eq(posts.id, postId));
  ctx.waitUntil(pool.end());
  return new Response(JSON.stringify({ post: onePost }));
};
```
```javascript
import { Pool } from '@neondatabase/serverless';
export default async (req: Request, ctx: any) => {
  const pool = new Pool({connectionString: process.env.DATABASE_URL});
  await pool.connect();
  const posts = await pool.query('SELECT * FROM posts WHERE id = $1', [postId]);
  ctx.waitUntil(pool.end());
  return new Response(JSON.stringify(post), {
    headers: { 'content-type': 'application/json' }
  });
}
export const config = {
  runtime: 'edge',
};
```
```ts
import { Pool } from '@neondatabase/serverless';
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(request: NextApiRequest, res: NextApiResponse) {
  const pool = new Pool({ connectionString: process.env.DATABASE_URL });
  const posts = await pool.query('SELECT * FROM posts WHERE id = $1', [postId]);
  await pool.end();
  return res.status(500).send(post);
}
```
### Pool and Client usage notes
- In Node.js and some other environments, there's no built-in WebSocket support. In these cases, supply a WebSocket constructor function.
  ```javascript
  import { Pool, neonConfig } from '@neondatabase/serverless';
  import ws from 'ws';
  neonConfig.webSocketConstructor = ws;
  ```
- In serverless environments such as Vercel Edge Functions or Cloudflare Workers, WebSocket connections can't outlive a single request. That means `Pool` or `Client` objects must be connected, used and closed within a single request handler. Don't create them outside a request handler; don't create them in one handler and try to reuse them in another; and to avoid exhausting available connections, don't forget to close them.
For examples that demonstrate these points, see [Pool and Client](https://github.com/neondatabase/serverless?tab=readme-ov-file#pool-and-client).
### Advanced configuration options
For advanced configuration options, see [neonConfig configuration](https://github.com/neondatabase/serverless/blob/main/CONFIG.md#neonconfig-configuration), in the Neon serverless driver GitHub readme.
## Developing locally with the Neon serverless driver
The Neon serverless driver enables you to query data over **HTTP** or **WebSockets** instead of TCP, even though Postgres does not natively support these connection methods. To use the Neon serverless driver locally, you must run a local instance of Neon's proxy and configure it to connect to your local Postgres database.
For a step-by-step guide to setting up a local environment, refer to this community guide: [Local Development with Neon](/guides/local-development-with-neon). The guide demonstrates how to use a [community-developed Docker Compose file](https://github.com/TimoWilhelm/local-neon-http-proxy) to configure a local Postgres database and a Neon proxy service. This setup allows connections over both WebSockets and HTTP.
## Example applications
Explore the example applications that use the Neon serverless driver.
### UNESCO World Heritage sites app
Neon provides an example application to help you get started with the Neon serverless driver. The application generates a `JSON` listing of the 10 nearest UNESCO World Heritage sites using IP geolocation (data copyright © 1992 – 2022 UNESCO/World Heritage Centre).

There are different implementations of the application to choose from.
Raw SQL + Vercel Edge Functions
Raw SQL via https + Vercel Edge Functions
Raw SQL + Cloudflare Workers
Kysely + Vercel Edge Functions
Zapatos + Vercel Edge Functions
Neon + pgTyped on Vercel Edge Functions
Neon + Knex on Vercel Edge Functions
### Ping Thing
The Ping Thing application pings a Neon Serverless Postgres database using a Vercel Edge Function and shows the journey your request makes. You can read more about this application in the accompanying blog post: [How to use Postgres at the Edge](/blog/how-to-use-postgres-at-the-edge)
Ping Thing
## Neon serverless driver GitHub repository and changelog
The GitHub repository and [changelog](https://github.com/neondatabase/serverless/blob/main/CHANGELOG.md) for the Neon serverless driver are found [here](https://github.com/neondatabase/serverless).
## References
- [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
- [node-postgres](https://node-postgres.com/)
- [Drizzle-ORM](https://orm.drizzle.team/docs/quick-postgresql/neon)
- [Schema migration with Neon Postgres and Drizzle ORM](/docs/guides/drizzle-migrations)
- [kysely](https://github.com/kysely-org/kysely)
- [Zapatos](https://jawj.github.io/zapatos/)
- [Vercel Edge Functions](https://vercel.com/docs/functions/edge-functions)
- [Cloudflare Workers](https://developers.cloudflare.com/workers/)
- [Use Neon with Cloudflare Workers](/docs/guides/cloudflare-workers)