Hyperdrive (Postgres and MySQL)
Cloudflare's product name is Hyperdrive (Postgres & MySQL). Official docs: developers.cloudflare.com/hyperdrive.
It is easy to read that title as "Cloudflare hosts our database." It does not.
What it is
Hyperdrive sits between Cloudflare Workers and an origin database you already run (RDS, Aurora, Neon, PlanetScale, Cockroach, and other Postgres- or MySQL-compatible hosts).
It:
- Keeps a connection pool close to the origin database.
- Lets each Worker request open a cheap client to Hyperdrive at the edge (avoids TCP + TLS + DB auth round trips on every isolate).
- Optionally caches read queries.
- Hands your Worker a connection string (
env.HYPERDRIVE.connectionString) that existing drivers (pg,mysql2, Prisma adapter) can use.
It does not:
- Create or store your tables.
- Replace PostgreSQL.
- Accept connections from Lightsail, a laptop, or Prisma Studio.
- Run Express, Socket.io, or ffmpeg.
- Speak MySQL to a Postgres schema.
Pooling is transaction mode. A connection returns to the pool when
the transaction ends. Do not hold long transactions to keep SET
state. Create the driver inside the Worker handler, not as a global
new Pool().
Limits (Cloudflare, subject to their current docs): origin pool about 20 connections on Free and 100 on Paid per Hyperdrive config; query duration cap 60s; cached response cap 50 MB. Workers can open many clients to Hyperdrive; Hyperdrive caps connections to RDS.
Verdict for RE360Muse
main-backend is Express + Prisma 6 on a VM. That process should use
DATABASE_URL → RDS (local: your Postgres). See
Production setup.
| Compute | Database path |
|---|---|
| Express on Lightsail | Direct DATABASE_URL to RDS |
wrangler dev / Worker | Hyperdrive binding → same RDS |
| Prisma migrate / seed / studio | Direct DATABASE_URL to RDS (never Hyperdrive) |
Do not set the API DATABASE_URL to a Hyperdrive string. Hyperdrive
strings only exist inside a Worker binding.
MySQL support in Hyperdrive does not change our engine. Schema and Prisma datasource stay postgresql.
Use Hyperdrive later only if we add a Worker that must query the same Postgres (for example a globally distributed public listing read path). The Worker pools through Hyperdrive; Express still talks to RDS.
Origin database (still required)
You need a reachable Postgres (or MySQL, if it were a different app).
For RE360Muse that origin is RDS PostgreSQL, same instance as the API.
RDS must allow Hyperdrive to connect:
- Publicly reachable or
- Workers VPC (Cloudflare's recommended private path, beta) or
- Cloudflare Tunnel (beta).
If the instance is public, restrict the security group using Cloudflare's
current Hyperdrive IP list:
Firewall and networking.
Do not leave 0.0.0.0/0 on 5432.
Prefer keeping RDS private and only allowing Lightsail via VPC peering. Add Hyperdrive later with VPC/Tunnel rather than opening the database to the internet.
SSL is required in production (sslmode=require). Hyperdrive can use
custom CA material (TLS/SSL certificates).
Create a dedicated DB user for Hyperdrive if a Worker exists, with the least privilege that Worker needs (often read-only on public listing tables). Do not reuse the Express owner's password in the Worker if you can avoid it.
Worker setup (only if we add a Worker)
Prerequisites: Cloudflare account, Node 20+, Wrangler, origin connection string (names only — paste values in the dashboard or CLI, not git).
npx wrangler login
npx wrangler hyperdrive create re360muse-rds --connection-string="postgres://USER:PASSWORD@RDS_HOST:5432/DBNAME"
Wrangler prints a Hyperdrive id. Bind it in wrangler.jsonc:
{
"name": "re360muse-listing-read",
"main": "src/index.ts",
"compatibility_date": "2026-09-18",
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "<HYPERDRIVE_ID>",
"localConnectionString": "postgres://USER:PASSWORD@127.0.0.1:5432/DBNAME"
}
]
}
localConnectionString is for wrangler dev. It talks to local
Postgres, not to Cloudflare's pool.
Postgres driver
npm i pg
npm i -D @types/pg
Create the client per request:
import { Client } from "pg";
export interface Env {
HYPERDRIVE: Hyperdrive;
}
export default {
async fetch(_req: Request, env: Env): Promise<Response> {
const client = new Client({
connectionString: env.HYPERDRIVE.connectionString,
});
await client.connect();
try {
const result = await client.query(
"select id, name from \"Property\" limit 20",
);
return Response.json(result.rows);
} finally {
await client.end();
}
},
};
Supported Postgres drivers (Cloudflare's list, not exhaustive):
pg ≥ 8.13 (avoid 8.11.4), Postgres.js ≥ 3.4.4, Drizzle/Kysely on
pg. See
Connect to PostgreSQL.
Prisma from a Worker (not from Express)
Express on Lightsail keeps the current Prisma Client and
DATABASE_URL. A Worker that wants Prisma must use the driver
adapter path Cloudflare documents (@prisma/adapter-pg, generate
with --no-engine). That is a second runtime, not a change to
main-backend.
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "@prisma/client";
const adapter = new PrismaPg({
connectionString: env.HYPERDRIVE.connectionString,
});
const prisma = new PrismaClient({ adapter });
Migrations still run from CI or the API box against RDS directly:
npx prisma migrate deploy
MySQL (not used)
Hyperdrive MySQL is mysql://USER:PASSWORD@HOST:3306/DB and mysql2
with disableEval: true on Workers. RE360Muse does not use this.
Pool size
npx wrangler hyperdrive update <HYPERDRIVE_ID> --origin-connection-limit=10
Minimum 5. Stay well under RDS max_connections. Remember Express also
opens Prisma connections to the same instance.
Credentials
Rotate the origin password, then update the Hyperdrive config
(rotating credentials).
Updating Hyperdrive does not change Lightsail DATABASE_URL; update
that env separately.
What we will not do
- Put Hyperdrive in front of
main-backend. - Host the OS schema on D1, Hyperdrive-only, or MySQL.
- Open RDS to the world "so Hyperdrive can try it."
- Cache Paystack, wallet, or membership writes through Hyperdrive read cache.
Source of truth for production topology: Production setup.