Initial commit
This commit is contained in:
478
skills/neon-drizzle/references/adapters.md
Normal file
478
skills/neon-drizzle/references/adapters.md
Normal file
@@ -0,0 +1,478 @@
|
||||
# Adapter Reference Guide
|
||||
|
||||
Complete guide for choosing between HTTP and WebSocket adapters.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Quick Decision Matrix](#quick-decision-matrix)
|
||||
- [HTTP Adapter](#http-adapter-neondatabaseserverless-with-neon-http)
|
||||
- [WebSocket Adapter](#websocket-adapter-neondatabaseserverless-with-neon-serverless)
|
||||
- [Framework-Specific Recommendations](#framework-specific-recommendations)
|
||||
- [Mixed Environments](#mixed-environments)
|
||||
- [Feature Comparison Table](#feature-comparison-table)
|
||||
- [Performance Considerations](#performance-considerations)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Migration Between Adapters](#migration-between-adapters)
|
||||
- [Choosing the Right Adapter](#choosing-the-right-adapter)
|
||||
- [Related Resources](#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:**
|
||||
```bash
|
||||
npm add drizzle-orm @neondatabase/serverless
|
||||
npm add -D drizzle-kit
|
||||
```
|
||||
|
||||
**Connection:**
|
||||
```typescript
|
||||
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:**
|
||||
```typescript
|
||||
await db.batch([
|
||||
db.insert(users).values({ email: 'test@example.com' }),
|
||||
db.insert(posts).values({ title: 'Test' }),
|
||||
]);
|
||||
```
|
||||
|
||||
**2. Cache query results:**
|
||||
```typescript
|
||||
import { unstable_cache } from 'next/cache';
|
||||
|
||||
const getUsers = unstable_cache(
|
||||
async () => db.select().from(users),
|
||||
['users'],
|
||||
{ revalidate: 60 }
|
||||
);
|
||||
```
|
||||
|
||||
**3. Minimize round trips:**
|
||||
```typescript
|
||||
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:**
|
||||
```bash
|
||||
npm add drizzle-orm @neondatabase/serverless ws
|
||||
npm add -D drizzle-kit @types/ws
|
||||
```
|
||||
|
||||
**Connection:**
|
||||
```typescript
|
||||
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:**
|
||||
```typescript
|
||||
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:**
|
||||
```typescript
|
||||
process.on('SIGTERM', async () => {
|
||||
await pool.end();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGINT', async () => {
|
||||
await pool.end();
|
||||
process.exit(0);
|
||||
});
|
||||
```
|
||||
|
||||
**3. Use transactions:**
|
||||
```typescript
|
||||
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:**
|
||||
```typescript
|
||||
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:**
|
||||
```typescript
|
||||
// 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:**
|
||||
```typescript
|
||||
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:**
|
||||
```typescript
|
||||
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:**
|
||||
```typescript
|
||||
// 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:**
|
||||
```typescript
|
||||
// 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:**
|
||||
```typescript
|
||||
// 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.
|
||||
|
||||
## Related Resources
|
||||
|
||||
- `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
|
||||
Reference in New Issue
Block a user