# Source: https://neon.com/llms/ai-ai-rules-neon-serverless.txt

# AI Rules Neon Serverless Driver

> The document outlines the AI Rules for the Neon Serverless Driver, detailing how AI-driven optimizations are implemented to enhance database performance and efficiency within Neon's serverless architecture.

## Source

- [AI Rules Neon Serverless Driver HTML](https://neon.com/docs/ai/ai-rules-neon-serverless): The original HTML version of this documentation

**Note** AI Rules are in Beta: AI Rules are currently in beta. We're actively improving them and would love to hear your feedback. Join us on [Discord](https://discord.gg/92vNTzKDGp) to share your experience and suggestions.

Related docs:
- [Neon Serverless Driver](https://neon.com/docs/serverless/serverless-driver)

Repository:
- [README](https://github.com/neondatabase-labs/ai-rules#readme)
- [neon-serverless.mdc](https://github.com/neondatabase-labs/ai-rules/blob/main/neon-serverless.mdc)

## How to use

You can use these rules in two ways:

## Option 1: Copy from this page

With Cursor, save the [rules](https://docs.cursor.com/context/rules-for-ai#project-rules-recommended) 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).

## Option 2: Clone from repository

If you prefer, you can clone or download the rules directly from our [AI Rules repository](https://github.com/neondatabase-labs/ai-rules).

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

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