Related docs
Repository
How to use
You can use these rules in two ways:
- Option 1: Copy from this page- With Cursor, save the rules to - .cursor/rules/neon-serverless.mdcand they'll be automatically applied when working with matching files (- *.ts,- *.tsx).- For other AI tools, you can include these rules as context when chatting with your AI assistant - check your tool's documentation for the specific method (like using "Include file" or context commands). 
- Option 2: Clone from repository- If you prefer, you can clone or download the rules directly from our AI Rules repository. - Once added to your project, AI tools will automatically use these rules when working with Neon Serverless code. You can also reference them explicitly in prompts. 
Rules
---
description: Use these rules to query your Neon database using the Neon Serverless driver
globs: *.tsx, *.ts
alwaysApply: false
---
# Neon Serverless Driver Guidelines
## Overview
This guide provides specific patterns and best practices for connecting to Neon databases in serverless environments using the `@neondatabase/serverless` driver. The driver connects over **HTTP** for fast, single queries or **WebSockets** for `node-postgres` compatibility and interactive transactions. Follow these guidelines to ensure efficient connections and optimal performance.
## Installation
Install the Neon Serverless driver with the correct package name:
```bash
# Using npm
npm install @neondatabase/serverless
# Using JSR
bunx jsr add @neon/serverless
```
**Note:** The driver version 1.0.0 and higher requires **Node.js v19 or later**.
For projects that depend on `pg` but want to use Neon's WebSocket-based connection pool:
```json
"dependencies": {
  "pg": "npm:@neondatabase/serverless@^0.10.4"
},
"overrides": {
  "pg": "npm:@neondatabase/serverless@^0.10.4"
}
```
Avoid incorrect package names like `neon-serverless` or `pg-neon`.
## Connection String
Always use environment variables for database connection strings to avoid exposing credentials.
```typescript
// For HTTP queries
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// For WebSocket connections
import { Pool } from '@neondatabase/serverless';
const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
```
Never hardcode credentials in your code:
```typescript
// AVOID: Hardcoded credentials
const sql = neon('postgres://username:password@host.neon.tech/neondb');
```
## Querying with the `neon` function (HTTP)
The `neon()` function is ideal for simple, "one-shot" queries in serverless and edge environments as it uses HTTP `fetch` and is the fastest method for single queries.
### Parameterized Queries
Use tagged template literals for safe parameter interpolation. This is the primary defense against SQL injection.
```typescript
const [post] = await sql`SELECT * FROM posts WHERE id = ${postId}`;
```
For manually constructed queries, use the `.query()` method with a parameter array:
```typescript
const [post] = await sql.query('SELECT * FROM posts WHERE id = $1', [postId]);
```
**Do not** concatenate user input directly into SQL strings:
```typescript
// AVOID: SQL Injection Risk
const [post] = await sql('SELECT * FROM posts WHERE id = ' + postId);
```
### Configuration Options
You can configure the `neon()` function to change the result format.
```typescript
// Return rows as arrays instead of objects
const sqlArrayMode = neon(process.env.DATABASE_URL!, { arrayMode: true });
const rows = await sqlArrayMode`SELECT id, title FROM posts`;
// rows -> [[1, "First Post"], [2, "Second Post"]]
// Get full results including row count and field metadata
const sqlFull = neon(process.env.DATABASE_URL!, { fullResults: true });
const result = await sqlFull`SELECT * FROM posts LIMIT 1`;
/* result -> {
     rows: [{ id: 1, title: 'First Post', ... }],
     fields: [...],
     rowCount: 1,
     ...
   }
*/
```
## Querying with `Pool` and `Client` (WebSockets)
Use the `Pool` and `Client` classes for `node-postgres` compatibility, interactive transactions, or session support. This method uses WebSockets.
### WebSocket Configuration
In Node.js environments version 21 and earlier, a WebSocket implementation must be provided.
```typescript
import { Pool, neonConfig } from '@neondatabase/serverless';
import ws from 'ws';
// This is only required for Node.js < v22
neonConfig.webSocketConstructor = ws;
const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
// ... use pool
```
### Serverless Lifecycle Management
When using a `Pool` in a serverless function, the connection must be created, used, and closed within the same invocation.
```typescript
// Example for Vercel Edge Functions
export default async (req: Request, ctx: ExecutionContext) => {
  // Create pool inside the request handler
  const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
  
  try {
    const { rows } = await pool.query('SELECT * FROM users');
    return new Response(JSON.stringify(rows));
  } catch (err) {
    console.error(err);
    return new Response('Database error', { status: 500 });
  } finally {
    // End the pool connection before the function execution completes
    ctx.waitUntil(pool.end());
  }
}
```
Avoid creating a global `Pool` instance outside the handler, as it may not be closed properly, leading to exhausted connections.
## Handling Transactions
### HTTP Transactions (`sql.transaction()`)
For running multiple queries in a single, non-interactive transaction over HTTP, use `sql.transaction()`. This is efficient and recommended for atomicity without the overhead of a persistent WebSocket.
```typescript
const [newUser, newProfile] = await sql.transaction([
  sql`INSERT INTO users(name) VALUES(${name}) RETURNING id`,
  sql`INSERT INTO profiles(user_id, bio) VALUES(${userId}, ${bio})`
], {
  // Optional transaction settings
  isolationLevel: 'ReadCommitted',
  readOnly: false
});
```
### Interactive Transactions (`Client`)
For complex transactions that require conditional logic, use a `Client` from a `Pool`.
```typescript
const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
const client = await pool.connect();
try {
  await client.query('BEGIN');
  const { rows: [{ id }] } = await client.query(
    'INSERT INTO users(name) VALUES($1) RETURNING id', [name]
  );
  await client.query(
    'INSERT INTO profiles(user_id, bio) VALUES($1, $2)', [id, bio]
  );
  await client.query('COMMIT');
} catch (err) {
  await client.query('ROLLBACK');
  throw err;
} finally {
  client.release();
  await pool.end(); // also close pool if no longer needed
}
```
Always include proper error handling and rollback mechanisms.
## Environment-Specific Optimizations
Apply environment-specific optimizations for best performance:
```javascript
// For Vercel Edge Functions, specify nearest region
export const config = {
  runtime: 'edge',
  regions: ['iad1'], // Region nearest to your Neon DB
};
// For Cloudflare Workers, consider using Hyperdrive instead
// https://neon.tech/blog/hyperdrive-neon-faq
```
## Library Integration (ORMs)
Integrate with popular ORMs by providing the appropriate driver interface.
### Drizzle ORM
Drizzle supports both HTTP and WebSocket clients. Choose the one that fits your needs:
- **With `neon()` (HTTP):** Use `drizzle-orm/neon-http`. Best for serverless/edge.
- **With `Pool` (WebSocket):** Use `drizzle-orm/neon-serverless`.
```typescript
import { neon, neonConfig, Pool } from '@neondatabase/serverless';
import { drizzle as drizzleWs } from 'drizzle-orm/neon-serverless';
import { drizzle as drizzleHttp } from 'drizzle-orm/neon-http';
import ws from 'ws';
const connectionString = process.env.DATABASE_URL!;
neonConfig.webSocketConstructor = ws; // Only required for Node.js < v22
const sql = neon(connectionString);
const pool = new Pool({ connectionString });
export const drizzleClientHttp = drizzleHttp({ client: sql });
export const drizzleClientWs = drizzleWs({ client: pool });
```
### Prisma
Prisma supports both HTTP and WebSocket clients. Choose the one that fits your needs:
```typescript
import { neonConfig } from '@neondatabase/serverless';
import { PrismaNeon, PrismaNeonHTTP } from '@prisma/adapter-neon';
import { PrismaClient } from '@prisma/client';
import ws from 'ws';
const connectionString = process.env.DATABASE_URL;
neonConfig.webSocketConstructor = ws;
const adapterHttp = new PrismaNeonHTTP(connectionString!, {});
export const prismaClientHttp = new PrismaClient({ adapter: adapterHttp });
const adapterWs = new PrismaNeon({ connectionString });
export const prismaClientWs = new PrismaClient({ adapter: adapterWs });
```
### Kysely
Use the `PostgresDialect` with a `Pool` instance.
```typescript
import { Pool } from '@neondatabase/serverless';
import { Kysely, PostgresDialect } from 'kysely';
const dialect = new PostgresDialect({
  pool: new Pool({ connectionString: process.env.DATABASE_URL })
});
const db = new Kysely({
  dialect,
  // schema definitions...
});
```
**NOTE:** Do not pass the `neon()` function to ORMs that expect a `node-postgres` compatible `Pool`. Use the appropriate adapter or dialect with a `new Pool()`.
## Error Handling
Implement proper error handling for database operations:
```javascript
// Pool error handling
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
pool.on('error', (err) => {
  console.error('Unexpected error on idle client', err);
  process.exit(-1);
});
// Query error handling
try {
  const [post] = await sql`SELECT * FROM posts WHERE id = ${postId}`;
  if (!post) {
    return new Response('Not found', { status: 404 });
  }
} catch (err) {
  console.error('Database query failed:', err);
  return new Response('Server error', { status: 500 });
}
```