mcpField
H
hono-cloudflare

hono-cloudflare

github.com/bobmatnyc云与 DevOps78626 次安装15 周前更新
代码仓库

关于 hono-cloudflare

Hono on Cloudflare Workers 是用于在 Cloudflare 生态中构建 Hono 应用的技能,涵盖类型化绑定、存储与计算服务、静态资源、Pages 和事件处理器。

主要功能

  1. 01原生支持 Workers
  2. 02类型化环境绑定
  3. 03KV、D1、R2 和 Durable Objects
  4. 04静态资源与 Pages
  5. 05队列和定时处理器

适用场景

  1. 01构建边缘 API
  2. 02使用 D1 提供无服务器 SQLite
  3. 03借助 Durable Objects 构建实时应用
  4. 04通过 R2 存储文件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill hono-cloudflare

安装后在 Claude Code 中输入/hono-cloudflare确认已加载。

附带文件metadata.json

详细介绍

Cloudflare Workers 上的 Hono

概述

Hono 最初是为 Cloudflare Workers 构建的,并为整个 Cloudflare 生态系统提供一流支持,包括 KV、D1、R2、Durable Objects、Queues 等。

主要功能:

  • 原生支持 Workers
  • 类型安全的绑定访问
  • 集成 KV、D1、R2 和 Durable Objects
  • 静态资源托管
  • 支持 Cloudflare Pages
  • 队列和定时任务处理器

何时使用此技能

在以下情况下使用 Hono on Cloudflare:

  • 构建具备全球分发能力的边缘 API
  • 需要使用 D1 实现无服务器 SQLite
  • 使用 Durable Objects 构建实时应用
  • 使用 R2 存储文件
  • 需要使用 KV 实现快速键值存储
  • 将全栈应用部署到 Pages

快速开始

创建新项目
npm create hono@latest my-app

# Select: cloudflare-workers

cd my-app
npm install
npm run dev
项目结构
my-app/
├── src/
│   └── index.ts         # Main entry point
├── wrangler.toml        # Cloudflare configuration
├── package.json
└── tsconfig.json
基本应用
// src/index.ts
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => c.text('Hello Cloudflare Workers!'))

export default app
部署
# Deploy to Cloudflare
npx wrangler deploy

# Local development
npx wrangler dev

环境绑定

类型化绑定
import { Hono } from 'hono'

// Define your bindings
type Bindings = {
  // Environment variables
  API_KEY: string
  DATABASE_URL: string

  // KV Namespaces
  MY_KV: KVNamespace

  // D1 Databases
  DB: D1Database

  // R2 Buckets
  BUCKET: R2Bucket

  // Durable Objects
  COUNTER: DurableObjectNamespace

  // Queues
  MY_QUEUE: Queue
}

const app = new Hono<{ Bindings: Bindings }>()

app.get('/config', (c) => {
  // Fully typed access
  const apiKey = c.env.API_KEY
  return c.json({ configured: !!apiKey })
})

export default app
wrangler.toml 配置
name = "my-app"
main = "src/index.ts"
compatibility_date = "2024-01-01"

[vars]
API_KEY = "your-api-key"  # pragma: allowlist secret

[[kv_namespaces]]
binding = "MY_KV"
id = "your-kv-id"

[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "your-d1-id"

[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket"

[[queues.producers]]
binding = "MY_QUEUE"
queue = "my-queue"

KV 存储

基本操作
type Bindings = {
  CACHE: KVNamespace
}

const app = new Hono<{ Bindings: Bindings }>()

// Get value
app.get('/cache/:key', async (c) => {
  const key = c.req.param('key')
  const value = await c.env.CACHE.get(key)

  if (!value) {
    return c.json({ error: 'Not found' }, 404)
  }

  return c.json({ key, value })
})

// Get JSON value
app.get('/cache/:key/json', async (c) => {
  const key = c.req.param('key')
  const value = await c.env.CACHE.get(key, 'json')

  return c.json({ key, value })
})

// Set value
app.put('/cache/:key', async (c) => {
  const key = c.req.param('key')
  const body = await c.req.json()

  await c.env.CACHE.put(key, JSON.stringify(body), {
    expirationTtl: 3600  // 1 hour
  })

  return c.json({ success: true })
})

// Delete value
app.delete('/cache/:key', async (c) => {
  const key = c.req.param('key')
  await c.env.CACHE.delete(key)

  return c.json({ success: true })
})

// List keys
app.get('/cache', async (c) => {
  const prefix = c.req.query('prefix') || ''
  const list = await c.env.CACHE.list({ prefix, limit: 100 })

  return c.json({ keys: list.keys })
})
使用元数据的 KV
interface UserMeta {
  createdAt: string
  role: string
}

app.put('/users/:id', async (c) => {
  const id = c.req.param('id')
  const user = await c.req.json()

  await c.env.CACHE.put(`user:${id}`, JSON.stringify(user), {
    metadata: {
      createdAt: new Date().toISOString(),
      role: user.role
    } as UserMeta
  })

  return c.json({ success: true })
})

app.get('/users/:id', async (c) => {
  const id = c.req.param('id')
  const { value, metadata } = await c.env.CACHE.getWithMetadata<UserMeta>(`user:${id}`, 'json')

  if (!value) {
    return c.json({ error: 'Not found' }, 404)
  }

  return c.json({ user: value, metadata })
})

D1 数据库

基本查询
type Bindings = {
  DB: D1Database
}

const app = new Hono<{ Bindings: Bindings }>()

// Select all
app.get('/users', async (c) => {
  const { results } = await c.env.DB
    .prepare('SELECT * FROM users ORDER BY created_at DESC')
    .all()

  return c.json({ users: results })
})

// Select one
app.get('/users/:id', async (c) => {
  const id = c.req.param('id')
  const user = await c.env.DB
    .prepare('SELECT * FROM users WHERE id = ?')
    .bind(id)
    .first()

  if (!user) {
    return c.json({ error: 'Not found' }, 404)
  }

  return c.json({ user })
})

// Insert
app.post('/users', async (c) => {
  const { name, email } = await c.req.json()

  const result = await c.env.DB
    .prepare('INSERT INTO users (name, email) VALUES (?, ?)')
    .bind(name, email)
    .run()

  return c.json({
    success: result.success,
    id: result.meta.last_row_id
  }, 201)
})

// Update
app.put('/users/:id', async (c) => {
  const id = c.req.param('id')
  const { name, email } = await c.req.json()

  const result = await c.env.DB
    .prepare('UPDATE users SET name = ?, email = ? WHERE id = ?')
    .bind(name, email, id)
    .run()

  return c.json({ success: result.success })
})

// Delete
app.delete('/users/:id', async (c) => {
  const id = c.req.param('id')

  const result = await c.env.DB
    .prepare('DELETE FROM users WHERE id = ?')
    .bind(id)
    .run()

  return c.json({ success: result.success })
})
批量操作
app.post('/users/batch', async (c) => {
  const { users } = await c.req.json()

  const statements = users.map((user: { name: string; email: string }) =>
    c.env.DB
      .prepare('INSERT INTO users (name, email) VALUES (?, ?)')
      .bind(user.name, user.email)
  )

  const results = await c.env.DB.batch(statements)

  return c.json({
    success: results.every(r => r.success),
    count: results.length
  })
})

R2 对象存储

type Bindings = {
  BUCKET: R2Bucket
}

const app = new Hono<{ Bindings: Bindings }>()

// Upload file
app.post('/files/:key', async (c) => {
  const key = c.req.param('key')
  const body = await c.req.arrayBuffer()
  const contentType = c.req.header('Content-Type') || 'application/octet-stream'

  await c.env.BUCKET.put(key, body, {
    httpMetadata: { contentType }
  })

  return c.json({ success: true, key })
})

// Download file
app.get('/files/:key', async (c) => {
  const key = c.req.param('key')
  const object = await c.env.BUCKET.get(key)

  if (!object) {
    return c.json({ error: 'Not found' }, 404)
  }

  const headers = new Headers()
  headers.set('Content-Type', object.httpMetadata?.contentType || 'application/octet-stream')
  headers.set('ETag', object.httpEtag)

  return new Response(object.body, { headers })
})

// Delete file
app.delete('/files/:key', async (c) => {
  const key = c.req.param('key')
  await c.env.BUCKET.delete(key)

  return c.json({ success: true })
})

// List files
app.get('/files', async (c) => {
  const prefix = c.req.query('prefix') || ''
  const list = await c.env.BUCKET.list({ prefix, limit: 100 })

  return c.json({
    objects: list.objects.map(obj => ({
      key: obj.key,
      size: obj.size,
      uploaded: obj.uploaded
    }))
  })
})

Durable Objects

定义 Durable Object
// src/counter.ts
export class Counter {
  private state: DurableObjectState
  private value: number = 0

  constructor(state: DurableObjectState) {
    this.state = state
  }

  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url)

    // Load value from storage
    this.value = await this.state.storage.get('value') || 0

    switch (url.pathname) {
      case '/increment':
        this.value++
        await this.state.storage.put('value', this.value)
        return new Response(String(this.value))

      case '/decrement':
        this.value--
        await this.state.storage.put('value', this.value)
        return new Response(String(this.value))

      case '/value':
        return new Response(String(this.value))

      default:
        return new Response('Not found', { status: 404 })
    }
  }
}
在 Hono 中使用
import { Hono } from 'hono'

type Bindings = {
  COUNTER: DurableObjectNamespace
}

const app = new Hono<{ Bindings: Bindings }>()

app.get('/counter/:name/increment', async (c) => {
  const name = c.req.param('name')
  const id = c.env.COUNTER.idFromName(name)
  const stub = c.env.COUNTER.get(id)

  const response = await stub.fetch('http://counter/increment')
  const value = await response.text()

  return c.json({ name, value: parseInt(value) })
})

app.get('/counter/:name', async (c) => {
  const name = c.req.param('name')
  const id = c.env.COUNTER.idFromName(name)
  const stub = c.env.COUNTER.get(id)

  const response = await stub.fetch('http://counter/value')
  const value = await response.text()

  return c.json({ name, value: parseInt(value) })
})

export default app
export { Counter }
Durable Objects 的 wrangler.toml 配置
[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"

[[migrations]]
tag = "v1"
new_classes = ["Counter"]

队列

生产者
type Bindings = {
  MY_QUEUE: Queue
}

const app = new Hono<{ Bindings: Bindings }>()

app.post('/tasks', async (c) => {
  const task = await c.req.json()

  await c.env.MY_QUEUE.send({
    type: 'process',
    data: task
  })

  return c.json({ queued: true })
})

// Batch send
app.post('/tasks/batch', async (c) => {
  const { tasks } = await c.req.json()

  await c.env.MY_QUEUE.sendBatch(
    tasks.map((task: any) => ({
      body: { type: 'process', data: task }
    }))
  )

  return c.json({ queued: tasks.length })
})
消费者
export default {
  fetch: app.fetch,

  async queue(batch: MessageBatch, env: Bindings): Promise<void> {
    for (const message of batch.messages) {
      const { type, data } = message.body as { type: string; data: any }

      try {
        // Process message
        console.log(`Processing ${type}:`, data)
        message.ack()
      } catch (error) {
        message.retry()
      }
    }
  }
}

静态资源

wrangler.toml
name = "my-app"
main = "src/index.ts"
compatibility_date = "2024-01-01"

# Serve static files from public directory
assets = { directory = "public" }
配合路由处理器使用
import { Hono } from 'hono'
import { serveStatic } from 'hono/cloudflare-workers'

const app = new Hono()

// Serve static files
app.use('/static/*', serveStatic({ root: './' }))

// API routes
app.get('/api/hello', (c) => c.json({ hello: 'world' }))

export default app

定时事件(Cron)

import { Hono } from 'hono'

const app = new Hono()

// Regular routes...

export default {
  fetch: app.fetch,

  async scheduled(
    event: ScheduledEvent,
    env: Bindings,
    ctx: ExecutionContext
  ): Promise<void> {
    switch (event.cron) {
      case '0 * * * *':  // Every hour
        await hourlyTask(env)
        break

      case '0 0 * * *':  // Daily at midnight
        await dailyTask(env)
        break
    }
  }
}

async function hourlyTask(env: Bindings) {
  console.log('Running hourly task')
}

async function dailyTask(env: Bindings) {
  console.log('Running daily task')
}
Cron 的 wrangler.toml 配置
[triggers]
crons = ["0 * * * *", "0 0 * * *"]

Cloudflare Pages

pages/functions 目录
my-app/
├── public/              # Static assets
│   ├── index.html
│   └── styles.css
└── functions/
    └── [[path]].ts      # Catch-all function
全匹配处理器
// functions/[[path]].ts
import { Hono } from 'hono'
import { handle } from 'hono/cloudflare-pages'

const app = new Hono().basePath('/api')

app.get('/hello', (c) => c.json({ hello: 'world' }))
app.post('/echo', async (c) => c.json(await c.req.json()))

export const onRequest = handle(app)

使用绑定的中间件

import { createMiddleware } from 'hono/factory'

type Bindings = {
  API_KEY: string
}

// Access env in middleware
const authMiddleware = createMiddleware<{ Bindings: Bindings }>(
  async (c, next) => {
    // Don't access env at module level - access in handler!
    const apiKey = c.req.header('X-API-Key')

    if (apiKey !== c.env.API_KEY) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    await next()
  }
)

app.use('/api/*', authMiddleware)

快速参考

绑定类型
type Bindings = {
  // Variables
  MY_VAR: string

  // KV
  MY_KV: KVNamespace

  // D1
  DB: D1Database

  // R2
  BUCKET: R2Bucket

  // Durable Objects
  MY_DO: DurableObjectNamespace

  // Queues
  MY_QUEUE: Queue

  // Service Bindings
  OTHER_WORKER: Fetcher
}
常用命令
# Local development
npx wrangler dev

# Deploy
npx wrangler deploy

# Create D1 database
npx wrangler d1 create my-database

# Execute D1 SQL
npx wrangler d1 execute my-database --file=schema.sql

# Create KV namespace
npx wrangler kv:namespace create MY_KV

# Create R2 bucket
npx wrangler r2 bucket create my-bucket

# Tail logs
npx wrangler tail

相关技能

  • hono-core - 框架基础知识
  • hono-middleware - 中间件模式
  • hono-testing - 使用模拟绑定进行测试

版本:Hono 4.x,Wrangler 3.x
最后更新:2025 年 1 月
许可证:MIT

评论

支持 Markdown