# Hyperdrive (Postgres and MySQL)

Cloudflare's product name is **Hyperdrive (Postgres & MySQL)**. Official
docs: [developers.cloudflare.com/hyperdrive](https://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:

1. Keeps a **connection pool close to the origin database**.
2. Lets each Worker request open a cheap client to Hyperdrive at the
   edge (avoids TCP + TLS + DB auth round trips on every isolate).
3. Optionally **caches read queries**.
4. 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](./production.md).

| 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](https://developers.cloudflare.com/hyperdrive/configuration/connect-to-private-database-vpc/)
  (Cloudflare's recommended private path, beta) **or**
- [Cloudflare Tunnel](https://developers.cloudflare.com/hyperdrive/configuration/connect-to-private-database/)
  (beta).

If the instance is public, restrict the security group using Cloudflare's
current Hyperdrive IP list:
[Firewall and networking](https://developers.cloudflare.com/hyperdrive/configuration/firewall-and-networking-configuration/).
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](https://developers.cloudflare.com/hyperdrive/configuration/tls-ssl-certificates-for-hyperdrive/)).

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

```bash
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`:

```json
{
  "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

```bash
npm i pg
npm i -D @types/pg
```

Create the client **per request**:

```ts
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](https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/).

### 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`.

```ts
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:

```bash
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

```bash
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](https://developers.cloudflare.com/hyperdrive/configuration/rotate-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](./production.md).
