Files
gh-neondatabase-labs-ai-rul…/skills/neon-drizzle/references/adapters.md
2025-11-30 08:43:11 +08:00

11 KiB

Adapter Reference Guide

Complete guide for choosing between HTTP and WebSocket adapters.

Table of Contents


Quick Decision Matrix

Environment Adapter Reason
Vercel HTTP Edge functions, stateless
Cloudflare Workers HTTP Edge runtime, no WebSocket
AWS Lambda HTTP Stateless, cold starts
Next.js (Vercel) HTTP App Router, Edge Runtime
Express/Fastify WebSocket Long-lived connections
Node.js server WebSocket Connection pooling
Bun server WebSocket Persistent runtime

HTTP Adapter (@neondatabase/serverless with neon-http)

When to Use

Serverless/Edge environments:

  • Vercel Edge Functions
  • Cloudflare Workers
  • AWS Lambda
  • Deno Deploy
  • Next.js App Router (default)

Characteristics:

  • Stateless requests
  • Cold starts
  • Short execution time
  • No persistent connections

Setup

Installation:

npm add drizzle-orm @neondatabase/serverless
npm add -D drizzle-kit

Connection:

import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql);

Complete example: See templates/db-http.ts

Pros

Perfect for serverless:

  • No connection management needed
  • Works in edge environments
  • Fast cold starts
  • Auto-scales

Simple:

  • Minimal configuration
  • No connection pooling complexity
  • Stateless = predictable

Cons

Limited features:

  • No transactions
  • No prepared statements
  • No streaming
  • Higher latency per query

Not ideal for:

  • Batch operations
  • Complex transactions
  • High-frequency queries from same process

Best Practices

1. Use batch for multiple operations:

await db.batch([
  db.insert(users).values({ email: 'test@example.com' }),
  db.insert(posts).values({ title: 'Test' }),
]);

2. Cache query results:

import { unstable_cache } from 'next/cache';

const getUsers = unstable_cache(
  async () => db.select().from(users),
  ['users'],
  { revalidate: 60 }
);

3. Minimize round trips:

const usersWithPosts = await db.query.users.findMany({
  with: { posts: true },
});

WebSocket Adapter (@neondatabase/serverless with neon-serverless)

When to Use

Long-lived processes:

  • Express/Fastify servers
  • Standard Node.js applications
  • Background workers
  • WebSocket servers
  • Bun applications

Characteristics:

  • Persistent connections
  • Long execution time
  • Connection pooling
  • Complex transactions

Setup

Installation:

npm add drizzle-orm @neondatabase/serverless ws
npm add -D drizzle-kit @types/ws

Connection:

import { drizzle } from 'drizzle-orm/neon-serverless';
import { Pool, neonConfig } from '@neondatabase/serverless';
import ws from 'ws';

neonConfig.webSocketConstructor = ws;

const pool = new Pool({
  connectionString: process.env.DATABASE_URL!,
  max: 10,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 5000,
});

export const db = drizzle(pool);

Complete example: See templates/db-websocket.ts

Pros

Full features:

  • Transactions
  • Prepared statements
  • Streaming
  • Lower latency (persistent connection)

Better for:

  • Multiple queries per request
  • Complex business logic
  • High-frequency operations

Cons

More complex:

  • Connection pool management
  • Need to handle connection errors
  • Not available in edge environments

Resource considerations:

  • Connection limits
  • Memory usage
  • Cold start overhead

Best Practices

1. Configure connection pool:

const pool = new Pool({
  connectionString: process.env.DATABASE_URL!,
  max: 10,                    // Max connections
  idleTimeoutMillis: 30000,   // Close idle after 30s
  connectionTimeoutMillis: 5000, // Timeout after 5s
});

2. Graceful shutdown:

process.on('SIGTERM', async () => {
  await pool.end();
  process.exit(0);
});

process.on('SIGINT', async () => {
  await pool.end();
  process.exit(0);
});

3. Use transactions:

await db.transaction(async (tx) => {
  const user = await tx.insert(users)
    .values({ email: 'test@example.com' })
    .returning();

  await tx.insert(posts)
    .values({ userId: user[0].id, title: 'First post' });
});

4. Handle connection errors:

pool.on('error', (err) => {
  console.error('Unexpected pool error:', err);
});

pool.on('connect', () => {
  console.log('Pool connection established');
});

Framework-Specific Recommendations

Next.js

App Router (default):

  • Use HTTP adapter (Edge Runtime)
  • Server Actions → HTTP
  • Route Handlers → HTTP

Pages Router:

  • API Routes → Either adapter works
  • Recommend HTTP for consistency

Example:

// app/actions/users.ts
'use server';

import { db } from '@/db'; // HTTP adapter
import { users } from '@/db/schema';

export async function createUser(email: string) {
  return db.insert(users).values({ email }).returning();
}

Express

Standard setup:

  • Use WebSocket adapter
  • Configure connection pool
  • Implement health checks

Example:

import express from 'express';
import { db } from './db'; // WebSocket adapter
import { users } from './db/schema';

const app = express();

app.get('/health', async (req, res) => {
  try {
    await db.select().from(users).limit(1);
    res.json({ status: 'healthy' });
  } catch (err) {
    res.status(500).json({ status: 'unhealthy', error: err.message });
  }
});

app.listen(3000);

Vite/React (SPA)

Deployment matters:

If deploying to Vercel:

  • API routes → HTTP adapter
  • Static files → No backend needed

If deploying to Node.js server:

  • Backend API → WebSocket adapter
  • Frontend → Fetch from API

Bun

Recommendation:

  • Use WebSocket adapter
  • Bun has built-in WebSocket support
  • No need for ws package

Setup:

import { drizzle } from 'drizzle-orm/neon-serverless';
import { Pool } from '@neondatabase/serverless';

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

Mixed Environments

Using Both Adapters

If you have both serverless and long-lived components:

Structure:

src/
├── db/
│   ├── http.ts        # HTTP adapter for serverless
│   ├── ws.ts          # WebSocket for servers
│   └── schema.ts      # Shared schema

HTTP adapter:

// src/db/http.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';

const sql = neon(process.env.DATABASE_URL!);
export const httpDb = drizzle(sql);

WebSocket adapter:

// src/db/ws.ts
import { drizzle } from 'drizzle-orm/neon-serverless';
import { Pool, neonConfig } from '@neondatabase/serverless';
import ws from 'ws';

neonConfig.webSocketConstructor = ws;
const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
export const wsDb = drizzle(pool);

Usage:

// Vercel Edge Function
import { httpDb as db } from '@/db/http';

// Express route
import { wsDb as db } from '@/db/ws';

Feature Comparison Table

Feature HTTP Adapter WebSocket Adapter
Transactions No Yes
Prepared statements No Yes
Streaming results No Yes
Connection pooling N/A (stateless) Yes
Edge runtime Yes No
Cold start speed Fast ⚠️ Slower
Latency per query ⚠️ Higher Lower
Batch operations Yes Yes
Max connection limit N/A ⚠️ Applies

Performance Considerations

HTTP Adapter Performance

Optimize by:

  • Minimizing round trips
  • Using batch operations
  • Caching query results
  • Pre-fetching related data

Typical latency:

  • Single query: 50-200ms
  • Batch operation: 100-300ms

WebSocket Adapter Performance

Optimize by:

  • Configuring pool size correctly
  • Using transactions for related operations
  • Implementing query caching
  • Monitoring connection usage

Typical latency:

  • First query (connection): 50-100ms
  • Subsequent queries: 10-50ms

Troubleshooting

HTTP Adapter Issues

Problem: "fetch is not defined"

  • Solution: Ensure running in environment with fetch API (Node 18+, edge runtime)

Problem: Slow queries

  • Solution: Use batch operations, reduce round trips

WebSocket Adapter Issues

Problem: "WebSocket is not defined"

  • Solution: Add neonConfig.webSocketConstructor = ws

Problem: "Too many connections"

  • Solution: Reduce pool max size, ensure connections are closed

Problem: Connection timeouts

  • Solution: Increase connectionTimeoutMillis, implement retry logic

Migration Between Adapters

HTTP → WebSocket

When: Moving from serverless to dedicated server.

Steps:

  1. Install ws: npm add ws @types/ws
  2. Update connection file to WebSocket adapter
  3. Update drizzle.config.ts if needed
  4. Test transactions (now available)

WebSocket → HTTP

When: Moving to serverless/edge deployment.

Steps:

  1. Update connection file to HTTP adapter
  2. Remove ws dependency
  3. Important: Replace transactions with batch operations
  4. Test thoroughly (feature differences)

Choosing the Right Adapter

Ask yourself:

  1. Where am I deploying?

    • Edge/Serverless → HTTP
    • Node.js server → WebSocket
  2. Do I need transactions?

    • Yes → WebSocket
    • No → Either works
  3. What's my request pattern?

    • Short, infrequent → HTTP
    • Long, frequent → WebSocket
  4. Am I optimizing for?

    • Cold starts → HTTP
    • Latency → WebSocket

When in doubt: Start with HTTP (simpler), migrate to WebSocket if needed.

  • guides/new-project.md - Setup guides for both adapters
  • guides/troubleshooting.md - Connection error solutions
  • templates/db-http.ts - HTTP adapter template
  • templates/db-websocket.ts - WebSocket adapter template