在本教程中,您将使用 D1 和 Hono ↗ 构建用于存储和检索博客评论的 JSON API。您将创建 D1 数据库、定义 schema,并连接从数据库读取和写入的 GET 和 POST 端点。
- 注册 Cloudflare 账户 ↗。
- 安装
Node.js↗。
Node.js 版本管理器
使用 Volta ↗ 或 nvm ↗ 等 Node 版本管理器,以避免权限问题并切换 Node.js 版本。本指南后续将介绍的 Wrangler 需要 Node 版本 16.17.0 或更高。
-
运行以下命令创建名为
d1-comments-api的新项目:npm create cloudflare@latest -- d1-comments-apiyarn create cloudflare d1-comments-apipnpm create cloudflare@latest d1-comments-api进行设置时,请选择以下选项:
- 对于 What would you like to start with?,选择
Hello World example。 - 对于 Which template would you like to use?,选择
Worker only。 - 对于 Which language do you want to use?,选择
TypeScript。 - 对于 Do you want to use git for version control?,选择
Yes。 - 对于 Do you want to deploy your application?,选择
No(部署前我们还会做一些修改)。
- 对于 What would you like to start with?,选择
-
进入项目目录:
cd d1-comments-api
安装 Hono ↗,用于在 Workers 上构建 API 的轻量级 Web 框架:
npm i honoyarn add honopnpm add honobun add hono-
使用 Wrangler 创建一个新的 D1 数据库:
npx wrangler@latest d1 create d1-comments-api -
当提示
Would you like Wrangler to add it on your behalf?时,选择Yes。这会自动将DB绑定添加到 Wrangler 配置文件中。确认 Wrangler 配置文件包含
d1_databases绑定和完整的项目配置:{ "$schema": "./node_modules/wrangler/config-schema.json", "name": "d1-comments-api", "main": "src/index.ts", // Set this to today's date "compatibility_date": "2026-08-17", "d1_databases": [ { "binding": "DB", "database_name": "d1-comments-api", "database_id": "<YOUR_DATABASE_ID>" } ] }name = "d1-comments-api" main = "src/index.ts" # Set this to today's date compatibility_date = "2026-08-17" [[d1_databases]] binding = "DB" # 可在您的 Worker 中通过 env.DB 使用 database_name = "d1-comments-api" database_id = "<YOUR_DATABASE_ID>"将
<YOUR_DATABASE_ID>替换为wrangler d1 create命令输出的 ID。
绑定 (Bindings) 允许您的 Workers 在代码中使用变量名称来访问资源,例如 D1 数据库、KV 命名空间和 R2 存储桶。您可以在您的 Worker 中通过 env.DB 访问您的 D1 数据库。
-
创建具有以下内容的
schemas/schema.sql文件:DROP TABLE IF EXISTS comments; CREATE TABLE IF NOT EXISTS comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, author TEXT NOT NULL, body TEXT NOT NULL, post_slug TEXT NOT NULL ); CREATE INDEX idx_comments_post_slug ON comments (post_slug); -- (可选)取消注释以下查询以插入种子数据 -- INSERT INTO comments (author, body, post_slug) VALUES ('Kristian', 'Great post!', 'hello-world'); -
首先针对本地数据库运行 schema:
npx wrangler d1 execute d1-comments-api --local --file schemas/schema.sql -
验证表已在本地创建:
npx wrangler d1 execute d1-comments-api --local --command "SELECT name FROM sqlite_schema WHERE type = 'table'"┌──────────┐ │ name │ ├──────────┤ │ comments │ └──────────┘ -
确认 schema 无误后,将其应用到远程(生产)数据库:
npx wrangler d1 execute d1-comments-api --remote --file schemas/schema.sql
将 src/index.ts 的内容替换为以下代码。这将设置带有类型化 Bindings 接口的 Hono 应用,使 env.DB 正确类型化为 D1Database:
import { Hono } from "hono";
const app = new Hono();
app.get("/api/posts/:slug/comments", async (c) => {
// 执行某些操作并返回 HTTP 响应
// (可选)对 c.req.param("slug") 进行某些处理
});
app.post("/api/posts/:slug/comments", async (c) => {
// 执行某些操作并返回 HTTP 响应
// (可选)对 c.req.param("slug") 进行某些处理
});
export default app;import { Hono } from "hono";
type Bindings = {
DB: D1Database;
};
const app = new Hono<{ Bindings: Bindings }>();
app.get("/api/posts/:slug/comments", async (c) => {
// 执行某些操作并返回 HTTP 响应
// (可选)对 c.req.param("slug") 进行某些处理
});
app.post("/api/posts/:slug/comments", async (c) => {
// 执行某些操作并返回 HTTP 响应
// (可选)对 c.req.param("slug") 进行某些处理
});
export default app;添加 GET 端点逻辑以检索给定帖子的评论。这使用 D1 的 Workers 绑定 API 来准备和执行参数化查询:
app.get("/api/posts/:slug/comments", async (c) => {
const { slug } = c.req.param();
const { results } = await c.env.DB.prepare(
"SELECT * FROM comments WHERE post_slug = ?",
)
.bind(slug)
.run();
return c.json(results);
});app.get("/api/posts/:slug/comments", async (c) => {
const { slug } = c.req.param();
const { results } = await c.env.DB.prepare(
"SELECT * FROM comments WHERE post_slug = ?",
)
.bind(slug)
.run();
return c.json(results);
});代码使用 prepare 创建参数化语句,bind 安全传递 slug 值(防止 SQL 注入),以及 run 执行查询。
添加 POST 端点以创建新评论。这会在插入行之前对请求体进行校验:
app.post("/api/posts/:slug/comments", async (c) => {
const { slug } = c.req.param();
const { author, body } = await c.req.json();
if (!author) return c.text("新评论缺少作者 (author) 的值", 400);
if (!body) return c.text("新评论缺少内容 (body) 的值", 400);
const { success } = await c.env.DB.prepare(
"INSERT INTO comments (author, body, post_slug) VALUES (?, ?, ?)",
)
.bind(author, body, slug)
.run();
if (success) {
c.status(201);
return c.text("Created");
} else {
c.status(500);
return c.text("出错了");
}
});app.post("/api/posts/:slug/comments", async (c) => {
const { slug } = c.req.param();
const { author, body } = await c.req.json<{
author: string;
body: string;
}>();
if (!author) return c.text("新评论缺少作者 (author) 的值", 400);
if (!body) return c.text("新评论缺少内容 (body) 的值", 400);
const { success } = await c.env.DB.prepare(
"INSERT INTO comments (author, body, post_slug) VALUES (?, ?, ?)",
)
.bind(author, body, slug)
.run();
if (success) {
c.status(201);
return c.text("Created");
} else {
c.status(500);
return c.text("出错了");
}
});如果您计划从不同源的前端应用调用此 API,请添加 CORS 中间件。从 Hono 导入 cors 模块并在路由之前添加:
import { Hono } from "hono";
import { cors } from "hono/cors";
const app = new Hono();
app.use("/api/*", cors());import { Hono } from "hono";
import { cors } from "hono/cors";
type Bindings = {
DB: D1Database;
};
const app = new Hono<{ Bindings: Bindings }>();
app.use("/api/*", cors());当您向 /api/* 发起请求时,Hono 将自动生成并向您的 API 响应添加 CORS 标头。
-
登录到您的 Cloudflare 账户(如果尚未登录):
npx wrangler whoami如果您尚未登录,Wrangler 会提示您登录。
-
部署您的 Worker:
npx wrangler deploy -
通过插入并检索评论来测试 API:
# 将 <YOUR_SUBDOMAIN> 替换为您的 workers.dev 子域 curl -X POST https://d1-comments-api.<YOUR_SUBDOMAIN>.workers.dev/api/posts/hello-world/comments \ -H "Content-Type: application/json" \ -d '{"author": "Kristian", "body": "Great post!"}'Createdcurl https://d1-comments-api.<YOUR_SUBDOMAIN>.workers.dev/api/posts/hello-world/comments[ { "id": 1, "author": "Kristian", "body": "Great post!", "post_slug": "hello-world" } ]
包含所有路由和 CORS 支持的完整 src/index.ts:
import { Hono } from "hono";
import { cors } from "hono/cors";
const app = new Hono();
app.use("/api/*", cors());
app.get("/api/posts/:slug/comments", async (c) => {
const { slug } = c.req.param();
const { results } = await c.env.DB.prepare(
"SELECT * FROM comments WHERE post_slug = ?",
)
.bind(slug)
.run();
return c.json(results);
});
app.post("/api/posts/:slug/comments", async (c) => {
const { slug } = c.req.param();
const { author, body } = await c.req.json();
if (!author) return c.text("新评论缺少作者 (author) 的值", 400);
if (!body) return c.text("新评论缺少内容 (body) 的值", 400);
const { success } = await c.env.DB.prepare(
"INSERT INTO comments (author, body, post_slug) VALUES (?, ?, ?)",
)
.bind(author, body, slug)
.run();
if (success) {
c.status(201);
return c.text("Created");
} else {
c.status(500);
return c.text("出错了");
}
});
export default app;import { Hono } from "hono";
import { cors } from "hono/cors";
type Bindings = {
DB: D1Database;
};
const app = new Hono<{ Bindings: Bindings }>();
app.use("/api/*", cors());
app.get("/api/posts/:slug/comments", async (c) => {
const { slug } = c.req.param();
const { results } = await c.env.DB.prepare(
"SELECT * FROM comments WHERE post_slug = ?",
)
.bind(slug)
.run();
return c.json(results);
});
app.post("/api/posts/:slug/comments", async (c) => {
const { slug } = c.req.param();
const { author, body } = await c.req.json<{
author: string;
body: string;
}>();
if (!author) return c.text("新评论缺少作者 (author) 的值", 400);
if (!body) return c.text("新评论缺少内容 (body) 的值", 400);
const { success } = await c.env.DB.prepare(
"INSERT INTO comments (author, body, post_slug) VALUES (?, ?, ?)",
)
.bind(author, body, slug)
.run();
if (success) {
c.status(201);
return c.text("Created");
} else {
c.status(500);
return c.text("出错了");
}
});
export default app;- 请参阅 D1 Workers 绑定 API 以获取所有可用方法的完整列表。
- 了解 D1 本地开发 从而在不部署的情况下测试您的数据库。
- 探索 构建在 D1 上的社区项目。