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.
| Hyperdrive | Direct TCP | |
|---|---|---|
| Connection reuse across requests | Yes, pooled globally by Cloudflare | No - a new connection per request |
| Query caching | Optional | No |
| Setup | One wrangler command + a binding | A compatibility flag |
| Best for | Production Workers | Quick 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:
{
"compatibility_flags": ["nodejs_compat"],
"hyperdrive": [{ "binding": "HYPERDRIVE", "id": "<id-from-the-create-command>" }]
}Then use any normal driver against env.HYPERDRIVE.connectionString:
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:
{ "compatibility_flags": ["nodejs_compat"] }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-fullverify-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 migratefrom CI or your machine againstDATABASE_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().