Get the FREE Ultimate OpenClaw Setup Guide →

cloudflare-bindings

Scanned
npx machina-cli add skill smicolon/ai-kit/cloudflare-bindings --openclaw
Files (1)
SKILL.md
7.8 KB

Cloudflare 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

  1. Step 1: Define a central Env type listing Bindings (D1, KV, R2, DO, ENV vars) and Variables
  2. Step 2: In your route handlers, access bindings via c.env (e.g., c.env.DB, c.env.KV)
  3. 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

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers