cloudflare-hyperdrive — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cloudflare-hyperdrive (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Status: Production Ready ✅ Last Updated: 2025-10-22 Dependencies: cloudflare-worker-base (recommended for Worker setup) Latest Versions: [email protected]+, [email protected]+, [email protected]+, [email protected]+
# For PostgreSQL
npx wrangler hyperdrive create my-postgres-db \
--connection-string="postgres://user:[email protected]:5432/database"
# For MySQL
npx wrangler hyperdrive create my-mysql-db \
--connection-string="mysql://user:[email protected]: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!
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_compat flag is REQUIRED for all database driversbinding is how you access Hyperdrive in code (env.HYPERDRIVE)id is the Hyperdrive configuration ID (NOT your database ID)# 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)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());
}
}
};npx wrangler deployThat's it! Your Worker now connects to your existing database via Hyperdrive with:
Connecting to traditional databases from Cloudflare's 300+ global locations presents challenges:
Hyperdrive solves these problems by:
Result: Single-region databases feel globally distributed.
You need:
Important: Hyperdrive requires TLS/SSL. Ensure your database has encryption enabled.
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:[email protected]:5432/mydb"
# MySQL connection string format:
# mysql://username:password@hostname:port/database_name
npx wrangler hyperdrive create my-hyperdrive \
--connection-string="mysql://myuser:[email protected]:3306/mydb"Option B: Cloudflare Dashboard
my-hyperdrivedb.example.com5432 (PostgreSQL) or 3306 (MySQL)mydbmyusermypasswordConnection 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$rdAdd 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, ... });
}
};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+ |
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());
}
}
};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
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
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()`?
DON'T do this:
await client.end(); // ❌ Blocks response, adds latency1. 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 });
}
};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.
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:
{
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "production-hyperdrive-id",
"localConnectionString": "postgres://user:password@localhost:5432/local_db"
}
]
}Caution: Don't commit real credentials to version control!
Connect to production database during local development:
npx wrangler dev --remoteWarning: This uses your PRODUCTION database. Changes cannot be undone!
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', '[email protected]');
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 functionpostgres.js - Enable prepared statements:
const sql = postgres(env.HYPERDRIVE.connectionString, {
prepare: true // CRITICAL for caching
});Without `prepare: true`, queries are NOT cacheable!
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, EXPIREDHyperdrive supports 3 TLS/SSL modes:
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-fullFor 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>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:[email protected]:5432/database"✅ 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
❌ 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
# 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>PREPARE, EXECUTE, DEALLOCATE)LISTEN and NOTIFYUSE statementsCOM_STMT_PREPARE)COM_INIT_DB messagescaching_sha2_password or mysql_native_passwordWorkaround: For unsupported features, create a second direct client connection (without Hyperdrive).
prepare: true)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) |
View Hyperdrive metrics in the dashboard:
Available Metrics:
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:
When to migrate:
Keep D1 if:
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.
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 integrationLast Updated: 2025-10-22 Package Versions: [email protected]+, [email protected]+, [email protected]+, [email protected]+ Production Tested: Based on official Cloudflare documentation and community examples
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.