cloudflare-bindings
Scannednpx machina-cli add skill smicolon/ai-kit/cloudflare-bindings --openclawCloudflare Bindings
Patterns for Cloudflare Workers bindings in Hono.
Type Definitions
Define all bindings in a central type:
// types/bindings.ts
export type Env = {
Bindings: {
// D1 Database
DB: D1Database
// KV Namespace
KV: KVNamespace
// R2 Bucket
BUCKET: R2Bucket
// Durable Object
COUNTER: DurableObjectNamespace
// Environment Variables
ENVIRONMENT: 'development' | 'staging' | 'production'
API_KEY: string
JWT_SECRET: string
}
Variables: {
user: User
requestId: string
}
}
D1 Database
Basic Queries
app.get('/users', async (c) => {
const db = c.env.DB
// Select all
const { results } = await db
.prepare('SELECT * FROM users WHERE deleted_at IS NULL')
.all()
return c.json({ data: results })
})
app.get('/users/:id', async (c) => {
const db = c.env.DB
const id = c.req.param('id')
// Select one
const user = await db
.prepare('SELECT * FROM users WHERE id = ?')
.bind(id)
.first()
if (!user) {
return c.json({ error: 'User not found' }, 404)
}
return c.json({ data: user })
})
Insert and Update
app.post('/users', async (c) => {
const db = c.env.DB
const { email, name } = c.req.valid('json')
const id = crypto.randomUUID()
const result = await db
.prepare('INSERT INTO users (id, email, name) VALUES (?, ?, ?)')
.bind(id, email, name)
.run()
return c.json({ data: { id, email, name } }, 201)
})
app.put('/users/:id', async (c) => {
const db = c.env.DB
const id = c.req.param('id')
const { name } = c.req.valid('json')
await db
.prepare('UPDATE users SET name = ?, updated_at = datetime("now") WHERE id = ?')
.bind(name, id)
.run()
return c.json({ data: { id, name } })
})
Transactions (Batch)
app.post('/transfer', async (c) => {
const db = c.env.DB
const { fromId, toId, amount } = c.req.valid('json')
// D1 batch for transaction-like behavior
const results = await db.batch([
db.prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?')
.bind(amount, fromId),
db.prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?')
.bind(amount, toId),
db.prepare('INSERT INTO transfers (from_id, to_id, amount) VALUES (?, ?, ?)')
.bind(fromId, toId, amount)
])
return c.json({ success: true })
})
Pagination
app.get('/users', async (c) => {
const db = c.env.DB
const { page, limit } = c.req.valid('query')
const offset = (page - 1) * limit
const [users, countResult] = await Promise.all([
db.prepare('SELECT * FROM users LIMIT ? OFFSET ?')
.bind(limit, offset)
.all(),
db.prepare('SELECT COUNT(*) as total FROM users')
.first<{ total: number }>()
])
return c.json({
data: users.results,
meta: {
page,
limit,
total: countResult?.total || 0
}
})
})
KV Namespace
Basic Operations
// Get
app.get('/cache/:key', async (c) => {
const kv = c.env.KV
const key = c.req.param('key')
const value = await kv.get(key)
if (!value) {
return c.json({ error: 'Not found' }, 404)
}
return c.json({ data: value })
})
// Get as JSON
const data = await kv.get('config', 'json')
// Get with metadata
const { value, metadata } = await kv.getWithMetadata('key')
// Put
await kv.put('key', 'value')
// Put with TTL (seconds)
await kv.put('session', token, { expirationTtl: 3600 })
// Put with expiration (Unix timestamp)
await kv.put('temp', data, { expiration: Date.now() / 1000 + 3600 })
// Put with metadata
await kv.put('user:123', JSON.stringify(user), {
metadata: { createdAt: Date.now() }
})
// Delete
await kv.delete('key')
// List keys
const { keys } = await kv.list({ prefix: 'user:' })
Caching Pattern
app.get('/expensive-data', async (c) => {
const kv = c.env.KV
const cacheKey = 'expensive-data'
// Try cache first
const cached = await kv.get(cacheKey, 'json')
if (cached) {
return c.json({ data: cached, cached: true })
}
// Compute expensive data
const data = await computeExpensiveData()
// Cache for 5 minutes
await kv.put(cacheKey, JSON.stringify(data), { expirationTtl: 300 })
return c.json({ data, cached: false })
})
Rate Limiting
const rateLimiter = createMiddleware<Env>(async (c, next) => {
const kv = c.env.KV
const ip = c.req.header('CF-Connecting-IP') || 'unknown'
const key = `ratelimit:${ip}`
const count = parseInt(await kv.get(key) || '0')
if (count >= 100) {
return c.json({ error: 'Rate limit exceeded' }, 429)
}
await kv.put(key, String(count + 1), { expirationTtl: 60 })
await next()
})
R2 Bucket
Upload Files
app.post('/upload', async (c) => {
const bucket = c.env.BUCKET
const body = await c.req.parseBody()
const file = body['file'] as File
if (!file) {
return c.json({ error: 'No file provided' }, 400)
}
const key = `uploads/${crypto.randomUUID()}-${file.name}`
await bucket.put(key, file.stream(), {
httpMetadata: {
contentType: file.type
},
customMetadata: {
originalName: file.name,
uploadedAt: new Date().toISOString()
}
})
return c.json({ key }, 201)
})
Download Files
app.get('/files/:key', async (c) => {
const bucket = c.env.BUCKET
const key = c.req.param('key')
const object = await bucket.get(key)
if (!object) {
return c.json({ error: 'File not found' }, 404)
}
const headers = new Headers()
headers.set('Content-Type', object.httpMetadata?.contentType || 'application/octet-stream')
headers.set('Content-Length', String(object.size))
return new Response(object.body, { headers })
})
List Files
app.get('/files', async (c) => {
const bucket = c.env.BUCKET
const prefix = c.req.query('prefix') || ''
const listed = await bucket.list({
prefix,
limit: 100
})
return c.json({
data: listed.objects.map(obj => ({
key: obj.key,
size: obj.size,
uploaded: obj.uploaded
}))
})
})
Delete Files
app.delete('/files/:key', async (c) => {
const bucket = c.env.BUCKET
const key = c.req.param('key')
await bucket.delete(key)
return c.body(null, 204)
})
Environment Variables
Access in Handlers
app.get('/config', (c) => {
const env = c.env.ENVIRONMENT
return c.json({
environment: env,
features: {
debug: env === 'development'
}
})
})
Secrets in Middleware
const authMiddleware = createMiddleware<Env>(async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '')
if (!token) {
throw new HTTPException(401, { message: 'Missing token' })
}
// Use secret from environment
const payload = await verify(token, c.env.JWT_SECRET)
c.set('user', payload)
await next()
})
wrangler.toml Configuration
name = "my-app"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[vars]
ENVIRONMENT = "production"
[[d1_databases]]
binding = "DB"
database_name = "my-app-db"
database_id = "xxxx-xxxx-xxxx"
[[kv_namespaces]]
binding = "KV"
id = "xxxx-xxxx-xxxx"
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-app-bucket"
# Secrets are set via wrangler secret put
# wrangler secret put JWT_SECRET
Local Development
.dev.vars (for local secrets):
JWT_SECRET=dev-secret-key
API_KEY=dev-api-key
# Run with local D1
wrangler d1 create my-app-db --local
# Run with local KV
wrangler kv:namespace create KV --local
# Start dev server
wrangler dev
Source
git clone https://github.com/smicolon/ai-kit/blob/main/packs/hono/skills/cloudflare-bindings/SKILL.mdView on GitHub Overview
This skill provides ready-to-use patterns for Cloudflare Workers bindings such as D1, KV, R2, Durable Objects and environment variables. It demonstrates common data access patterns including database queries, caching, file storage, and secrets management, all wired through a central Env type. It helps you build scalable, serverless apps on Cloudflare with consistent bindings usage.
How This Skill Works
Define a central Env type that describes Bindings (D1, KV, R2, Durable Objects, env vars) and Variables. Access bindings in route handlers via c.env and implement typical patterns (DB queries with D1, KV caching, R2 file storage, and DO interactions) using the examples provided (basic queries, insert/update, transactions, and KV operations).
When to Use It
- Building an API-backed app with a D1 database in Cloudflare Workers
- Caching frequently read data with KV for performance and cost efficiency
- Storing and serving files using an R2 bucket
- Managing secrets and environment-specific config via environment variables
- Coordinating stateful logic with Durable Objects for per-entity behavior
Quick Start
- Step 1: Define a central Env type listing Bindings (D1, KV, R2, DO, ENV vars) and Variables
- Step 2: In your route handlers, access bindings via c.env (e.g., c.env.DB, c.env.KV)
- Step 3: Implement a pattern (D1 query, insert/update, batch transfers, and KV get/put) using the examples
Best Practices
- Define all bindings in a central Env type (Bindings and Variables) for consistency
- Use D1 prepared statements and batch() for transactional-like operations
- Handle not-found and error cases with clear responses (e.g., 404, 500)
- Leverage KV for caching with appropriate TTL and metadata when possible
- Keep secrets and environment-specific values typed and validated
Example Use Cases
- GET /users: fetch all non-deleted users from D1 and return as JSON
- GET /users/:id: fetch a single user by id from D1 and handle missing users
- POST /users: insert a new user with a generated id using D1
- POST /transfer: run a multi-step batch to transfer balance and record the transfer
- KV cache: get and put values in KV, including TTL and metadata handling