Serverless / Edge
Development Status: The serverless driver is currently in development. The client-side API is defined but the server-side query endpoint has not been implemented yet. The documentation below describes the planned interface.
Turbine provides an HTTP-based driver for environments where direct TCP connections to Postgres are not possible, such as Vercel Edge Functions, Cloudflare Workers, and Deno Deploy.
The Problem
Edge runtimes typically do not support raw TCP sockets. The standard pg driver requires a TCP connection to Postgres, which means it cannot run on:
- Vercel Edge Functions
- Cloudflare Workers
- Deno Deploy
- AWS Lambda@Edge
The Solution
Turbine's serverless driver sends queries as JSON over HTTP to a Turbine query endpoint, which executes them against the actual database and returns typed results.
import { createServerlessClient } from 'turbine-orm/serverless';
const db = createServerlessClient({
endpoint: 'https://your-turbine-proxy.fly.dev/query',
authToken: process.env.TURBINE_AUTH_TOKEN!,
});
Configuration
import { createServerlessClient } from 'turbine-orm/serverless';
const db = createServerlessClient({
// Required: URL of the Turbine query endpoint
endpoint: process.env.TURBINE_ENDPOINT!,
// Required: authentication token
authToken: process.env.TURBINE_AUTH_TOKEN!,
// Optional: request timeout in ms (default: 10000)
timeout: 10_000,
// Optional: custom fetch implementation
fetch: globalThis.fetch,
// Optional: custom headers
headers: {
'X-Request-Id': crypto.randomUUID(),
},
});
ServerlessConfig
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| endpoint | string | Yes | -- | URL of the Turbine query endpoint |
| authToken | string | Yes | -- | Authentication token |
| timeout | number | No | 10000 | Request timeout in ms |
| fetch | typeof fetch | No | globalThis.fetch | Custom fetch implementation |
| headers | Record<string, string> | No | {} | Custom headers |
Query Methods
query
Execute a SQL query and get all rows:
const result = await db.query<{ id: number; name: string }>(
'SELECT id, name FROM users WHERE org_id = $1',
[42],
);
console.log(result.rows); // [{ id: 1, name: 'Alice' }, ...]
console.log(result.rowCount); // number of rows
console.log(result.durationMs); // server-side execution time
queryOne
Execute a query and get the first row, or null:
const user = await db.queryOne<{ id: number; name: string }>(
'SELECT id, name FROM users WHERE id = $1',
[1],
);
// => { id: 1, name: 'Alice' } | null
sql (Tagged Template)
Use tagged template literals for parameterized queries:
const users = await db.sql<{ id: number; name: string }>`
SELECT id, name FROM users WHERE org_id = ${orgId}
`;
// Interpolated values become $1, $2, etc. -- no SQL injection
batch
Execute multiple queries in a single HTTP request:
const results = await db.batch([
{ sql: 'SELECT * FROM users WHERE id = $1', params: [1] },
{ sql: 'SELECT COUNT(*) FROM posts WHERE user_id = $1', params: [1] },
], {
transaction: true, // wrap in a transaction
});
console.log(results.results[0].rows); // user rows
console.log(results.results[1].rows); // count result
Usage in Edge Functions
Vercel Edge Function
// app/api/users/route.ts
import { createServerlessClient } from 'turbine-orm/serverless';
export const runtime = 'edge';
const db = createServerlessClient({
endpoint: process.env.TURBINE_ENDPOINT!,
authToken: process.env.TURBINE_AUTH_TOKEN!,
});
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const orgId = Number(searchParams.get('orgId'));
const users = await db.sql<{ id: number; name: string; email: string }>`
SELECT id, name, email FROM users WHERE org_id = ${orgId}
`;
return Response.json({ users });
}
Cloudflare Worker
import { createServerlessClient } from 'turbine-orm/serverless';
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const db = createServerlessClient({
endpoint: env.TURBINE_ENDPOINT,
authToken: env.TURBINE_AUTH_TOKEN,
});
const users = await db.sql`SELECT * FROM users LIMIT 10`;
return Response.json({ users });
},
};
HTTP Protocol
The serverless driver communicates via JSON over HTTP.
Single Query
POST /query
Authorization: Bearer <token>
Content-Type: application/json
{
"sql": "SELECT * FROM users WHERE id = $1",
"params": [1],
"mode": "rows"
}
Response:
{
"rows": [{ "id": 1, "name": "Alice", "email": "alice@example.com" }],
"rowCount": 1,
"fields": [
{ "name": "id", "dataTypeID": 20 },
{ "name": "name", "dataTypeID": 25 }
],
"durationMs": 1.2
}
Batch Query
POST /batch
Authorization: Bearer <token>
Content-Type: application/json
{
"queries": [
{ "sql": "SELECT * FROM users WHERE id = $1", "params": [1] },
{ "sql": "SELECT COUNT(*) FROM posts", "params": [] }
],
"transaction": true
}
Standard Driver vs Serverless
| Feature | Standard (pg) | Serverless (HTTP) |
|---|---|---|
| Connection | TCP socket | HTTP fetch |
| Runtime | Node.js, Bun | Any (Edge, Workers, Deno) |
| Typed ORM API | Full (db.users.findMany(...)) | SQL queries only |
| Performance | Lower latency (direct) | Higher latency (HTTP overhead) |
| Connection pooling | Built-in via pg.Pool | Handled by the proxy |
Note: The serverless driver currently provides a SQL query interface. The full typed ORM API (findMany, create, etc.) over HTTP is planned for a future release.
Neon Serverless Alternative
If you are using Neon Postgres, you can also use Neon's own serverless driver (@neondatabase/serverless) as the connection layer:
import { Pool, neonConfig } from '@neondatabase/serverless';
import ws from 'ws';
// Configure Neon for serverless
neonConfig.webSocketConstructor = ws;
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
// Use with Turbine's standard client
// (Neon's serverless driver is pg-compatible)
This gives you the full typed ORM API in serverless environments via Neon's WebSocket-based connection.