Postgres 18 just shipped, and it’s already available on Neon. Deploy it in seconds and try the new features
/AI for Agents/Neon Serverless

AI Rules Neon Serverless Driver

Context rules for AI tools to help implement the Neon Serverless driver

How to use

You can use these rules in two ways:

  1. Option 1: Copy from this page

    With Cursor, save the rules to .cursor/rules/neon-serverless.mdc and 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).

  2. 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 });
}
```

Last updated on

Was this page helpful?