跳转到内容
搜索文档

构建评论 API

最后更新 查看 MarkdownAgent 设置

在本教程中,您将使用 D1 和 Hono 构建用于存储和检索博客评论的 JSON API。您将创建 D1 数据库、定义 schema,并连接从数据库读取和写入的 GETPOST 端点。

前提条件

  1. 注册 Cloudflare 账户
  2. 安装 Node.js

Node.js 版本管理器

使用 Voltanvm 等 Node 版本管理器,以避免权限问题并切换 Node.js 版本。本指南后续将介绍的 Wrangler 需要 Node 版本 16.17.0 或更高。

1. 创建新 Worker 项目

  1. 运行以下命令创建名为 d1-comments-api 的新项目:

    npm 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(部署前我们还会做一些修改)。
  2. 进入项目目录:

    cd d1-comments-api

2. 安装 Hono

安装 Hono,用于在 Workers 上构建 API 的轻量级 Web 框架:

npm i hono

3. 创建数据库

  1. 使用 Wrangler 创建一个新的 D1 数据库:

    npx wrangler@latest d1 create d1-comments-api
  2. 当提示 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 数据库。

4. 创建 schema 并填充数据库

  1. 创建具有以下内容的 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');
  2. 首先针对本地数据库运行 schema:

    npx wrangler d1 execute d1-comments-api --local --file schemas/schema.sql
  3. 验证表已在本地创建:

    npx wrangler d1 execute d1-comments-api --local --command "SELECT name FROM sqlite_schema WHERE type = 'table'"
    ┌──────────┐
    │ name     │
    ├──────────┤
    │ comments │
    └──────────┘
  4. 确认 schema 无误后,将其应用到远程(生产)数据库:

    npx wrangler d1 execute d1-comments-api --remote --file schemas/schema.sql

5. 初始化 Hono 应用

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;

6. 查询评论

添加 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 执行查询。

7. 插入评论

添加 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("出错了");
	}
});

8. (可选)添加 CORS 支持

如果您计划从不同源的前端应用调用此 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 标头。

9. 部署您的应用程序

  1. 登录到您的 Cloudflare 账户(如果尚未登录):

    npx wrangler whoami

    如果您尚未登录,Wrangler 会提示您登录。

  2. 部署您的 Worker:

    npx wrangler deploy
  3. 通过插入并检索评论来测试 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!"}'
    Created
    curl 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;

后续步骤

这篇文档对您有帮助吗?