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