
Cloudflare Hyperdrive
- 41 installs
- 16 repo stars
- Updated November 20, 2025
- jackspace/claudeskillz
Connects Cloudflare Workers to existing PostgreSQL and MySQL databases via Hyperdrive with global connection pooling, query caching, and reduced latency.
About
A skill for Cloudflare Hyperdrive, which accelerates Worker connections to existing PostgreSQL and MySQL databases with pooling and caching. Developers use it to connect Workers to RDS/Aurora/Neon/Supabase with node-postgres, postgres.js, mysql2, Drizzle, or Prisma.
- Global connection pooling and query caching for Postgres/MySQL
- Works with node-postgres, postgres.js, mysql2, Drizzle, and Prisma
Cloudflare Hyperdrive by the numbers
- 41 all-time installs (skills.sh)
- Ranked #443 of 911 Databases skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jackspace/claudeskillz --skill cloudflare-hyperdriveAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 41 |
|---|---|
| repo stars | ★ 16 |
| Last updated | November 20, 2025 |
| Repository | jackspace/claudeskillz ↗ |
What it does
Connects Cloudflare Workers to existing PostgreSQL and MySQL databases via Hyperdrive with global connection pooling, query caching, and reduced latency.
Files
Cloudflare Hyperdrive
Status: Production Ready ✅ Last Updated: 2025-10-22 Dependencies: cloudflare-worker-base (recommended for Worker setup) Latest Versions: wrangler@4.43.0+, pg@8.13.0+, postgres@3.4.5+, mysql2@3.13.0+
---
Quick Start (5 Minutes)
1. Create Hyperdrive Configuration
# For PostgreSQL
npx wrangler hyperdrive create my-postgres-db \
--connection-string="postgres://user:password@db-host.cloud:5432/database"
# For MySQL
npx wrangler hyperdrive create my-mysql-db \
--connection-string="mysql://user:password@db-host.cloud:3306/database"
# Output:
# ✅ Successfully created Hyperdrive configuration
#
# [[hyperdrive]]
# binding = "HYPERDRIVE"
# id = "a76a99bc-7901-48c9-9c15-c4b11b559606"Save the `id` value - you'll need it in the next step!
---
2. Configure Bindings in wrangler.jsonc
Add to your wrangler.jsonc:
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2024-09-23",
"compatibility_flags": ["nodejs_compat"], // REQUIRED for database drivers
"hyperdrive": [
{
"binding": "HYPERDRIVE", // Available as env.HYPERDRIVE
"id": "a76a99bc-7901-48c9-9c15-c4b11b559606" // From wrangler hyperdrive create
}
]
}CRITICAL:
nodejs_compatflag is REQUIRED for all database driversbindingis how you access Hyperdrive in code (env.HYPERDRIVE)idis the Hyperdrive configuration ID (NOT your database ID)
---
3. Install Database Driver
# For PostgreSQL (choose one)
npm install pg # node-postgres (most common)
npm install postgres # postgres.js (modern, minimum v3.4.5)
# For MySQL
npm install mysql2 # mysql2 (minimum v3.13.0)---
4. Query Your Database
PostgreSQL with node-postgres (pg):
import { Client } from "pg";
type Bindings = {
HYPERDRIVE: Hyperdrive;
};
export default {
async fetch(request: Request, env: Bindings, ctx: ExecutionContext) {
const client = new Client({
connectionString: env.HYPERDRIVE.connectionString
});
await client.connect();
try {
const result = await client.query('SELECT * FROM users LIMIT 10');
return Response.json({ users: result.rows });
} finally {
// Clean up connection AFTER response is sent
ctx.waitUntil(client.end());
}
}
};MySQL with mysql2:
import { createConnection } from "mysql2/promise";
export default {
async fetch(request: Request, env: Bindings, ctx: ExecutionContext) {
const connection = await createConnection({
host: env.HYPERDRIVE.host,
user: env.HYPERDRIVE.user,
password: env.HYPERDRIVE.password,
database: env.HYPERDRIVE.database,
port: env.HYPERDRIVE.port,
disableEval: true // REQUIRED for Workers (eval() not supported)
});
try {
const [rows] = await connection.query('SELECT * FROM users LIMIT 10');
return Response.json({ users: rows });
} finally {
ctx.waitUntil(connection.end());
}
}
};---
5. Deploy
npx wrangler deployThat's it! Your Worker now connects to your existing database via Hyperdrive with:
- ✅ Global connection pooling
- ✅ Automatic query caching
- ✅ Reduced latency (eliminates 7 round trips)
---
How Hyperdrive Works
The Problem
Connecting to traditional databases from Cloudflare's 300+ global locations presents challenges:
1. High Latency - Multiple round trips for each connection:
- TCP handshake (1 round trip)
- TLS negotiation (3 round trips)
- Database authentication (3 round trips)
- Total: 7 round trips before you can even send a query
2. Connection Limits - Traditional databases handle limited concurrent connections, easily exhausted by distributed traffic
The Solution
Hyperdrive solves these problems by:
1. Edge Connection Setup - Connection handshake happens near your Worker (low latency) 2. Connection Pooling - Pool near your database reuses connections (eliminates round trips) 3. Query Caching - Popular queries cached at the edge (reduces database load)
Result: Single-region databases feel globally distributed.
---
Complete Setup Process
Step 1: Prerequisites
You need:
- Cloudflare account with Workers access
- Existing PostgreSQL (v9.0-17.x) or MySQL (v5.7-8.x) database
- Database accessible via:
- Public internet (with TLS/SSL enabled), OR
- Private network (via Cloudflare Tunnel)
Important: Hyperdrive requires TLS/SSL. Ensure your database has encryption enabled.
---
Step 2: Create Hyperdrive Configuration
Option A: Wrangler CLI (Recommended)
# PostgreSQL connection string format:
# postgres://username:password@hostname:port/database_name
npx wrangler hyperdrive create my-hyperdrive \
--connection-string="postgres://myuser:mypassword@db.example.com:5432/mydb"
# MySQL connection string format:
# mysql://username:password@hostname:port/database_name
npx wrangler hyperdrive create my-hyperdrive \
--connection-string="mysql://myuser:mypassword@db.example.com:3306/mydb"Option B: Cloudflare Dashboard
1. Go to Hyperdrive Dashboard 2. Click Create Configuration 3. Enter connection details:
- Name:
my-hyperdrive - Protocol: PostgreSQL or MySQL
- Host:
db.example.com - Port:
5432(PostgreSQL) or3306(MySQL) - Database:
mydb - Username:
myuser - Password:
mypassword
4. Click Create
Connection String Formats:
# PostgreSQL (standard)
postgres://user:password@host:5432/database
# PostgreSQL with SSL mode
postgres://user:password@host:5432/database?sslmode=require
# MySQL
mysql://user:password@host:3306/database
# With special characters in password (URL encode)
postgres://user:p%40ssw%24rd@host:5432/database # p@ssw$rd---
Step 3: Configure Worker Bindings
Add Hyperdrive binding to wrangler.jsonc:
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2024-09-23",
"compatibility_flags": ["nodejs_compat"],
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "<your-hyperdrive-id-here>"
}
]
}Multiple Hyperdrive configs:
{
"hyperdrive": [
{
"binding": "POSTGRES_DB",
"id": "postgres-hyperdrive-id"
},
{
"binding": "MYSQL_DB",
"id": "mysql-hyperdrive-id"
}
]
}Access in Worker:
type Bindings = {
POSTGRES_DB: Hyperdrive;
MYSQL_DB: Hyperdrive;
};
export default {
async fetch(request, env: Bindings, ctx) {
// Access different databases
const pgClient = new Client({ connectionString: env.POSTGRES_DB.connectionString });
const mysqlConn = await createConnection({ host: env.MYSQL_DB.host, ... });
}
};---
Step 4: Install Database Driver
PostgreSQL Drivers:
# Option 1: node-postgres (pg) - Most popular
npm install pg
npm install @types/pg # TypeScript types
# Option 2: postgres.js - Modern, faster (minimum v3.4.5)
npm install postgres@^3.4.5MySQL Drivers:
# mysql2 (minimum v3.13.0)
npm install mysql2Driver Comparison:
| Driver | Database | Pros | Cons | Min Version |
|---|---|---|---|---|
| pg | PostgreSQL | Most popular, stable, well-documented | Slightly slower than postgres.js | 8.13.0+ |
| postgres | PostgreSQL | Faster, modern API, streaming support | Newer (less community examples) | 3.4.5+ |
| mysql2 | MySQL | Promises, prepared statements, fast | Requires disableEval: true for Workers | 3.13.0+ |
---
Step 5: Use Driver in Worker
PostgreSQL with pg (Client):
import { Client } from "pg";
export default {
async fetch(request: Request, env: { HYPERDRIVE: Hyperdrive }, ctx: ExecutionContext) {
// Create client for this request
const client = new Client({
connectionString: env.HYPERDRIVE.connectionString
});
await client.connect();
try {
// Run query
const result = await client.query('SELECT $1::text as message', ['Hello from Hyperdrive!']);
return Response.json(result.rows);
} catch (error) {
return new Response(`Database error: ${error.message}`, { status: 500 });
} finally {
// CRITICAL: Clean up connection after response
ctx.waitUntil(client.end());
}
}
};PostgreSQL with pg (Pool for parallel queries):
import { Pool } from "pg";
export default {
async fetch(request: Request, env: { HYPERDRIVE: Hyperdrive }, ctx: ExecutionContext) {
// Create pool (max 5 to stay within Workers' 6 connection limit)
const pool = new Pool({
connectionString: env.HYPERDRIVE.connectionString,
max: 5 // CRITICAL: Workers limit is 6 concurrent external connections
});
try {
// Run parallel queries
const [users, posts] = await Promise.all([
pool.query('SELECT * FROM users LIMIT 10'),
pool.query('SELECT * FROM posts LIMIT 10')
]);
return Response.json({
users: users.rows,
posts: posts.rows
});
} finally {
ctx.waitUntil(pool.end());
}
}
};PostgreSQL with postgres.js:
import postgres from "postgres";
export default {
async fetch(request: Request, env: { HYPERDRIVE: Hyperdrive }, ctx: ExecutionContext) {
const sql = postgres(env.HYPERDRIVE.connectionString, {
max: 5, // Max 5 connections (Workers limit: 6)
fetch_types: false, // Disable if not using array types (reduces latency)
prepare: true // CRITICAL: Enable prepared statements for caching
});
try {
const users = await sql`SELECT * FROM users LIMIT 10`;
return Response.json({ users });
} finally {
ctx.waitUntil(sql.end({ timeout: 5 }));
}
}
};MySQL with mysql2:
import { createConnection } from "mysql2/promise";
export default {
async fetch(request: Request, env: { HYPERDRIVE: Hyperdrive }, ctx: ExecutionContext) {
const connection = await createConnection({
host: env.HYPERDRIVE.host,
user: env.HYPERDRIVE.user,
password: env.HYPERDRIVE.password,
database: env.HYPERDRIVE.database,
port: env.HYPERDRIVE.port,
disableEval: true // REQUIRED: eval() not supported in Workers
});
try {
const [rows] = await connection.query('SELECT * FROM users LIMIT 10');
return Response.json({ users: rows });
} finally {
ctx.waitUntil(connection.end());
}
}
};---
Connection Patterns
Pattern 1: Single Connection (pg.Client)
When to use: Simple queries, single query per request
import { Client } from "pg";
const client = new Client({ connectionString: env.HYPERDRIVE.connectionString });
await client.connect();
const result = await client.query('SELECT ...');
ctx.waitUntil(client.end());Pros: Simple, straightforward Cons: Can't run parallel queries
---
Pattern 2: Connection Pool (pg.Pool)
When to use: Multiple parallel queries in single request
import { Pool } from "pg";
const pool = new Pool({
connectionString: env.HYPERDRIVE.connectionString,
max: 5 // CRITICAL: Stay within Workers' 6 connection limit
});
const [result1, result2] = await Promise.all([
pool.query('SELECT ...'),
pool.query('SELECT ...')
]);
ctx.waitUntil(pool.end());Pros: Parallel queries, better performance Cons: Must manage max connections
---
Pattern 3: Connection Cleanup
CRITICAL: Always use ctx.waitUntil() to clean up connections AFTER response is sent:
export default {
async fetch(request, env, ctx) {
const client = new Client({ connectionString: env.HYPERDRIVE.connectionString });
await client.connect();
try {
const result = await client.query('SELECT ...');
return Response.json(result.rows); // Response sent here
} finally {
// This runs AFTER response is sent (non-blocking)
ctx.waitUntil(client.end());
}
}
};Why `ctx.waitUntil()`?
- Allows Worker to return response immediately
- Connection cleanup happens in background
- Prevents connection leaks
DON'T do this:
await client.end(); // ❌ Blocks response, adds latency---
ORM Integration
Drizzle ORM (PostgreSQL)
1. Install dependencies:
npm install drizzle-orm postgres dotenv
npm install -D drizzle-kit2. Define schema (`src/db/schema.ts`):
import { pgTable, serial, varchar, timestamp } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: varchar("name", { length: 255 }).notNull(),
email: varchar("email", { length: 255 }).notNull().unique(),
createdAt: timestamp("created_at").defaultNow(),
});3. Use in Worker:
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { users } from "./db/schema";
export default {
async fetch(request, env: { HYPERDRIVE: Hyperdrive }, ctx) {
const sql = postgres(env.HYPERDRIVE.connectionString, { max: 5 });
const db = drizzle(sql);
const allUsers = await db.select().from(users);
ctx.waitUntil(sql.end());
return Response.json({ users: allUsers });
}
};---
Prisma ORM (PostgreSQL)
1. Install dependencies:
npm install prisma @prisma/client
npm install pg @prisma/adapter-pg2. Initialize Prisma:
npx prisma init3. Define schema (`prisma/schema.prisma`):
generator client {
provider = "prisma-client-js"
previewFeatures = ["driverAdapters"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now())
}4. Generate Prisma Client:
npx prisma generate --no-engine5. Use in Worker:
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "@prisma/client";
import { Pool } from "pg";
export default {
async fetch(request, env: { HYPERDRIVE: Hyperdrive }, ctx) {
// Create driver adapter with Hyperdrive connection
const pool = new Pool({ connectionString: env.HYPERDRIVE.connectionString, max: 5 });
const adapter = new PrismaPg(pool);
const prisma = new PrismaClient({ adapter });
const users = await prisma.user.findMany();
ctx.waitUntil(pool.end());
return Response.json({ users });
}
};IMPORTANT: Prisma requires driver adapters (@prisma/adapter-pg) to work with Hyperdrive.
---
Local Development
Option 1: Environment Variable (Recommended)
Set CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING> environment variable:
# If your binding is named "HYPERDRIVE"
export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="postgres://user:password@localhost:5432/local_db"
# Start local dev server
npx wrangler devBenefits:
- No credentials in wrangler.jsonc
- Safe to commit configuration files
- Different devs can use different local databases
---
Option 2: localConnectionString in wrangler.jsonc
{
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "production-hyperdrive-id",
"localConnectionString": "postgres://user:password@localhost:5432/local_db"
}
]
}Caution: Don't commit real credentials to version control!
---
Option 3: Remote Development
Connect to production database during local development:
npx wrangler dev --remoteWarning: This uses your PRODUCTION database. Changes cannot be undone!
---
Query Caching
What Gets Cached
Hyperdrive automatically caches non-mutating queries (read-only):
-- ✅ Cached
SELECT * FROM articles WHERE published = true ORDER BY date DESC LIMIT 50;
SELECT COUNT(*) FROM users;
SELECT * FROM products WHERE category = 'electronics';
-- ❌ NOT Cached
INSERT INTO users (name, email) VALUES ('John', 'john@example.com');
UPDATE posts SET published = true WHERE id = 123;
DELETE FROM sessions WHERE expired = true;
SELECT LASTVAL(); -- PostgreSQL volatile function
SELECT LAST_INSERT_ID(); -- MySQL volatile functionHow It Works
1. Wire Protocol Parsing: Hyperdrive parses database protocol to differentiate mutations 2. Automatic Detection: No configuration needed 3. Edge Caching: Cached at Cloudflare's edge (near users) 4. Cache Invalidation: Writes invalidate relevant cached queries
Caching Optimization
postgres.js - Enable prepared statements:
const sql = postgres(env.HYPERDRIVE.connectionString, {
prepare: true // CRITICAL for caching
});Without `prepare: true`, queries are NOT cacheable!
Cache Status
Check if query was cached:
const response = await fetch('https://your-worker.dev/api/users');
const cacheStatus = response.headers.get('cf-cache-status');
// Values: HIT, MISS, BYPASS, EXPIRED---
TLS/SSL Configuration
SSL Modes
Hyperdrive supports 3 TLS/SSL modes:
1. `require` (default) - TLS required, basic certificate validation 2. `verify-ca` - Verify server certificate signed by expected CA 3. `verify-full` - Verify CA + hostname matches certificate SAN
Server Certificates (verify-ca / verify-full)
1. Upload CA certificate:
npx wrangler cert upload certificate-authority \
--ca-cert root-ca.pem \
--name my-ca-cert2. Create Hyperdrive with CA:
npx wrangler hyperdrive create my-db \
--connection-string="postgres://..." \
--ca-certificate-id <CA_CERT_ID> \
--sslmode verify-fullClient Certificates (mTLS)
For databases requiring client authentication:
1. Upload client certificate + key:
npx wrangler cert upload mtls-certificate \
--cert client-cert.pem \
--key client-key.pem \
--name my-client-cert2. Create Hyperdrive with client cert:
npx wrangler hyperdrive create my-db \
--connection-string="postgres://..." \
--mtls-certificate-id <CERT_PAIR_ID>---
Private Database Access (Cloudflare Tunnel)
Connect Hyperdrive to databases in private networks (VPCs, on-premises):
1. Install cloudflared:
# macOS
brew install cloudflare/cloudflare/cloudflared
# Linux
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd642. Create tunnel:
cloudflared tunnel create my-db-tunnel3. Configure tunnel (`config.yml`):
tunnel: <TUNNEL_ID>
credentials-file: /path/to/credentials.json
ingress:
- hostname: db.example.com
service: tcp://localhost:5432 # Your private database
- service: http_status:4044. Run tunnel:
cloudflared tunnel run my-db-tunnel5. Create Hyperdrive:
npx wrangler hyperdrive create my-private-db \
--connection-string="postgres://user:password@db.example.com:5432/database"---
Critical Rules
Always Do
✅ Include nodejs_compat in compatibility_flags ✅ Use ctx.waitUntil(client.end()) for connection cleanup ✅ Set max: 5 for connection pools (Workers limit: 6) ✅ Enable TLS/SSL on your database (Hyperdrive requires it) ✅ Use prepared statements for caching (postgres.js: prepare: true) ✅ Set disableEval: true for mysql2 driver ✅ Handle errors gracefully with try/catch ✅ Use environment variables for local development connection strings ✅ Test locally with wrangler dev before deploying
Never Do
❌ Skip nodejs_compat flag (causes "No such module" errors) ❌ Use private IP addresses directly (use Cloudflare Tunnel instead) ❌ Use await client.end() (blocks response, use ctx.waitUntil()) ❌ Set connection pool max > 5 (exceeds Workers' 6 connection limit) ❌ Wrap all queries in transactions (limits connection multiplexing) ❌ Use SQL-level PREPARE/EXECUTE/DEALLOCATE (unsupported) ❌ Use advisory locks, LISTEN/NOTIFY (PostgreSQL unsupported features) ❌ Use multi-statement queries in MySQL (unsupported) ❌ Commit database credentials to version control
---
Wrangler Commands Reference
# Create Hyperdrive configuration
wrangler hyperdrive create <name> --connection-string="postgres://..."
# List all Hyperdrive configurations
wrangler hyperdrive list
# Get details of a configuration
wrangler hyperdrive get <hyperdrive-id>
# Update connection string
wrangler hyperdrive update <hyperdrive-id> --connection-string="postgres://..."
# Delete configuration
wrangler hyperdrive delete <hyperdrive-id>
# Upload CA certificate
wrangler cert upload certificate-authority --ca-cert <file>.pem --name <name>
# Upload client certificate pair
wrangler cert upload mtls-certificate --cert <cert>.pem --key <key>.pem --name <name>---
Supported Databases
PostgreSQL (v9.0 - 17.x)
- ✅ AWS RDS / Aurora
- ✅ Google Cloud SQL
- ✅ Azure Database for PostgreSQL
- ✅ Neon
- ✅ Supabase
- ✅ PlanetScale (PostgreSQL)
- ✅ Timescale
- ✅ CockroachDB
- ✅ Materialize
- ✅ Fly.io
- ✅ pgEdge Cloud
- ✅ Prisma Postgres
MySQL (v5.7 - 8.x)
- ✅ AWS RDS / Aurora
- ✅ Google Cloud SQL
- ✅ Azure Database for MySQL
- ✅ PlanetScale (MySQL)
NOT Supported
- ❌ SQL Server
- ❌ MongoDB (NoSQL)
- ❌ Oracle Database
---
Unsupported Features
PostgreSQL
- SQL-level prepared statements (
PREPARE,EXECUTE,DEALLOCATE) - Advisory locks
LISTENandNOTIFY- Per-session state modifications
MySQL
- Non-UTF8 characters in queries
USEstatements- Multi-statement queries
- Protocol-level prepared statements (
COM_STMT_PREPARE) COM_INIT_DBmessages- Auth plugins other than
caching_sha2_passwordormysql_native_password
Workaround: For unsupported features, create a second direct client connection (without Hyperdrive).
---
Performance Best Practices
1. Avoid long-running transactions - Limits connection multiplexing 2. Use prepared statements - Enables query caching (postgres.js: prepare: true) 3. Set max: 5 for pools - Stays within Workers' 6 connection limit 4. Disable fetch_types if not needed - Reduces latency (postgres.js) 5. Use ctx.waitUntil() for cleanup - Non-blocking connection close 6. Cache-friendly queries - Prefer SELECT over complex joins 7. Index frequently queried columns - Improves query performance 8. Monitor with Hyperdrive analytics - Track cache hit ratios and latency
---
Troubleshooting
See references/troubleshooting.md for complete error reference with solutions.
Quick fixes:
| Error | Solution |
|---|---|
| "No such module 'node:*'" | Add nodejs_compat to compatibility_flags |
| "TLS not supported by database" | Enable SSL/TLS on your database |
| "Connection refused" | Check firewall rules, allow public internet or use Tunnel |
| "Failed to acquire connection" | Use ctx.waitUntil() for cleanup, avoid long transactions |
| "Code generation from strings disallowed" | Set disableEval: true in mysql2 config |
| "Bad hostname" | Verify DNS resolves, check for typos |
| "Invalid database credentials" | Check username/password (case-sensitive) |
---
Metrics and Analytics
View Hyperdrive metrics in the dashboard:
1. Go to Hyperdrive Dashboard 2. Select your configuration 3. Click Metrics tab
Available Metrics:
- Query count
- Cache hit ratio (hit vs miss)
- Query latency (p50, p95, p99)
- Connection latency
- Query bytes / result bytes
- Error rate
---
Migration Strategies
From Direct Database Connection
Before (direct connection):
const client = new Client({
host: 'db.example.com',
user: 'myuser',
password: 'mypassword',
database: 'mydb',
port: 5432
});After (with Hyperdrive):
const client = new Client({
connectionString: env.HYPERDRIVE.connectionString
});Benefits:
- ✅ 7 round trips eliminated
- ✅ Query caching enabled
- ✅ Connection pooling automatic
- ✅ Global performance boost
---
From D1 to Hyperdrive
When to migrate:
- Need PostgreSQL/MySQL features (JSON types, full-text search, etc.)
- Existing database with data
- Multi-region read replicas
- Advanced indexing strategies
Keep D1 if:
- Building new Cloudflare-native app
- SQLite features sufficient
- No existing database to migrate
- Want simpler serverless setup
---
Credential Rotation
Option 1: Create new Hyperdrive config
# Create new config with new credentials
wrangler hyperdrive create my-db-v2 --connection-string="postgres://..."
# Update wrangler.jsonc to use new ID
# Deploy gradually (no downtime)
# Delete old config when migration completeOption 2: Update existing config
wrangler hyperdrive update <id> --connection-string="postgres://new-credentials@..."Best practice: Use separate Hyperdrive configs for staging and production.
---
Examples
See templates/ directory for complete working examples:
postgres-basic.ts- Simple query with pg.Clientpostgres-pool.ts- Parallel queries with pg.Poolpostgres-js.ts- Using postgres.js drivermysql2-basic.ts- MySQL with mysql2 driverdrizzle-postgres.ts- Drizzle ORM integrationdrizzle-mysql.ts- Drizzle ORM with MySQLprisma-postgres.ts- Prisma ORM integration
---
References
- Official Documentation
- Get Started Guide
- How Hyperdrive Works
- Query Caching
- Local Development
- TLS/SSL Certificates
- Troubleshooting Guide
- Wrangler Commands
- Supported Databases
---
Last Updated: 2025-10-22 Package Versions: wrangler@4.43.0+, pg@8.13.0+, postgres@3.4.5+, mysql2@3.13.0+ Production Tested: Based on official Cloudflare documentation and community examples
Cloudflare Hyperdrive
Complete knowledge domain for Cloudflare Hyperdrive - Connect Cloudflare Workers to existing PostgreSQL and MySQL databases with global connection pooling, query caching, and reduced latency.
---
Auto-Trigger Keywords
Primary Keywords
- hyperdrive
- cloudflare hyperdrive
- workers hyperdrive
- postgres workers
- postgresql workers
- mysql workers
- hyperdrive bindings
- wrangler hyperdrive
- connection pooling cloudflare
- workers database connection
- database acceleration workers
Secondary Keywords
- node-postgres hyperdrive
- pg hyperdrive
- postgres.js workers
- mysql2 workers
- drizzle hyperdrive
- drizzle orm hyperdrive
- prisma hyperdrive
- prisma orm workers
- query caching cloudflare
- hyperdrive configuration
- workers rds
- workers aurora
- workers neon
- workers supabase
- workers planetscale
- existing database workers
- migrate database to cloudflare
- hybrid architecture workers
Error-Based Keywords
- Failed to acquire a connection from the pool
- TLS not supported by the database
- connection refused hyperdrive
- nodejs_compat missing
- disableEval mysql2
- Code generation from strings disallowed
- Uncaught Error: No such module "node:
- Bad hostname hyperdrive
- Invalid database credentials hyperdrive
- Server connection attempt failed
- TLS handshake failed
Framework Integration Keywords
- hono hyperdrive
- express workers hyperdrive
- workers ai database
- vectorize hyperdrive
- d1 vs hyperdrive
- hyperdrive local development
- wrangler dev hyperdrive
- cloudflare tunnel database
---
What This Skill Does
This skill provides complete Hyperdrive knowledge including:
- ✅ Connection Pooling - Eliminates 7 round trips (TCP handshake + TLS negotiation + authentication)
- ✅ Query Caching - Automatic caching of read queries at the edge
- ✅ Global Acceleration - Makes single-region databases feel globally distributed
- ✅ PostgreSQL Support - All versions 9.0-17.x (RDS, Aurora, Neon, Supabase, etc.)
- ✅ MySQL Support - All versions 5.7-8.x (RDS, Aurora, PlanetScale, etc.)
- ✅ Driver Integration - node-postgres (pg), postgres.js, mysql2
- ✅ ORM Support - Drizzle ORM and Prisma ORM patterns
- ✅ Local Development - Full local dev workflow with wrangler
- ✅ TLS/SSL Configuration - Server certificates, client certificates, mTLS
- ✅ Private Database Access - Connect via Cloudflare Tunnel
---
Known Issues Prevented
| Issue | Description | Prevention |
|---|---|---|
| "nodejs_compat" flag missing | Worker crashes with "No such module" error | Always include nodejs_compat in compatibility_flags |
| TLS not supported error (2012) | Database doesn't have SSL enabled | Ensure database has TLS/SSL enabled before creating Hyperdrive config |
| Connection refused (2011) | Firewall blocking Hyperdrive | Allow public internet connections or use Cloudflare Tunnel |
| Failed to acquire connection | Connection pool exhausted | Use ctx.waitUntil() for cleanup, avoid long-running transactions |
| mysql2 eval() error | mysql2 uses eval() which is blocked | Set disableEval: true in mysql2 connection config |
| Connection limit exceeded | Workers limit: 6 concurrent connections | Set max: 5 for pg.Pool to stay within limits |
| Queries not cached | postgres.js configured with prepare: false | Enable prepared statements with prepare: true |
| Invalid credentials (2013) | Username or password incorrect | Verify credentials are correct (case-sensitive password) |
| Private IP not supported (2009) | Trying to connect to 192.168.x.x or 10.x.x.x | Use Cloudflare Tunnel for private database access |
| Database doesn't exist (2014) | Wrong database name in connection string | Verify database (not table) name exists |
---
Quick Example
import { Client } from "pg";
type Bindings = {
HYPERDRIVE: Hyperdrive;
};
export default {
async fetch(request: Request, env: Bindings, ctx: ExecutionContext) {
// Connect to PostgreSQL via Hyperdrive
const client = new Client({
connectionString: env.HYPERDRIVE.connectionString
});
await client.connect();
try {
// Query your database
const result = await client.query('SELECT * FROM users LIMIT 10');
return Response.json({
users: result.rows,
// Hyperdrive metadata
origin: env.HYPERDRIVE.host,
cacheStatus: request.cf?.cacheStatus
});
} finally {
// Clean up connection after response is sent
ctx.waitUntil(client.end());
}
}
};wrangler.jsonc:
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2024-09-23",
"compatibility_flags": ["nodejs_compat"],
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "<your-hyperdrive-id>"
}
]
}Create Hyperdrive config:
npx wrangler hyperdrive create my-database \
--connection-string="postgres://user:password@host:5432/database"---
Token Efficiency
Without this skill:
- 15-20 searches for connection pooling patterns
- 10+ lookups for driver configuration
- 8-12 troubleshooting attempts for common errors
- ~12,000 tokens of repetitive research
With this skill:
- 1 skill invocation with complete knowledge
- ~5,000 tokens of focused, production-ready patterns
- ~58% token savings
---
Coverage
Database Providers
PostgreSQL: AWS RDS/Aurora, Google Cloud SQL, Azure Database, Neon, Supabase, PlanetScale, Timescale, CockroachDB, Materialize, Fly.io, pgEdge, Prisma Postgres
MySQL: AWS RDS/Aurora, Google Cloud SQL, Azure Database, PlanetScale
Drivers
- PostgreSQL: node-postgres (pg), postgres.js (v3.4.5+)
- MySQL: mysql2 (v3.13.0+), mysql (legacy)
ORMs
- Drizzle ORM: PostgreSQL & MySQL support
- Prisma ORM: PostgreSQL with driver adapters
Patterns
- Single connection per request (pg.Client)
- Connection pooling for parallel queries (pg.Pool, max: 5)
- Query caching optimization
- Local development setup
- TLS/SSL certificate configuration
- Error handling and retry strategies
- Connection cleanup with ctx.waitUntil()
Production Topics
- Connection limits (Workers: 6 concurrent, use max: 5 for pools)
- Query caching behavior (SELECT cached, INSERT/UPDATE not cached)
- Transaction impact on connection multiplexing
- Unsupported features (PREPARE/EXECUTE, advisory locks, LISTEN/NOTIFY)
- Credential rotation strategies
- Metrics and analytics
---
When to Use This Skill
Use this skill when you see keywords like:
- "connect Workers to existing database"
- "migrate PostgreSQL to Cloudflare"
- "Workers with RDS/Aurora/Neon/Supabase"
- "connection pooling for Workers"
- "query caching for database"
- "Hyperdrive configuration error"
- "Failed to acquire connection from pool"
- "TLS not supported by database"
- "Drizzle ORM with Workers"
- "Prisma ORM with Cloudflare"
---
When NOT to Use This Skill
- New Cloudflare-native apps → Use D1 (serverless SQLite) instead
- Key-value storage → Use Workers KV
- Document storage → Use R2 or Durable Objects
- NoSQL databases → Hyperdrive doesn't support MongoDB (use Atlas Data API or Realm)
- SQL Server → Not currently supported by Hyperdrive
---
References
{
"description": "|",
"metadata": {
"license": "MIT"
},
"references": {
"files": [
"references/connection-pooling.md",
"references/drizzle-integration.md",
"references/prisma-integration.md",
"references/query-caching.md",
"references/supported-databases.md",
"references/tls-ssl-setup.md",
"references/troubleshooting.md",
"references/wrangler-commands.md"
]
},
"content": "**Status**: Production Ready ✅\r\n**Last Updated**: 2025-10-22\r\n**Dependencies**: cloudflare-worker-base (recommended for Worker setup)\r\n**Latest Versions**: wrangler@4.43.0+, pg@8.13.0+, postgres@3.4.5+, mysql2@3.13.0+\r\n\r\n---\r\n\r\n\r\n### 1. Create Hyperdrive Configuration\r\n\r\n```bash\r\nnpx wrangler hyperdrive create my-postgres-db \\\r\n --connection-string=\"postgres://user:password@db-host.cloud:5432/database\"\r\n\r\nnpx wrangler hyperdrive create my-mysql-db \\\r\n --connection-string=\"mysql://user:password@db-host.cloud:3306/database\"\r\n\r\n#\r\n```\r\n\r\n**Save the `id` value** - you'll need it in the next step!\r\n\r\n---\r\n\r\n### 2. Configure Bindings in wrangler.jsonc\r\n\r\nAdd to your `wrangler.jsonc`:\r\n\r\n```jsonc\r\n{\r\n \"name\": \"my-worker\",\r\n \"main\": \"src/index.ts\",\r\n \"compatibility_date\": \"2024-09-23\",\r\n \"compatibility_flags\": [\"nodejs_compat\"], // REQUIRED for database drivers\r\n \"hyperdrive\": [\r\n {\r\n \"binding\": \"HYPERDRIVE\", // Available as env.HYPERDRIVE\r\n \"id\": \"a76a99bc-7901-48c9-9c15-c4b11b559606\" // From wrangler hyperdrive create\r\n }\r\n ]\r\n}\r\n```\r\n\r\n**CRITICAL:**\r\n- `nodejs_compat` flag is **REQUIRED** for all database drivers\r\n- `binding` is how you access Hyperdrive in code (`env.HYPERDRIVE`)\r\n- `id` is the Hyperdrive configuration ID (NOT your database ID)\r\n\r\n---\r\n\r\n### 3. Install Database Driver\r\n\r\n```bash\r\nnpm install pg # node-postgres (most common)\r\nnpm install postgres # postgres.js (modern, minimum v3.4.5)\r\n\r\n\r\n### Step 1: Prerequisites\r\n\r\n**You need:**\r\n- Cloudflare account with Workers access\r\n- Existing PostgreSQL (v9.0-17.x) or MySQL (v5.7-8.x) database\r\n- Database accessible via:\r\n - **Public internet** (with TLS/SSL enabled), OR\r\n - **Private network** (via Cloudflare Tunnel)\r\n\r\n**Important**: Hyperdrive **requires TLS/SSL**. Ensure your database has encryption enabled.\r\n\r\n---\r\n\r\n### Step 2: Create Hyperdrive Configuration\r\n\r\n**Option A: Wrangler CLI** (Recommended)\r\n\r\n```bash\r\n\r\nnpx wrangler hyperdrive create my-hyperdrive \\\r\n --connection-string=\"postgres://myuser:mypassword@db.example.com:5432/mydb\"\r\n\r\n\r\nnpx wrangler hyperdrive create my-hyperdrive \\\r\n --connection-string=\"mysql://myuser:mypassword@db.example.com:3306/mydb\"\r\n```\r\n\r\n**Option B: Cloudflare Dashboard**\r\n\r\n1. Go to [Hyperdrive Dashboard](https://dash.cloudflare.com/?to=/:account/workers/hyperdrive)\r\n2. Click **Create Configuration**\r\n3. Enter connection details:\r\n - Name: `my-hyperdrive`\r\n - Protocol: PostgreSQL or MySQL\r\n - Host: `db.example.com`\r\n - Port: `5432` (PostgreSQL) or `3306` (MySQL)\r\n - Database: `mydb`\r\n - Username: `myuser`\r\n - Password: `mypassword`\r\n4. Click **Create**\r\n\r\n**Connection String Formats:**\r\n\r\n```bash\r\npostgres://user:password@host:5432/database\r\n\r\npostgres://user:password@host:5432/database?sslmode=require\r\n\r\nmysql://user:password@host:3306/database\r\n\r\npostgres://user:p%40ssw%24rd@host:5432/database # p@ssw$rd\r\n```\r\n\r\n---\r\n\r\n### Step 3: Configure Worker Bindings\r\n\r\nAdd Hyperdrive binding to `wrangler.jsonc`:\r\n\r\n```jsonc\r\n{\r\n \"name\": \"my-worker\",\r\n \"main\": \"src/index.ts\",\r\n \"compatibility_date\": \"2024-09-23\",\r\n \"compatibility_flags\": [\"nodejs_compat\"],\r\n \"hyperdrive\": [\r\n {\r\n \"binding\": \"HYPERDRIVE\",\r\n \"id\": \"<your-hyperdrive-id-here>\"\r\n }\r\n ]\r\n}\r\n```\r\n\r\n**Multiple Hyperdrive configs:**\r\n```jsonc\r\n{\r\n \"hyperdrive\": [\r\n {\r\n \"binding\": \"POSTGRES_DB\",\r\n \"id\": \"postgres-hyperdrive-id\"\r\n },\r\n {\r\n \"binding\": \"MYSQL_DB\",\r\n \"id\": \"mysql-hyperdrive-id\"\r\n }\r\n ]\r\n}\r\n```\r\n\r\n**Access in Worker:**\r\n```typescript\r\ntype Bindings = {\r\n POSTGRES_DB: Hyperdrive;\r\n MYSQL_DB: Hyperdrive;\r\n};\r\n\r\nexport default {\r\n async fetch(request, env: Bindings, ctx) {\r\n // Access different databases\r\n const pgClient = new Client({ connectionString: env.POSTGRES_DB.connectionString });\r\n const mysqlConn = await createConnection({ host: env.MYSQL_DB.host, ... });\r\n }\r\n};\r\n```\r\n\r\n---\r\n\r\n### Step 4: Install Database Driver\r\n\r\n**PostgreSQL Drivers:**\r\n\r\n```bash\r\nnpm install pg\r\nnpm install @types/pg # TypeScript types\r\n\r\nnpm install postgres@^3.4.5\r\n```\r\n\r\n**MySQL Drivers:**\r\n\r\n```bash\r\n\r\n### Option 1: Environment Variable (Recommended)\r\n\r\nSet `CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>` environment variable:\r\n\r\n```bash\r\nexport CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE=\"postgres://user:password@localhost:5432/local_db\"\r\n\r\n\r\nConnect Hyperdrive to databases in private networks (VPCs, on-premises):\r\n\r\n**1. Install cloudflared:**\r\n```bash\r\nbrew install cloudflare/cloudflare/cloudflared\r\n\r\n\r\n```bash\r\nwrangler hyperdrive create <name> --connection-string=\"postgres://...\"\r\n\r\nwrangler hyperdrive list\r\n\r\nwrangler hyperdrive get <hyperdrive-id>\r\n\r\nwrangler hyperdrive update <hyperdrive-id> --connection-string=\"postgres://...\"\r\n\r\nwrangler hyperdrive delete <hyperdrive-id>\r\n\r\nwrangler cert upload certificate-authority --ca-cert <file>.pem --name <name>\r\n\r\n\r\n**Option 1: Create new Hyperdrive config**\r\n```bash\r\nwrangler hyperdrive create my-db-v2 --connection-string=\"postgres://...\"",
"name": "cloudflare-hyperdrive",
"id": "cloudflare-hyperdrive",
"sections": {
"Complete Setup Process": "npm install mysql2\r\n```\r\n\r\n**Driver Comparison:**\r\n\r\n| Driver | Database | Pros | Cons | Min Version |\r\n|--------|----------|------|------|-------------|\r\n| **pg** | PostgreSQL | Most popular, stable, well-documented | Slightly slower than postgres.js | 8.13.0+ |\r\n| **postgres** | PostgreSQL | Faster, modern API, streaming support | Newer (less community examples) | 3.4.5+ |\r\n| **mysql2** | MySQL | Promises, prepared statements, fast | Requires `disableEval: true` for Workers | 3.13.0+ |\r\n\r\n---\r\n\r\n### Step 5: Use Driver in Worker\r\n\r\n**PostgreSQL with pg (Client):**\r\n\r\n```typescript\r\nimport { Client } from \"pg\";\r\n\r\nexport default {\r\n async fetch(request: Request, env: { HYPERDRIVE: Hyperdrive }, ctx: ExecutionContext) {\r\n // Create client for this request\r\n const client = new Client({\r\n connectionString: env.HYPERDRIVE.connectionString\r\n });\r\n\r\n await client.connect();\r\n\r\n try {\r\n // Run query\r\n const result = await client.query('SELECT $1::text as message', ['Hello from Hyperdrive!']);\r\n return Response.json(result.rows);\r\n } catch (error) {\r\n return new Response(`Database error: ${error.message}`, { status: 500 });\r\n } finally {\r\n // CRITICAL: Clean up connection after response\r\n ctx.waitUntil(client.end());\r\n }\r\n }\r\n};\r\n```\r\n\r\n**PostgreSQL with pg (Pool for parallel queries):**\r\n\r\n```typescript\r\nimport { Pool } from \"pg\";\r\n\r\nexport default {\r\n async fetch(request: Request, env: { HYPERDRIVE: Hyperdrive }, ctx: ExecutionContext) {\r\n // Create pool (max 5 to stay within Workers' 6 connection limit)\r\n const pool = new Pool({\r\n connectionString: env.HYPERDRIVE.connectionString,\r\n max: 5 // CRITICAL: Workers limit is 6 concurrent external connections\r\n });\r\n\r\n try {\r\n // Run parallel queries\r\n const [users, posts] = await Promise.all([\r\n pool.query('SELECT * FROM users LIMIT 10'),\r\n pool.query('SELECT * FROM posts LIMIT 10')\r\n ]);\r\n\r\n return Response.json({\r\n users: users.rows,\r\n posts: posts.rows\r\n });\r\n } finally {\r\n ctx.waitUntil(pool.end());\r\n }\r\n }\r\n};\r\n```\r\n\r\n**PostgreSQL with postgres.js:**\r\n\r\n```typescript\r\nimport postgres from \"postgres\";\r\n\r\nexport default {\r\n async fetch(request: Request, env: { HYPERDRIVE: Hyperdrive }, ctx: ExecutionContext) {\r\n const sql = postgres(env.HYPERDRIVE.connectionString, {\r\n max: 5, // Max 5 connections (Workers limit: 6)\r\n fetch_types: false, // Disable if not using array types (reduces latency)\r\n prepare: true // CRITICAL: Enable prepared statements for caching\r\n });\r\n\r\n try {\r\n const users = await sql`SELECT * FROM users LIMIT 10`;\r\n return Response.json({ users });\r\n } finally {\r\n ctx.waitUntil(sql.end({ timeout: 5 }));\r\n }\r\n }\r\n};\r\n```\r\n\r\n**MySQL with mysql2:**\r\n\r\n```typescript\r\nimport { createConnection } from \"mysql2/promise\";\r\n\r\nexport default {\r\n async fetch(request: Request, env: { HYPERDRIVE: Hyperdrive }, ctx: ExecutionContext) {\r\n const connection = await createConnection({\r\n host: env.HYPERDRIVE.host,\r\n user: env.HYPERDRIVE.user,\r\n password: env.HYPERDRIVE.password,\r\n database: env.HYPERDRIVE.database,\r\n port: env.HYPERDRIVE.port,\r\n disableEval: true // REQUIRED: eval() not supported in Workers\r\n });\r\n\r\n try {\r\n const [rows] = await connection.query('SELECT * FROM users LIMIT 10');\r\n return Response.json({ users: rows });\r\n } finally {\r\n ctx.waitUntil(connection.end());\r\n }\r\n }\r\n};\r\n```\r\n\r\n---",
"Migration Strategies": "### From Direct Database Connection\r\n\r\n**Before (direct connection):**\r\n```typescript\r\nconst client = new Client({\r\n host: 'db.example.com',\r\n user: 'myuser',\r\n password: 'mypassword',\r\n database: 'mydb',\r\n port: 5432\r\n});\r\n```\r\n\r\n**After (with Hyperdrive):**\r\n```typescript\r\nconst client = new Client({\r\n connectionString: env.HYPERDRIVE.connectionString\r\n});\r\n```\r\n\r\n**Benefits:**\r\n- ✅ 7 round trips eliminated\r\n- ✅ Query caching enabled\r\n- ✅ Connection pooling automatic\r\n- ✅ Global performance boost\r\n\r\n---\r\n\r\n### From D1 to Hyperdrive\r\n\r\n**When to migrate:**\r\n- Need PostgreSQL/MySQL features (JSON types, full-text search, etc.)\r\n- Existing database with data\r\n- Multi-region read replicas\r\n- Advanced indexing strategies\r\n\r\n**Keep D1 if:**\r\n- Building new Cloudflare-native app\r\n- SQLite features sufficient\r\n- No existing database to migrate\r\n- Want simpler serverless setup\r\n\r\n---",
"Quick Start (5 Minutes)": "npm install mysql2 # mysql2 (minimum v3.13.0)\r\n```\r\n\r\n---\r\n\r\n### 4. Query Your Database\r\n\r\n**PostgreSQL with node-postgres (pg):**\r\n```typescript\r\nimport { Client } from \"pg\";\r\n\r\ntype Bindings = {\r\n HYPERDRIVE: Hyperdrive;\r\n};\r\n\r\nexport default {\r\n async fetch(request: Request, env: Bindings, ctx: ExecutionContext) {\r\n const client = new Client({\r\n connectionString: env.HYPERDRIVE.connectionString\r\n });\r\n\r\n await client.connect();\r\n\r\n try {\r\n const result = await client.query('SELECT * FROM users LIMIT 10');\r\n return Response.json({ users: result.rows });\r\n } finally {\r\n // Clean up connection AFTER response is sent\r\n ctx.waitUntil(client.end());\r\n }\r\n }\r\n};\r\n```\r\n\r\n**MySQL with mysql2:**\r\n```typescript\r\nimport { createConnection } from \"mysql2/promise\";\r\n\r\nexport default {\r\n async fetch(request: Request, env: Bindings, ctx: ExecutionContext) {\r\n const connection = await createConnection({\r\n host: env.HYPERDRIVE.host,\r\n user: env.HYPERDRIVE.user,\r\n password: env.HYPERDRIVE.password,\r\n database: env.HYPERDRIVE.database,\r\n port: env.HYPERDRIVE.port,\r\n disableEval: true // REQUIRED for Workers (eval() not supported)\r\n });\r\n\r\n try {\r\n const [rows] = await connection.query('SELECT * FROM users LIMIT 10');\r\n return Response.json({ users: rows });\r\n } finally {\r\n ctx.waitUntil(connection.end());\r\n }\r\n }\r\n};\r\n```\r\n\r\n---\r\n\r\n### 5. Deploy\r\n\r\n```bash\r\nnpx wrangler deploy\r\n```\r\n\r\n**That's it!** Your Worker now connects to your existing database via Hyperdrive with:\r\n- ✅ Global connection pooling\r\n- ✅ Automatic query caching\r\n- ✅ Reduced latency (eliminates 7 round trips)\r\n\r\n---",
"Performance Best Practices": "1. **Avoid long-running transactions** - Limits connection multiplexing\r\n2. **Use prepared statements** - Enables query caching (postgres.js: `prepare: true`)\r\n3. **Set max: 5 for pools** - Stays within Workers' 6 connection limit\r\n4. **Disable fetch_types if not needed** - Reduces latency (postgres.js)\r\n5. **Use ctx.waitUntil() for cleanup** - Non-blocking connection close\r\n6. **Cache-friendly queries** - Prefer SELECT over complex joins\r\n7. **Index frequently queried columns** - Improves query performance\r\n8. **Monitor with Hyperdrive analytics** - Track cache hit ratios and latency\r\n\r\n---",
"Private Database Access (Cloudflare Tunnel)": "wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64\r\n```\r\n\r\n**2. Create tunnel:**\r\n```bash\r\ncloudflared tunnel create my-db-tunnel\r\n```\r\n\r\n**3. Configure tunnel (`config.yml`):**\r\n```yaml\r\ntunnel: <TUNNEL_ID>\r\ncredentials-file: /path/to/credentials.json\r\n\r\ningress:\r\n - hostname: db.example.com\r\n service: tcp://localhost:5432 # Your private database\r\n - service: http_status:404\r\n```\r\n\r\n**4. Run tunnel:**\r\n```bash\r\ncloudflared tunnel run my-db-tunnel\r\n```\r\n\r\n**5. Create Hyperdrive:**\r\n```bash\r\nnpx wrangler hyperdrive create my-private-db \\\r\n --connection-string=\"postgres://user:password@db.example.com:5432/database\"\r\n```\r\n\r\n---",
"How Hyperdrive Works": "### The Problem\r\nConnecting to traditional databases from Cloudflare's 300+ global locations presents challenges:\r\n\r\n1. **High Latency** - Multiple round trips for each connection:\r\n - TCP handshake (1 round trip)\r\n - TLS negotiation (3 round trips)\r\n - Database authentication (3 round trips)\r\n - **Total: 7 round trips before you can even send a query**\r\n\r\n2. **Connection Limits** - Traditional databases handle limited concurrent connections, easily exhausted by distributed traffic\r\n\r\n### The Solution\r\nHyperdrive solves these problems by:\r\n\r\n1. **Edge Connection Setup** - Connection handshake happens near your Worker (low latency)\r\n2. **Connection Pooling** - Pool near your database reuses connections (eliminates round trips)\r\n3. **Query Caching** - Popular queries cached at the edge (reduces database load)\r\n\r\n**Result**: Single-region databases feel globally distributed.\r\n\r\n---",
"Supported Databases": "### PostgreSQL (v9.0 - 17.x)\r\n- ✅ AWS RDS / Aurora\r\n- ✅ Google Cloud SQL\r\n- ✅ Azure Database for PostgreSQL\r\n- ✅ Neon\r\n- ✅ Supabase\r\n- ✅ PlanetScale (PostgreSQL)\r\n- ✅ Timescale\r\n- ✅ CockroachDB\r\n- ✅ Materialize\r\n- ✅ Fly.io\r\n- ✅ pgEdge Cloud\r\n- ✅ Prisma Postgres\r\n\r\n### MySQL (v5.7 - 8.x)\r\n- ✅ AWS RDS / Aurora\r\n- ✅ Google Cloud SQL\r\n- ✅ Azure Database for MySQL\r\n- ✅ PlanetScale (MySQL)\r\n\r\n### NOT Supported\r\n- ❌ SQL Server\r\n- ❌ MongoDB (NoSQL)\r\n- ❌ Oracle Database\r\n\r\n---",
"Query Caching": "### What Gets Cached\r\n\r\nHyperdrive automatically caches **non-mutating queries** (read-only):\r\n\r\n```sql\r\n-- ✅ Cached\r\nSELECT * FROM articles WHERE published = true ORDER BY date DESC LIMIT 50;\r\nSELECT COUNT(*) FROM users;\r\nSELECT * FROM products WHERE category = 'electronics';\r\n\r\n-- ❌ NOT Cached\r\nINSERT INTO users (name, email) VALUES ('John', 'john@example.com');\r\nUPDATE posts SET published = true WHERE id = 123;\r\nDELETE FROM sessions WHERE expired = true;\r\nSELECT LASTVAL(); -- PostgreSQL volatile function\r\nSELECT LAST_INSERT_ID(); -- MySQL volatile function\r\n```\r\n\r\n### How It Works\r\n\r\n1. **Wire Protocol Parsing**: Hyperdrive parses database protocol to differentiate mutations\r\n2. **Automatic Detection**: No configuration needed\r\n3. **Edge Caching**: Cached at Cloudflare's edge (near users)\r\n4. **Cache Invalidation**: Writes invalidate relevant cached queries\r\n\r\n### Caching Optimization\r\n\r\n**postgres.js - Enable prepared statements:**\r\n```typescript\r\nconst sql = postgres(env.HYPERDRIVE.connectionString, {\r\n prepare: true // CRITICAL for caching\r\n});\r\n```\r\n\r\n**Without `prepare: true`, queries are NOT cacheable!**\r\n\r\n### Cache Status\r\n\r\nCheck if query was cached:\r\n\r\n```typescript\r\nconst response = await fetch('https://your-worker.dev/api/users');\r\nconst cacheStatus = response.headers.get('cf-cache-status');\r\n// Values: HIT, MISS, BYPASS, EXPIRED\r\n```\r\n\r\n---",
"Credential Rotation": "```\r\n\r\n**Option 2: Update existing config**\r\n```bash\r\nwrangler hyperdrive update <id> --connection-string=\"postgres://new-credentials@...\"\r\n```\r\n\r\n**Best practice**: Use separate Hyperdrive configs for staging and production.\r\n\r\n---",
"Unsupported Features": "### PostgreSQL\r\n- SQL-level prepared statements (`PREPARE`, `EXECUTE`, `DEALLOCATE`)\r\n- Advisory locks\r\n- `LISTEN` and `NOTIFY`\r\n- Per-session state modifications\r\n\r\n### MySQL\r\n- Non-UTF8 characters in queries\r\n- `USE` statements\r\n- Multi-statement queries\r\n- Protocol-level prepared statements (`COM_STMT_PREPARE`)\r\n- `COM_INIT_DB` messages\r\n- Auth plugins other than `caching_sha2_password` or `mysql_native_password`\r\n\r\n**Workaround**: For unsupported features, create a second direct client connection (without Hyperdrive).\r\n\r\n---",
"Connection Patterns": "### Pattern 1: Single Connection (pg.Client)\r\n\r\n**When to use**: Simple queries, single query per request\r\n\r\n```typescript\r\nimport { Client } from \"pg\";\r\n\r\nconst client = new Client({ connectionString: env.HYPERDRIVE.connectionString });\r\nawait client.connect();\r\nconst result = await client.query('SELECT ...');\r\nctx.waitUntil(client.end());\r\n```\r\n\r\n**Pros**: Simple, straightforward\r\n**Cons**: Can't run parallel queries\r\n\r\n---\r\n\r\n### Pattern 2: Connection Pool (pg.Pool)\r\n\r\n**When to use**: Multiple parallel queries in single request\r\n\r\n```typescript\r\nimport { Pool } from \"pg\";\r\n\r\nconst pool = new Pool({\r\n connectionString: env.HYPERDRIVE.connectionString,\r\n max: 5 // CRITICAL: Stay within Workers' 6 connection limit\r\n});\r\n\r\nconst [result1, result2] = await Promise.all([\r\n pool.query('SELECT ...'),\r\n pool.query('SELECT ...')\r\n]);\r\n\r\nctx.waitUntil(pool.end());\r\n```\r\n\r\n**Pros**: Parallel queries, better performance\r\n**Cons**: Must manage max connections\r\n\r\n---\r\n\r\n### Pattern 3: Connection Cleanup\r\n\r\n**CRITICAL**: Always use `ctx.waitUntil()` to clean up connections AFTER response is sent:\r\n\r\n```typescript\r\nexport default {\r\n async fetch(request, env, ctx) {\r\n const client = new Client({ connectionString: env.HYPERDRIVE.connectionString });\r\n await client.connect();\r\n\r\n try {\r\n const result = await client.query('SELECT ...');\r\n return Response.json(result.rows); // Response sent here\r\n } finally {\r\n // This runs AFTER response is sent (non-blocking)\r\n ctx.waitUntil(client.end());\r\n }\r\n }\r\n};\r\n```\r\n\r\n**Why `ctx.waitUntil()`?**\r\n- Allows Worker to return response immediately\r\n- Connection cleanup happens in background\r\n- Prevents connection leaks\r\n\r\n**DON'T do this:**\r\n```typescript\r\nawait client.end(); // ❌ Blocks response, adds latency\r\n```\r\n\r\n---",
"Examples": "See `templates/` directory for complete working examples:\r\n\r\n- `postgres-basic.ts` - Simple query with pg.Client\r\n- `postgres-pool.ts` - Parallel queries with pg.Pool\r\n- `postgres-js.ts` - Using postgres.js driver\r\n- `mysql2-basic.ts` - MySQL with mysql2 driver\r\n- `drizzle-postgres.ts` - Drizzle ORM integration\r\n- `drizzle-mysql.ts` - Drizzle ORM with MySQL\r\n- `prisma-postgres.ts` - Prisma ORM integration\r\n\r\n---",
"Metrics and Analytics": "View Hyperdrive metrics in the dashboard:\r\n\r\n1. Go to [Hyperdrive Dashboard](https://dash.cloudflare.com/?to=/:account/workers/hyperdrive)\r\n2. Select your configuration\r\n3. Click **Metrics** tab\r\n\r\n**Available Metrics:**\r\n- Query count\r\n- Cache hit ratio (hit vs miss)\r\n- Query latency (p50, p95, p99)\r\n- Connection latency\r\n- Query bytes / result bytes\r\n- Error rate\r\n\r\n---",
"Critical Rules": "### Always Do\r\n\r\n✅ Include `nodejs_compat` in `compatibility_flags`\r\n✅ Use `ctx.waitUntil(client.end())` for connection cleanup\r\n✅ Set `max: 5` for connection pools (Workers limit: 6)\r\n✅ Enable TLS/SSL on your database (Hyperdrive requires it)\r\n✅ Use prepared statements for caching (postgres.js: `prepare: true`)\r\n✅ Set `disableEval: true` for mysql2 driver\r\n✅ Handle errors gracefully with try/catch\r\n✅ Use environment variables for local development connection strings\r\n✅ Test locally with `wrangler dev` before deploying\r\n\r\n### Never Do\r\n\r\n❌ Skip `nodejs_compat` flag (causes \"No such module\" errors)\r\n❌ Use private IP addresses directly (use Cloudflare Tunnel instead)\r\n❌ Use `await client.end()` (blocks response, use `ctx.waitUntil()`)\r\n❌ Set connection pool max > 5 (exceeds Workers' 6 connection limit)\r\n❌ Wrap all queries in transactions (limits connection multiplexing)\r\n❌ Use SQL-level PREPARE/EXECUTE/DEALLOCATE (unsupported)\r\n❌ Use advisory locks, LISTEN/NOTIFY (PostgreSQL unsupported features)\r\n❌ Use multi-statement queries in MySQL (unsupported)\r\n❌ Commit database credentials to version control\r\n\r\n---",
"TLS/SSL Configuration": "### SSL Modes\r\n\r\nHyperdrive supports 3 TLS/SSL modes:\r\n\r\n1. **`require`** (default) - TLS required, basic certificate validation\r\n2. **`verify-ca`** - Verify server certificate signed by expected CA\r\n3. **`verify-full`** - Verify CA + hostname matches certificate SAN\r\n\r\n### Server Certificates (verify-ca / verify-full)\r\n\r\n**1. Upload CA certificate:**\r\n```bash\r\nnpx wrangler cert upload certificate-authority \\\r\n --ca-cert root-ca.pem \\\r\n --name my-ca-cert\r\n```\r\n\r\n**2. Create Hyperdrive with CA:**\r\n```bash\r\nnpx wrangler hyperdrive create my-db \\\r\n --connection-string=\"postgres://...\" \\\r\n --ca-certificate-id <CA_CERT_ID> \\\r\n --sslmode verify-full\r\n```\r\n\r\n### Client Certificates (mTLS)\r\n\r\nFor databases requiring client authentication:\r\n\r\n**1. Upload client certificate + key:**\r\n```bash\r\nnpx wrangler cert upload mtls-certificate \\\r\n --cert client-cert.pem \\\r\n --key client-key.pem \\\r\n --name my-client-cert\r\n```\r\n\r\n**2. Create Hyperdrive with client cert:**\r\n```bash\r\nnpx wrangler hyperdrive create my-db \\\r\n --connection-string=\"postgres://...\" \\\r\n --mtls-certificate-id <CERT_PAIR_ID>\r\n```\r\n\r\n---",
"Troubleshooting": "See `references/troubleshooting.md` for complete error reference with solutions.\r\n\r\n**Quick fixes:**\r\n\r\n| Error | Solution |\r\n|-------|----------|\r\n| \"No such module 'node:*'\" | Add `nodejs_compat` to compatibility_flags |\r\n| \"TLS not supported by database\" | Enable SSL/TLS on your database |\r\n| \"Connection refused\" | Check firewall rules, allow public internet or use Tunnel |\r\n| \"Failed to acquire connection\" | Use `ctx.waitUntil()` for cleanup, avoid long transactions |\r\n| \"Code generation from strings disallowed\" | Set `disableEval: true` in mysql2 config |\r\n| \"Bad hostname\" | Verify DNS resolves, check for typos |\r\n| \"Invalid database credentials\" | Check username/password (case-sensitive) |\r\n\r\n---",
"ORM Integration": "### Drizzle ORM (PostgreSQL)\r\n\r\n**1. Install dependencies:**\r\n```bash\r\nnpm install drizzle-orm postgres dotenv\r\nnpm install -D drizzle-kit\r\n```\r\n\r\n**2. Define schema (`src/db/schema.ts`):**\r\n```typescript\r\nimport { pgTable, serial, varchar, timestamp } from \"drizzle-orm/pg-core\";\r\n\r\nexport const users = pgTable(\"users\", {\r\n id: serial(\"id\").primaryKey(),\r\n name: varchar(\"name\", { length: 255 }).notNull(),\r\n email: varchar(\"email\", { length: 255 }).notNull().unique(),\r\n createdAt: timestamp(\"created_at\").defaultNow(),\r\n});\r\n```\r\n\r\n**3. Use in Worker:**\r\n```typescript\r\nimport { drizzle } from \"drizzle-orm/postgres-js\";\r\nimport postgres from \"postgres\";\r\nimport { users } from \"./db/schema\";\r\n\r\nexport default {\r\n async fetch(request, env: { HYPERDRIVE: Hyperdrive }, ctx) {\r\n const sql = postgres(env.HYPERDRIVE.connectionString, { max: 5 });\r\n const db = drizzle(sql);\r\n\r\n const allUsers = await db.select().from(users);\r\n\r\n ctx.waitUntil(sql.end());\r\n return Response.json({ users: allUsers });\r\n }\r\n};\r\n```\r\n\r\n---\r\n\r\n### Prisma ORM (PostgreSQL)\r\n\r\n**1. Install dependencies:**\r\n```bash\r\nnpm install prisma @prisma/client\r\nnpm install pg @prisma/adapter-pg\r\n```\r\n\r\n**2. Initialize Prisma:**\r\n```bash\r\nnpx prisma init\r\n```\r\n\r\n**3. Define schema (`prisma/schema.prisma`):**\r\n```prisma\r\ngenerator client {\r\n provider = \"prisma-client-js\"\r\n previewFeatures = [\"driverAdapters\"]\r\n}\r\n\r\ndatasource db {\r\n provider = \"postgresql\"\r\n url = env(\"DATABASE_URL\")\r\n}\r\n\r\nmodel User {\r\n id Int @id @default(autoincrement())\r\n name String\r\n email String @unique\r\n createdAt DateTime @default(now())\r\n}\r\n```\r\n\r\n**4. Generate Prisma Client:**\r\n```bash\r\nnpx prisma generate --no-engine\r\n```\r\n\r\n**5. Use in Worker:**\r\n```typescript\r\nimport { PrismaPg } from \"@prisma/adapter-pg\";\r\nimport { PrismaClient } from \"@prisma/client\";\r\nimport { Pool } from \"pg\";\r\n\r\nexport default {\r\n async fetch(request, env: { HYPERDRIVE: Hyperdrive }, ctx) {\r\n // Create driver adapter with Hyperdrive connection\r\n const pool = new Pool({ connectionString: env.HYPERDRIVE.connectionString, max: 5 });\r\n const adapter = new PrismaPg(pool);\r\n const prisma = new PrismaClient({ adapter });\r\n\r\n const users = await prisma.user.findMany();\r\n\r\n ctx.waitUntil(pool.end());\r\n return Response.json({ users });\r\n }\r\n};\r\n```\r\n\r\n**IMPORTANT**: Prisma requires driver adapters (`@prisma/adapter-pg`) to work with Hyperdrive.\r\n\r\n---",
"Local Development": "npx wrangler dev\r\n```\r\n\r\n**Benefits:**\r\n- No credentials in wrangler.jsonc\r\n- Safe to commit configuration files\r\n- Different devs can use different local databases\r\n\r\n---\r\n\r\n### Option 2: localConnectionString in wrangler.jsonc\r\n\r\n```jsonc\r\n{\r\n \"hyperdrive\": [\r\n {\r\n \"binding\": \"HYPERDRIVE\",\r\n \"id\": \"production-hyperdrive-id\",\r\n \"localConnectionString\": \"postgres://user:password@localhost:5432/local_db\"\r\n }\r\n ]\r\n}\r\n```\r\n\r\n**Caution**: Don't commit real credentials to version control!\r\n\r\n---\r\n\r\n### Option 3: Remote Development\r\n\r\nConnect to production database during local development:\r\n\r\n```bash\r\nnpx wrangler dev --remote\r\n```\r\n\r\n**Warning**: This uses your PRODUCTION database. Changes cannot be undone!\r\n\r\n---",
"References": "- [Official Documentation](https://developers.cloudflare.com/hyperdrive/)\r\n- [Get Started Guide](https://developers.cloudflare.com/hyperdrive/get-started/)\r\n- [How Hyperdrive Works](https://developers.cloudflare.com/hyperdrive/configuration/how-hyperdrive-works/)\r\n- [Query Caching](https://developers.cloudflare.com/hyperdrive/configuration/query-caching/)\r\n- [Local Development](https://developers.cloudflare.com/hyperdrive/configuration/local-development/)\r\n- [TLS/SSL Certificates](https://developers.cloudflare.com/hyperdrive/configuration/tls-ssl-certificates-for-hyperdrive/)\r\n- [Troubleshooting Guide](https://developers.cloudflare.com/hyperdrive/observability/troubleshooting/)\r\n- [Wrangler Commands](https://developers.cloudflare.com/hyperdrive/reference/wrangler-commands/)\r\n- [Supported Databases](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/)\r\n\r\n---\r\n\r\n**Last Updated**: 2025-10-22\r\n**Package Versions**: wrangler@4.43.0+, pg@8.13.0+, postgres@3.4.5+, mysql2@3.13.0+\r\n**Production Tested**: Based on official Cloudflare documentation and community examples",
"Wrangler Commands Reference": "wrangler cert upload mtls-certificate --cert <cert>.pem --key <key>.pem --name <name>\r\n```\r\n\r\n---"
}
}---
name: cloudflare-hyperdrive
description: |
Complete knowledge domain for Cloudflare Hyperdrive - connecting Cloudflare Workers to existing PostgreSQL and MySQL databases with global connection pooling, query caching, and reduced latency.
Use when: connecting Workers to existing databases, migrating PostgreSQL/MySQL to Cloudflare, setting up connection pooling, configuring Hyperdrive bindings, using node-postgres/postgres.js/mysql2 drivers, integrating Drizzle ORM or Prisma ORM, or encountering "Failed to acquire a connection from the pool", "TLS not supported by the database", "connection refused", "nodejs_compat missing", "Code generation from strings disallowed", or Hyperdrive configuration errors.
Keywords: hyperdrive, cloudflare hyperdrive, workers hyperdrive, postgres workers, mysql workers, connection pooling, query caching, node-postgres, pg, postgres.js, mysql2, drizzle hyperdrive, prisma hyperdrive, workers rds, workers aurora, workers neon, workers supabase, database acceleration, hybrid architecture, cloudflare tunnel database, wrangler hyperdrive, hyperdrive bindings, local development hyperdrive
license: MIT
---
# Cloudflare Hyperdrive
**Status**: Production Ready ✅
**Last Updated**: 2025-10-22
**Dependencies**: cloudflare-worker-base (recommended for Worker setup)
**Latest Versions**: wrangler@4.43.0+, pg@8.13.0+, postgres@3.4.5+, mysql2@3.13.0+
---
## Quick Start (5 Minutes)
### 1. Create Hyperdrive Configuration
```bash
# For PostgreSQL
npx wrangler hyperdrive create my-postgres-db \
--connection-string="postgres://user:password@db-host.cloud:5432/database"
# For MySQL
npx wrangler hyperdrive create my-mysql-db \
--connection-string="mysql://user:password@db-host.cloud:3306/database"
# Output:
# ✅ Successfully created Hyperdrive configuration
#
# [[hyperdrive]]
# binding = "HYPERDRIVE"
# id = "a76a99bc-7901-48c9-9c15-c4b11b559606"
```
**Save the `id` value** - you'll need it in the next step!
---
### 2. Configure Bindings in wrangler.jsonc
Add to your `wrangler.jsonc`:
```jsonc
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2024-09-23",
"compatibility_flags": ["nodejs_compat"], // REQUIRED for database drivers
"hyperdrive": [
{
"binding": "HYPERDRIVE", // Available as env.HYPERDRIVE
"id": "a76a99bc-7901-48c9-9c15-c4b11b559606" // From wrangler hyperdrive create
}
]
}
```
**CRITICAL:**
- `nodejs_compat` flag is **REQUIRED** for all database drivers
- `binding` is how you access Hyperdrive in code (`env.HYPERDRIVE`)
- `id` is the Hyperdrive configuration ID (NOT your database ID)
---
### 3. Install Database Driver
```bash
# For PostgreSQL (choose one)
npm install pg # node-postgres (most common)
npm install postgres # postgres.js (modern, minimum v3.4.5)
# For MySQL
npm install mysql2 # mysql2 (minimum v3.13.0)
```
---
### 4. Query Your Database
**PostgreSQL with node-postgres (pg):**
```typescript
import { Client } from "pg";
type Bindings = {
HYPERDRIVE: Hyperdrive;
};
export default {
async fetch(request: Request, env: Bindings, ctx: ExecutionContext) {
const client = new Client({
connectionString: env.HYPERDRIVE.connectionString
});
await client.connect();
try {
const result = await client.query('SELECT * FROM users LIMIT 10');
return Response.json({ users: result.rows });
} finally {
// Clean up connection AFTER response is sent
ctx.waitUntil(client.end());
}
}
};
```
**MySQL with mysql2:**
```typescript
import { createConnection } from "mysql2/promise";
export default {
async fetch(request: Request, env: Bindings, ctx: ExecutionContext) {
const connection = await createConnection({
host: env.HYPERDRIVE.host,
user: env.HYPERDRIVE.user,
password: env.HYPERDRIVE.password,
database: env.HYPERDRIVE.database,
port: env.HYPERDRIVE.port,
disableEval: true // REQUIRED for Workers (eval() not supported)
});
try {
const [rows] = await connection.query('SELECT * FROM users LIMIT 10');
return Response.json({ users: rows });
} finally {
ctx.waitUntil(connection.end());
}
}
};
```
---
### 5. Deploy
```bash
npx wrangler deploy
```
**That's it!** Your Worker now connects to your existing database via Hyperdrive with:
- ✅ Global connection pooling
- ✅ Automatic query caching
- ✅ Reduced latency (eliminates 7 round trips)
---
## How Hyperdrive Works
### The Problem
Connecting to traditional databases from Cloudflare's 300+ global locations presents challenges:
1. **High Latency** - Multiple round trips for each connection:
- TCP handshake (1 round trip)
- TLS negotiation (3 round trips)
- Database authentication (3 round trips)
- **Total: 7 round trips before you can even send a query**
2. **Connection Limits** - Traditional databases handle limited concurrent connections, easily exhausted by distributed traffic
### The Solution
Hyperdrive solves these problems by:
1. **Edge Connection Setup** - Connection handshake happens near your Worker (low latency)
2. **Connection Pooling** - Pool near your database reuses connections (eliminates round trips)
3. **Query Caching** - Popular queries cached at the edge (reduces database load)
**Result**: Single-region databases feel globally distributed.
---
## Complete Setup Process
### Step 1: Prerequisites
**You need:**
- Cloudflare account with Workers access
- Existing PostgreSQL (v9.0-17.x) or MySQL (v5.7-8.x) database
- Database accessible via:
- **Public internet** (with TLS/SSL enabled), OR
- **Private network** (via Cloudflare Tunnel)
**Important**: Hyperdrive **requires TLS/SSL**. Ensure your database has encryption enabled.
---
### Step 2: Create Hyperdrive Configuration
**Option A: Wrangler CLI** (Recommended)
```bash
# PostgreSQL connection string format:
# postgres://username:password@hostname:port/database_name
npx wrangler hyperdrive create my-hyperdrive \
--connection-string="postgres://myuser:mypassword@db.example.com:5432/mydb"
# MySQL connection string format:
# mysql://username:password@hostname:port/database_name
npx wrangler hyperdrive create my-hyperdrive \
--connection-string="mysql://myuser:mypassword@db.example.com:3306/mydb"
```
**Option B: Cloudflare Dashboard**
1. Go to [Hyperdrive Dashboard](https://dash.cloudflare.com/?to=/:account/workers/hyperdrive)
2. Click **Create Configuration**
3. Enter connection details:
- Name: `my-hyperdrive`
- Protocol: PostgreSQL or MySQL
- Host: `db.example.com`
- Port: `5432` (PostgreSQL) or `3306` (MySQL)
- Database: `mydb`
- Username: `myuser`
- Password: `mypassword`
4. Click **Create**
**Connection String Formats:**
```bash
# PostgreSQL (standard)
postgres://user:password@host:5432/database
# PostgreSQL with SSL mode
postgres://user:password@host:5432/database?sslmode=require
# MySQL
mysql://user:password@host:3306/database
# With special characters in password (URL encode)
postgres://user:p%40ssw%24rd@host:5432/database # p@ssw$rd
```
---
### Step 3: Configure Worker Bindings
Add Hyperdrive binding to `wrangler.jsonc`:
```jsonc
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2024-09-23",
"compatibility_flags": ["nodejs_compat"],
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "<your-hyperdrive-id-here>"
}
]
}
```
**Multiple Hyperdrive configs:**
```jsonc
{
"hyperdrive": [
{
"binding": "POSTGRES_DB",
"id": "postgres-hyperdrive-id"
},
{
"binding": "MYSQL_DB",
"id": "mysql-hyperdrive-id"
}
]
}
```
**Access in Worker:**
```typescript
type Bindings = {
POSTGRES_DB: Hyperdrive;
MYSQL_DB: Hyperdrive;
};
export default {
async fetch(request, env: Bindings, ctx) {
// Access different databases
const pgClient = new Client({ connectionString: env.POSTGRES_DB.connectionString });
const mysqlConn = await createConnection({ host: env.MYSQL_DB.host, ... });
}
};
```
---
### Step 4: Install Database Driver
**PostgreSQL Drivers:**
```bash
# Option 1: node-postgres (pg) - Most popular
npm install pg
npm install @types/pg # TypeScript types
# Option 2: postgres.js - Modern, faster (minimum v3.4.5)
npm install postgres@^3.4.5
```
**MySQL Drivers:**
```bash
# mysql2 (minimum v3.13.0)
npm install mysql2
```
**Driver Comparison:**
| Driver | Database | Pros | Cons | Min Version |
|--------|----------|------|------|-------------|
| **pg** | PostgreSQL | Most popular, stable, well-documented | Slightly slower than postgres.js | 8.13.0+ |
| **postgres** | PostgreSQL | Faster, modern API, streaming support | Newer (less community examples) | 3.4.5+ |
| **mysql2** | MySQL | Promises, prepared statements, fast | Requires `disableEval: true` for Workers | 3.13.0+ |
---
### Step 5: Use Driver in Worker
**PostgreSQL with pg (Client):**
```typescript
import { Client } from "pg";
export default {
async fetch(request: Request, env: { HYPERDRIVE: Hyperdrive }, ctx: ExecutionContext) {
// Create client for this request
const client = new Client({
connectionString: env.HYPERDRIVE.connectionString
});
await client.connect();
try {
// Run query
const result = await client.query('SELECT $1::text as message', ['Hello from Hyperdrive!']);
return Response.json(result.rows);
} catch (error) {
return new Response(`Database error: ${error.message}`, { status: 500 });
} finally {
// CRITICAL: Clean up connection after response
ctx.waitUntil(client.end());
}
}
};
```
**PostgreSQL with pg (Pool for parallel queries):**
```typescript
import { Pool } from "pg";
export default {
async fetch(request: Request, env: { HYPERDRIVE: Hyperdrive }, ctx: ExecutionContext) {
// Create pool (max 5 to stay within Workers' 6 connection limit)
const pool = new Pool({
connectionString: env.HYPERDRIVE.connectionString,
max: 5 // CRITICAL: Workers limit is 6 concurrent external connections
});
try {
// Run parallel queries
const [users, posts] = await Promise.all([
pool.query('SELECT * FROM users LIMIT 10'),
pool.query('SELECT * FROM posts LIMIT 10')
]);
return Response.json({
users: users.rows,
posts: posts.rows
});
} finally {
ctx.waitUntil(pool.end());
}
}
};
```
**PostgreSQL with postgres.js:**
```typescript
import postgres from "postgres";
export default {
async fetch(request: Request, env: { HYPERDRIVE: Hyperdrive }, ctx: ExecutionContext) {
const sql = postgres(env.HYPERDRIVE.connectionString, {
max: 5, // Max 5 connections (Workers limit: 6)
fetch_types: false, // Disable if not using array types (reduces latency)
prepare: true // CRITICAL: Enable prepared statements for caching
});
try {
const users = await sql`SELECT * FROM users LIMIT 10`;
return Response.json({ users });
} finally {
ctx.waitUntil(sql.end({ timeout: 5 }));
}
}
};
```
**MySQL with mysql2:**
```typescript
import { createConnection } from "mysql2/promise";
export default {
async fetch(request: Request, env: { HYPERDRIVE: Hyperdrive }, ctx: ExecutionContext) {
const connection = await createConnection({
host: env.HYPERDRIVE.host,
user: env.HYPERDRIVE.user,
password: env.HYPERDRIVE.password,
database: env.HYPERDRIVE.database,
port: env.HYPERDRIVE.port,
disableEval: true // REQUIRED: eval() not supported in Workers
});
try {
const [rows] = await connection.query('SELECT * FROM users LIMIT 10');
return Response.json({ users: rows });
} finally {
ctx.waitUntil(connection.end());
}
}
};
```
---
## Connection Patterns
### Pattern 1: Single Connection (pg.Client)
**When to use**: Simple queries, single query per request
```typescript
import { Client } from "pg";
const client = new Client({ connectionString: env.HYPERDRIVE.connectionString });
await client.connect();
const result = await client.query('SELECT ...');
ctx.waitUntil(client.end());
```
**Pros**: Simple, straightforward
**Cons**: Can't run parallel queries
---
### Pattern 2: Connection Pool (pg.Pool)
**When to use**: Multiple parallel queries in single request
```typescript
import { Pool } from "pg";
const pool = new Pool({
connectionString: env.HYPERDRIVE.connectionString,
max: 5 // CRITICAL: Stay within Workers' 6 connection limit
});
const [result1, result2] = await Promise.all([
pool.query('SELECT ...'),
pool.query('SELECT ...')
]);
ctx.waitUntil(pool.end());
```
**Pros**: Parallel queries, better performance
**Cons**: Must manage max connections
---
### Pattern 3: Connection Cleanup
**CRITICAL**: Always use `ctx.waitUntil()` to clean up connections AFTER response is sent:
```typescript
export default {
async fetch(request, env, ctx) {
const client = new Client({ connectionString: env.HYPERDRIVE.connectionString });
await client.connect();
try {
const result = await client.query('SELECT ...');
return Response.json(result.rows); // Response sent here
} finally {
// This runs AFTER response is sent (non-blocking)
ctx.waitUntil(client.end());
}
}
};
```
**Why `ctx.waitUntil()`?**
- Allows Worker to return response immediately
- Connection cleanup happens in background
- Prevents connection leaks
**DON'T do this:**
```typescript
await client.end(); // ❌ Blocks response, adds latency
```
---
## ORM Integration
### Drizzle ORM (PostgreSQL)
**1. Install dependencies:**
```bash
npm install drizzle-orm postgres dotenv
npm install -D drizzle-kit
```
**2. Define schema (`src/db/schema.ts`):**
```typescript
import { pgTable, serial, varchar, timestamp } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: varchar("name", { length: 255 }).notNull(),
email: varchar("email", { length: 255 }).notNull().unique(),
createdAt: timestamp("created_at").defaultNow(),
});
```
**3. Use in Worker:**
```typescript
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { users } from "./db/schema";
export default {
async fetch(request, env: { HYPERDRIVE: Hyperdrive }, ctx) {
const sql = postgres(env.HYPERDRIVE.connectionString, { max: 5 });
const db = drizzle(sql);
const allUsers = await db.select().from(users);
ctx.waitUntil(sql.end());
return Response.json({ users: allUsers });
}
};
```
---
### Prisma ORM (PostgreSQL)
**1. Install dependencies:**
```bash
npm install prisma @prisma/client
npm install pg @prisma/adapter-pg
```
**2. Initialize Prisma:**
```bash
npx prisma init
```
**3. Define schema (`prisma/schema.prisma`):**
```prisma
generator client {
provider = "prisma-client-js"
previewFeatures = ["driverAdapters"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now())
}
```
**4. Generate Prisma Client:**
```bash
npx prisma generate --no-engine
```
**5. Use in Worker:**
```typescript
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "@prisma/client";
import { Pool } from "pg";
export default {
async fetch(request, env: { HYPERDRIVE: Hyperdrive }, ctx) {
// Create driver adapter with Hyperdrive connection
const pool = new Pool({ connectionString: env.HYPERDRIVE.connectionString, max: 5 });
const adapter = new PrismaPg(pool);
const prisma = new PrismaClient({ adapter });
const users = await prisma.user.findMany();
ctx.waitUntil(pool.end());
return Response.json({ users });
}
};
```
**IMPORTANT**: Prisma requires driver adapters (`@prisma/adapter-pg`) to work with Hyperdrive.
---
## Local Development
### Option 1: Environment Variable (Recommended)
Set `CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>` environment variable:
```bash
# If your binding is named "HYPERDRIVE"
export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="postgres://user:password@localhost:5432/local_db"
# Start local dev server
npx wrangler dev
```
**Benefits:**
- No credentials in wrangler.jsonc
- Safe to commit configuration files
- Different devs can use different local databases
---
### Option 2: localConnectionString in wrangler.jsonc
```jsonc
{
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "production-hyperdrive-id",
"localConnectionString": "postgres://user:password@localhost:5432/local_db"
}
]
}
```
**Caution**: Don't commit real credentials to version control!
---
### Option 3: Remote Development
Connect to production database during local development:
```bash
npx wrangler dev --remote
```
**Warning**: This uses your PRODUCTION database. Changes cannot be undone!
---
## Query Caching
### What Gets Cached
Hyperdrive automatically caches **non-mutating queries** (read-only):
```sql
-- ✅ Cached
SELECT * FROM articles WHERE published = true ORDER BY date DESC LIMIT 50;
SELECT COUNT(*) FROM users;
SELECT * FROM products WHERE category = 'electronics';
-- ❌ NOT Cached
INSERT INTO users (name, email) VALUES ('John', 'john@example.com');
UPDATE posts SET published = true WHERE id = 123;
DELETE FROM sessions WHERE expired = true;
SELECT LASTVAL(); -- PostgreSQL volatile function
SELECT LAST_INSERT_ID(); -- MySQL volatile function
```
### How It Works
1. **Wire Protocol Parsing**: Hyperdrive parses database protocol to differentiate mutations
2. **Automatic Detection**: No configuration needed
3. **Edge Caching**: Cached at Cloudflare's edge (near users)
4. **Cache Invalidation**: Writes invalidate relevant cached queries
### Caching Optimization
**postgres.js - Enable prepared statements:**
```typescript
const sql = postgres(env.HYPERDRIVE.connectionString, {
prepare: true // CRITICAL for caching
});
```
**Without `prepare: true`, queries are NOT cacheable!**
### Cache Status
Check if query was cached:
```typescript
const response = await fetch('https://your-worker.dev/api/users');
const cacheStatus = response.headers.get('cf-cache-status');
// Values: HIT, MISS, BYPASS, EXPIRED
```
---
## TLS/SSL Configuration
### SSL Modes
Hyperdrive supports 3 TLS/SSL modes:
1. **`require`** (default) - TLS required, basic certificate validation
2. **`verify-ca`** - Verify server certificate signed by expected CA
3. **`verify-full`** - Verify CA + hostname matches certificate SAN
### Server Certificates (verify-ca / verify-full)
**1. Upload CA certificate:**
```bash
npx wrangler cert upload certificate-authority \
--ca-cert root-ca.pem \
--name my-ca-cert
```
**2. Create Hyperdrive with CA:**
```bash
npx wrangler hyperdrive create my-db \
--connection-string="postgres://..." \
--ca-certificate-id <CA_CERT_ID> \
--sslmode verify-full
```
### Client Certificates (mTLS)
For databases requiring client authentication:
**1. Upload client certificate + key:**
```bash
npx wrangler cert upload mtls-certificate \
--cert client-cert.pem \
--key client-key.pem \
--name my-client-cert
```
**2. Create Hyperdrive with client cert:**
```bash
npx wrangler hyperdrive create my-db \
--connection-string="postgres://..." \
--mtls-certificate-id <CERT_PAIR_ID>
```
---
## Private Database Access (Cloudflare Tunnel)
Connect Hyperdrive to databases in private networks (VPCs, on-premises):
**1. Install cloudflared:**
```bash
# macOS
brew install cloudflare/cloudflare/cloudflared
# Linux
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
```
**2. Create tunnel:**
```bash
cloudflared tunnel create my-db-tunnel
```
**3. Configure tunnel (`config.yml`):**
```yaml
tunnel: <TUNNEL_ID>
credentials-file: /path/to/credentials.json
ingress:
- hostname: db.example.com
service: tcp://localhost:5432 # Your private database
- service: http_status:404
```
**4. Run tunnel:**
```bash
cloudflared tunnel run my-db-tunnel
```
**5. Create Hyperdrive:**
```bash
npx wrangler hyperdrive create my-private-db \
--connection-string="postgres://user:password@db.example.com:5432/database"
```
---
## Critical Rules
### Always Do
✅ Include `nodejs_compat` in `compatibility_flags`
✅ Use `ctx.waitUntil(client.end())` for connection cleanup
✅ Set `max: 5` for connection pools (Workers limit: 6)
✅ Enable TLS/SSL on your database (Hyperdrive requires it)
✅ Use prepared statements for caching (postgres.js: `prepare: true`)
✅ Set `disableEval: true` for mysql2 driver
✅ Handle errors gracefully with try/catch
✅ Use environment variables for local development connection strings
✅ Test locally with `wrangler dev` before deploying
### Never Do
❌ Skip `nodejs_compat` flag (causes "No such module" errors)
❌ Use private IP addresses directly (use Cloudflare Tunnel instead)
❌ Use `await client.end()` (blocks response, use `ctx.waitUntil()`)
❌ Set connection pool max > 5 (exceeds Workers' 6 connection limit)
❌ Wrap all queries in transactions (limits connection multiplexing)
❌ Use SQL-level PREPARE/EXECUTE/DEALLOCATE (unsupported)
❌ Use advisory locks, LISTEN/NOTIFY (PostgreSQL unsupported features)
❌ Use multi-statement queries in MySQL (unsupported)
❌ Commit database credentials to version control
---
## Wrangler Commands Reference
```bash
# Create Hyperdrive configuration
wrangler hyperdrive create <name> --connection-string="postgres://..."
# List all Hyperdrive configurations
wrangler hyperdrive list
# Get details of a configuration
wrangler hyperdrive get <hyperdrive-id>
# Update connection string
wrangler hyperdrive update <hyperdrive-id> --connection-string="postgres://..."
# Delete configuration
wrangler hyperdrive delete <hyperdrive-id>
# Upload CA certificate
wrangler cert upload certificate-authority --ca-cert <file>.pem --name <name>
# Upload client certificate pair
wrangler cert upload mtls-certificate --cert <cert>.pem --key <key>.pem --name <name>
```
---
## Supported Databases
### PostgreSQL (v9.0 - 17.x)
- ✅ AWS RDS / Aurora
- ✅ Google Cloud SQL
- ✅ Azure Database for PostgreSQL
- ✅ Neon
- ✅ Supabase
- ✅ PlanetScale (PostgreSQL)
- ✅ Timescale
- ✅ CockroachDB
- ✅ Materialize
- ✅ Fly.io
- ✅ pgEdge Cloud
- ✅ Prisma Postgres
### MySQL (v5.7 - 8.x)
- ✅ AWS RDS / Aurora
- ✅ Google Cloud SQL
- ✅ Azure Database for MySQL
- ✅ PlanetScale (MySQL)
### NOT Supported
- ❌ SQL Server
- ❌ MongoDB (NoSQL)
- ❌ Oracle Database
---
## Unsupported Features
### PostgreSQL
- SQL-level prepared statements (`PREPARE`, `EXECUTE`, `DEALLOCATE`)
- Advisory locks
- `LISTEN` and `NOTIFY`
- Per-session state modifications
### MySQL
- Non-UTF8 characters in queries
- `USE` statements
- Multi-statement queries
- Protocol-level prepared statements (`COM_STMT_PREPARE`)
- `COM_INIT_DB` messages
- Auth plugins other than `caching_sha2_password` or `mysql_native_password`
**Workaround**: For unsupported features, create a second direct client connection (without Hyperdrive).
---
## Performance Best Practices
1. **Avoid long-running transactions** - Limits connection multiplexing
2. **Use prepared statements** - Enables query caching (postgres.js: `prepare: true`)
3. **Set max: 5 for pools** - Stays within Workers' 6 connection limit
4. **Disable fetch_types if not needed** - Reduces latency (postgres.js)
5. **Use ctx.waitUntil() for cleanup** - Non-blocking connection close
6. **Cache-friendly queries** - Prefer SELECT over complex joins
7. **Index frequently queried columns** - Improves query performance
8. **Monitor with Hyperdrive analytics** - Track cache hit ratios and latency
---
## Troubleshooting
See `references/troubleshooting.md` for complete error reference with solutions.
**Quick fixes:**
| Error | Solution |
|-------|----------|
| "No such module 'node:*'" | Add `nodejs_compat` to compatibility_flags |
| "TLS not supported by database" | Enable SSL/TLS on your database |
| "Connection refused" | Check firewall rules, allow public internet or use Tunnel |
| "Failed to acquire connection" | Use `ctx.waitUntil()` for cleanup, avoid long transactions |
| "Code generation from strings disallowed" | Set `disableEval: true` in mysql2 config |
| "Bad hostname" | Verify DNS resolves, check for typos |
| "Invalid database credentials" | Check username/password (case-sensitive) |
---
## Metrics and Analytics
View Hyperdrive metrics in the dashboard:
1. Go to [Hyperdrive Dashboard](https://dash.cloudflare.com/?to=/:account/workers/hyperdrive)
2. Select your configuration
3. Click **Metrics** tab
**Available Metrics:**
- Query count
- Cache hit ratio (hit vs miss)
- Query latency (p50, p95, p99)
- Connection latency
- Query bytes / result bytes
- Error rate
---
## Migration Strategies
### From Direct Database Connection
**Before (direct connection):**
```typescript
const client = new Client({
host: 'db.example.com',
user: 'myuser',
password: 'mypassword',
database: 'mydb',
port: 5432
});
```
**After (with Hyperdrive):**
```typescript
const client = new Client({
connectionString: env.HYPERDRIVE.connectionString
});
```
**Benefits:**
- ✅ 7 round trips eliminated
- ✅ Query caching enabled
- ✅ Connection pooling automatic
- ✅ Global performance boost
---
### From D1 to Hyperdrive
**When to migrate:**
- Need PostgreSQL/MySQL features (JSON types, full-text search, etc.)
- Existing database with data
- Multi-region read replicas
- Advanced indexing strategies
**Keep D1 if:**
- Building new Cloudflare-native app
- SQLite features sufficient
- No existing database to migrate
- Want simpler serverless setup
---
## Credential Rotation
**Option 1: Create new Hyperdrive config**
```bash
# Create new config with new credentials
wrangler hyperdrive create my-db-v2 --connection-string="postgres://..."
# Update wrangler.jsonc to use new ID
# Deploy gradually (no downtime)
# Delete old config when migration complete
```
**Option 2: Update existing config**
```bash
wrangler hyperdrive update <id> --connection-string="postgres://new-credentials@..."
```
**Best practice**: Use separate Hyperdrive configs for staging and production.
---
## Examples
See `templates/` directory for complete working examples:
- `postgres-basic.ts` - Simple query with pg.Client
- `postgres-pool.ts` - Parallel queries with pg.Pool
- `postgres-js.ts` - Using postgres.js driver
- `mysql2-basic.ts` - MySQL with mysql2 driver
- `drizzle-postgres.ts` - Drizzle ORM integration
- `drizzle-mysql.ts` - Drizzle ORM with MySQL
- `prisma-postgres.ts` - Prisma ORM integration
---
## References
- [Official Documentation](https://developers.cloudflare.com/hyperdrive/)
- [Get Started Guide](https://developers.cloudflare.com/hyperdrive/get-started/)
- [How Hyperdrive Works](https://developers.cloudflare.com/hyperdrive/configuration/how-hyperdrive-works/)
- [Query Caching](https://developers.cloudflare.com/hyperdrive/configuration/query-caching/)
- [Local Development](https://developers.cloudflare.com/hyperdrive/configuration/local-development/)
- [TLS/SSL Certificates](https://developers.cloudflare.com/hyperdrive/configuration/tls-ssl-certificates-for-hyperdrive/)
- [Troubleshooting Guide](https://developers.cloudflare.com/hyperdrive/observability/troubleshooting/)
- [Wrangler Commands](https://developers.cloudflare.com/hyperdrive/reference/wrangler-commands/)
- [Supported Databases](https://developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features/)
---
**Last Updated**: 2025-10-22
**Package Versions**: wrangler@4.43.0+, pg@8.13.0+, postgres@3.4.5+, mysql2@3.13.0+
**Production Tested**: Based on official Cloudflare documentation and community examples