Skip to main content
The Serverless Proxy enables you to connect to your Xata PostgreSQL database using HTTP and WebSocket protocols, in addition to the standard PostgreSQL wire protocol (TCP). This is ideal for serverless functions and edge environments where maintaining persistent TCP connections is not practical.
Xata implements the Neon serverless driver protocol, allowing you to use the @neondatabase/serverless npm package to connect to your Xata database over HTTP or WebSocket. If you’re migrating from Neon, and you are using the serverless driver, you don’t need any code changes.

Why use the Serverless Proxy?

Traditional PostgreSQL connections use TCP, which requires maintaining a persistent connection. This works well for long-running servers but presents challenges in serverless environments:
  • Cold starts: Serverless functions spin up and down frequently, making persistent connections impractical
  • Edge locations: Edge functions often have restrictions on outbound TCP connections
  • Connection limits: Each serverless invocation could exhaust your database connections
The Serverless Proxy solves these problems by allowing stateless HTTP requests and efficient WebSocket connections to your database.

Connection methods

Xata supports three ways to connect to your database:
MethodProtocolBest for
StandardTCP (wire protocol)Traditional servers, long-running applications
HTTPHTTPSServerless functions, one-off queries
WebSocketWSSEdge functions, interactive applications

Get started

Install the @neondatabase/serverless driver:
npm install @neondatabase/serverless
Set the DATABASE_URL environment variable to your connection string. You can obtain it by running xata branch url or by copying the connection string from the Xata console:
export DATABASE_URL=$(xata branch url)

HTTP vs WebSocket

The driver supports two modes of communication, each suited to different use cases:
FeatureHTTPWebSocket
Connection overheadNone (stateless)One-time handshake
Latency per queryHigher (new request each time)Lower (reuses connection)
TransactionsNot supportedSupported
Session variablesNot supportedSupported
Prepared statementsNot supportedSupported
Multiple queriesOne per requestBatched efficiently
Best forSingle, simple queriesComplex operations
Use HTTP when:
  • You only need to run a single query per request
  • Your queries are simple SELECTs or single INSERTs
  • You want the simplest possible setup with zero connection management
Use WebSocket when:
  • You need to run transactions (BEGIN/COMMIT/ROLLBACK)
  • You’re executing multiple queries in a single request
  • You need session-level features like SET commands or prepared statements
  • You want lower per-query latency for multiple operations

HTTP connections

Use HTTP for simple, stateless queries. Each query is a single HTTP request—no connection management required.
import { neon } from '@neondatabase/serverless';

const sql = neon(process.env.DATABASE_URL);

const users = await sql`SELECT * FROM users WHERE id = ${userId}`;
HTTP connections are stateless. Each query runs in its own transaction and cannot share state with other queries. Use WebSocket connections if you need transactions or session-level features.

WebSocket connections

Use WebSocket connections when you need transactions, session variables, or want to execute multiple queries efficiently. The WebSocket mode maintains a persistent connection, allowing for stateful interactions with the database.
If you are running in Node.js versions earlier than 21, you need to supply a WebSocket implementation. Install the ws package (npm install ws) and configure it before creating a pool:
import { neonConfig } from '@neondatabase/serverless';
import ws from 'ws';

neonConfig.webSocketConstructor = ws;
This is not needed in edge runtimes (Vercel Edge, Cloudflare Workers, Deno) or Node.js 21+, which have built-in WebSocket support.
import { Pool } from '@neondatabase/serverless';

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

const client = await pool.connect();
try {
  await client.query('BEGIN');
  await client.query('INSERT INTO orders (user_id) VALUES ($1)', [userId]);
  await client.query('UPDATE inventory SET quantity = quantity - 1 WHERE product_id = $1', [productId]);
  await client.query('COMMIT');
} catch (e) {
  await client.query('ROLLBACK');
  throw e;
} finally {
  client.release();
}

Framework examples

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

export async function GET() {
  const sql = neon(process.env.DATABASE_URL!);
  const users = await sql`SELECT * FROM users LIMIT 10`;
  return Response.json(users);
}