使用 Hono、Cloudflare Workers、R2 对象存储和 D1 数据库构建全栈应用

48 min read

在本文中,我们将逐步介绍如何使用 Cloudflare 的一些最新无服务器技术来构建一个完整的全栈应用程序 - Hono 作为 Web 框架,Workers 用于无服务器计算,R2 用于对象存储,D1 用于 SQL 数据库。这套技术栈允许你在 Cloudflare 的边缘网络上构建和部署高度可扩展的应用程序。

技术概述

以下是我们将使用的关键技术的简要介绍:

  • Hono - 一个为 Cloudflare Workers 设计的小巧、简单、快速的 Web 框架。Hono 提供了类似 Express.js 风格的中间件和路由层。

  • Cloudflare Workers - 在 Cloudflare 的边缘网络上运行的无服务器函数,使你能够在不管理基础设施的情况下构建应用程序。

  • R2 存储 - 在边缘运行的 S3 兼容对象存储,与 Workers 无缝集成。

  • D1 数据库 - 一个在 Cloudflare 的边缘与你的 Worker 一起运行的 SQL 数据库(基于 SQLite)。

通过结合这些技术,你可以构建全球分布式、快速且可自动扩展以处理任何负载的应用程序 - 所有这些都使用熟悉的范例,如 HTTP 中间件和 SQL.

设置项目

首先,确保你已经安装了最新的 Wrangler CLI 来在本地开发 Worker 项目:

npm install -g wrangler 

然后用 Hono 初始化一个新的 Worker 项目:

npm init hono .

这将在当前目录中设置一个新的 Hono 项目。现在我们可以添加所需的其他依赖项:

npm install hono nanoid @cloudflare/workers-types

这将安装 Hono 框架、用于生成唯一 ID 的 nanoid,以及用于 Workers 开发的类型。

接下来,使用你的 Cloudflare 账户验证 Wrangler CLI,并在项目的 wrangler.toml 文件中配置你的账户详细信息、路由、D1 数据库绑定和 R2 存储桶绑定。

配置 Bindings

wrangler.toml 中定义 Bindings,告诉 Wrangler 和 Workers 运行时如何映射代码中使用的变量。对于这个示例,我们需要配置 R2 存储桶、用户名和密码:

name = "my-app"
type = "javascript"

account_id = "<YOUR_ACCOUNT_ID>"
workers_dev = true

[[r2_buckets]]
binding = "MY_BUCKET" 
bucket_name = "<YOUR_BUCKET_NAME>"

[[d1_databases]]
binding = "DB"
database_name = "my_db"
database_id = "<YOUR_DB_ID>"

[vars]
USERNAME = "<YOUR_USERNAME>"  
PASSWORD = "<YOUR_PASSWORD>"

# 其他配置...

使用适当的凭据填写这些绑定后,Wrangler 将在每次部署时设置这些环境变量,并使它们在 c.env 对象下的代码中可用。

编写服务端代码

现在让我们在 src/index.ts 中实现服务端应用程序:

import { Hono } from 'hono'
import { bearerAuth } from "hono/bearer-auth";
import { logger } from 'hono/logger' 
import { nanoid } from "nanoid";

type Bindings = {
    MY_BUCKET: R2Bucket
    USERNAME: string
    PASSWORD: string  
}

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

app.use(logger())
app.use('/api/*', bearerAuth({token: Env.TOKEN}))

app.get('/', (c) => {    
    return c.json({ ok: true })
})

app.post('/api/v1/upload', async (c) => {
    const key = nanoid(10)
    const formData = await c.req.parseBody()
    const file = formData['file']
    
    if (file instanceof File) {
        const fileBuffer = await file.arrayBuffer()
        const ext = file.name.split('.').pop()
        const path = `images/${key}.${ext}`
        await c.env.MY_BUCKET.put(path, fileBuffer)    
        return c.json({ 'image': { 'url': `${Env.HOST}${path}` }})
    } else {
        return c.text('Invalid file', 400)
    }
})

export default app

这里设置了几个路由:

  • GET / - 健康检查端点
  • POST /api/v1/upload - 用于将图像上传到 R2 的身份验证端点

/upload 端点需要一个带有包含要上传图像的 file 字段的 multipart/form-data 主体。它使用 nanoid 生成一个唯一密钥,将文件上传到 R2 存储桶,并返回公共 URL。

部署到 Cloudflare

实现代码后,我们可以使用 Wrangler 在本地测试它:

wrangler dev

这将启动一个模拟 Workers 运行时的本地服务器。

准备将应用程序部署到 Cloudflare 账户时:

wrangler publish