11 KiB
Adapter Reference Guide
Complete guide for choosing between HTTP and WebSocket adapters.
Table of Contents
- Quick Decision Matrix
- HTTP Adapter
- WebSocket Adapter
- Framework-Specific Recommendations
- Mixed Environments
- Feature Comparison Table
- Performance Considerations
- Troubleshooting
- Migration Between Adapters
- Choosing the Right Adapter
- Related Resources
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
wspackage
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
maxsize, 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:
- Install ws:
npm add ws @types/ws - Update connection file to WebSocket adapter
- Update drizzle.config.ts if needed
- Test transactions (now available)
WebSocket → HTTP
When: Moving to serverless/edge deployment.
Steps:
- Update connection file to HTTP adapter
- Remove ws dependency
- Important: Replace transactions with batch operations
- Test thoroughly (feature differences)
Choosing the Right Adapter
Ask yourself:
-
Where am I deploying?
- Edge/Serverless → HTTP
- Node.js server → WebSocket
-
Do I need transactions?
- Yes → WebSocket
- No → Either works
-
What's my request pattern?
- Short, infrequent → HTTP
- Long, frequent → WebSocket
-
Am I optimizing for?
- Cold starts → HTTP
- Latency → WebSocket
When in doubt: Start with HTTP (simpler), migrate to WebSocket if needed.
Related Resources
guides/new-project.md- Setup guides for both adaptersguides/troubleshooting.md- Connection error solutionstemplates/db-http.ts- HTTP adapter templatetemplates/db-websocket.ts- WebSocket adapter template