CapyDB Docs
Getting StartedConnect your framework

Cloudflare Workers

Connect a Worker to a CapyDB cell - over Hyperdrive, or directly over TCP with node compatibility.

Workers can reach a CapyDB cell two ways. Both use a standard postgres:// connection string - there is no CapyDB-specific driver to install, and nothing about your cell needs changing.

HyperdriveDirect TCP
Connection reuse across requestsYes, pooled globally by CloudflareNo - a new connection per request
Query cachingOptionalNo
SetupOne wrangler command + a bindingA compatibility flag
Best forProduction WorkersQuick starts, low traffic, or when you want no intermediary

Cloudflare recommends Hyperdrive for traditional hosted Postgres, and it is the better default here too.

Option A - Hyperdrive

Hyperdrive takes the same connection string you would paste anywhere else. Use the pooled (:6432) URL so Hyperdrive's connections land on your cell's own pooler and stay within your plan's connection budget:

capydb env pull                     # DATABASE_URL (pooled) + DATABASE_DIRECT_URL
npx wrangler hyperdrive create my-cell \
  --connection-string="postgres://USER:PASSWORD@YOUR-CELL.db.capydb.dev:6432/DBNAME?sslmode=verify-full"

Bind the returned ID in wrangler.jsonc:

wrangler.jsonc
{
  "compatibility_flags": ["nodejs_compat"],
  "hyperdrive": [{ "binding": "HYPERDRIVE", "id": "<id-from-the-create-command>" }]
}

Then use any normal driver against env.HYPERDRIVE.connectionString:

src/index.ts
import postgres from 'postgres'

export default {
  async fetch(request: Request, env: Env) {
    // prepare: false - the cell's pooler runs in transaction mode, so named
    // prepared statements cannot be relied on between statements.
    const sql = postgres(env.HYPERDRIVE.connectionString, { prepare: false, max: 5 })
    const rows = await sql`select now()`
    return Response.json(rows)
  },
}

Option B - direct TCP

Workers can open TCP sockets directly. Enable node compatibility and connect as you would anywhere else:

wrangler.jsonc
{ "compatibility_flags": ["nodejs_compat"] }
src/index.ts
import postgres from 'postgres'

export default {
  async fetch(request: Request, env: Env) {
    const sql = postgres(env.DATABASE_URL, { prepare: false, max: 1 })
    const rows = await sql`select now()`
    return Response.json(rows)
  },
}

Use the pooled (:6432) URL here. Without Hyperdrive every request opens a brand-new connection, and a busy Worker will exhaust your plan's connection budget against the direct port within seconds. The pooled endpoint exists precisely to absorb that pattern - see Connection pooling.

TLS

Your cell is served from a publicly trusted certificate (*.db.capydb.dev), so you can use the strictest setting rather than merely encrypting:

?sslmode=verify-full

verify-full checks the certificate chain and the hostname, which require does not - require encrypts the connection but will happily talk to anything that answers. Prefer verify-full from Workers, where you have no control over the network path.

What does not apply

  • No special driver. The Neon-style HTTP/WebSocket drivers exist because V8 isolates historically could not open TCP sockets. Workers can now, so a plain Postgres driver works.
  • Migrations do not belong in a Worker. Run drizzle-kit/prisma migrate from CI or your machine against DATABASE_DIRECT_URL; a request-scoped runtime is the wrong place for advisory locks and long transactions.

Cold starts

If your cell is paused by scale-to-zero, the first connection is held while it resumes and then served normally - your Worker sees a slower request, not an error. For scheduled Workers that are the first thing to touch a paused cell and want a bounded warm-up, @capydb/drizzle exports waitForWake().