--- 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. ## Handling transient connection drops Like any cloud database service, Neon may occasionally experience brief connection drops during maintenance, updates, or network interruptions. When using the Neon serverless driver, especially over HTTP, you should implement retry logic to handle these transient errors gracefully. Here's a minimal retry example using the `async-retry` library with the HTTP driver: ```javascript import { neon } from '@neondatabase/serverless'; import retry from 'async-retry'; const sql = neon(process.env.DATABASE_URL); const result = await retry( async () => { return await sql`SELECT * FROM users WHERE id = ${userId}`; }, { retries: 5, factor: 2, minTimeout: 1000, randomize: true, } ); ``` ## 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). ![UNESCO World Heritage sites app](/docs/changelog/unesco_sites.png) 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)