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.

TypeScript
import { createServerlessClient } from 'turbine-orm/serverless';

const db = createServerlessClient({
  endpoint: 'https://your-turbine-proxy.fly.dev/query',
  authToken: process.env.TURBINE_AUTH_TOKEN!,
});

Configuration

TypeScript
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:

TypeScript
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:

TypeScript
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:

TypeScript
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:

TypeScript
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

TypeScript
// 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

TypeScript
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:

JSON
{
  "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:

TypeScript
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.