跳转到内容
搜索文档

在 Cloudflare Workers 上部署 Express.js 应用

最后更新 查看 MarkdownAgent 设置

在本教程中,你将学习如何使用 Cloudflare Workers 平台D1 数据库 在 Cloudflare Workers 上部署 Express.js 应用。你将构建一个成员注册表 API,具备基本的创建、读取、更新和删除(CRUD)操作。你将使用 D1 作为存储和检索成员数据的数据库。

开始之前

所有教程都假设你已经完成了快速入门指南,该指南帮助你设置 Cloudflare Workers 账户、C3Wrangler

快速入门

如果想跳过步骤快速开始,请选择下方的 Deploy to Cloudflare(部署到 Cloudflare)

Deploy to Cloudflare

这会在你的 GitHub 账户中创建仓库并将应用部署到 Cloudflare Workers。如果你熟悉 Cloudflare Workers 并希望跳过逐步指导,请使用此选项。

如果你是 Cloudflare Workers 新手,可能需要手动跟随步骤操作。

1. 创建新的 Cloudflare Workers 项目

使用 C3(Cloudflare 开发者产品的命令行工具)创建新目录并初始化新的 Worker 项目:

npm create cloudflare@latest -- express-d1-app

进行设置时,请选择以下选项:

  • 对于 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(部署前我们还会做一些修改)。

进入新项目目录:

cd express-d1-app

2. 安装 Express 和依赖项

在本教程中,你将使用 Express.js,这是 Node.js 的流行 Web 框架。要在 Cloudflare Workers 环境中使用 Express,请安装 Express 以及必要的 TypeScript 类型:

npm i express @types/express

Cloudflare Workers 上的 Express.js 需要 nodejs_compat 兼容性标志。此标志启用 Node.js API,允许 Express 在 Workers 运行时上运行。将以下内容添加到 Wrangler 配置文件:

{
	"compatibility_flags": [
		"nodejs_compat"
	]
}
compatibility_flags = [ "nodejs_compat" ]

3. 创建 D1 数据库

现在你将创建 D1 数据库来存储成员信息。使用 wrangler d1 create 命令创建新数据库:

npx wrangler d1 create members-db

该命令将创建新的 D1 数据库并询问以下问题:

  • Would you like Wrangler to add it on your behalf?:输入 Y
  • What binding name would you like to use?:输入 DB 并按 Enter。
  • For local dev, do you want to connect to the remote resource instead of a local resource?:输入 N
 ⛅️ wrangler 4.44.0
───────────────────
 Successfully created DB 'members-db' in region WNAM
Created your new D1 database.

To access your new D1 Database in your Worker, add the following snippet to your configuration file:
{
  "d1_databases": [
    {
      "binding": "members_db",
      "database_name": "members-db",
      "database_id": "<unique-ID-for-your-database>"
    }
  ]
}
 Would you like Wrangler to add it on your behalf? yes
 What binding name would you like to use? DB
 For local dev, do you want to connect to the remote resource instead of a local resource? no

绑定将添加到你的 Wrangler 配置文件。

{
	"d1_databases": [
		{
			"binding": "DB",
			"database_name": "members-db",
			"database_id": "<unique-ID-for-your-database>"
		}
	]
}
[[d1_databases]]
binding = "DB"
database_name = "members-db"
database_id = "<unique-ID-for-your-database>"

4. 创建数据库架构

在项目根目录创建名为 schemas 的目录,在其中创建名为 schema.sql 的文件:

schemas/schema.sqlsql
DROP TABLE IF EXISTS members;
CREATE TABLE IF NOT EXISTS members (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  email TEXT NOT NULL UNIQUE,
  joined_date TEXT NOT NULL
);

-- Insert sample data
INSERT INTO members (name, email, joined_date) VALUES
  ('Alice Johnson', '[email protected]', '2024-01-15'),
  ('Bob Smith', '[email protected]', '2024-02-20'),
  ('Carol Williams', '[email protected]', '2024-03-10');

此架构创建 members 表,包含自增 ID、姓名、电子邮件和加入日期字段。它还插入三名示例成员。

对 D1 数据库执行架构文件:

npx wrangler d1 execute members-db --file=./schemas/schema.sql

上述命令在本地开发数据库中创建表。稍后你将把架构部署到生产环境。

5. 初始化 Express 应用

更新 src/index.ts 文件以设置带 TypeScript 的 Express。用以下内容替换文件内容:

src/index.tsts
import { env } from "cloudflare:workers";
import { httpServerHandler } from "cloudflare:node";
import express from "express";

const app = express();

// Middleware to parse JSON bodies
app.use(express.json());

// Health check endpoint
app.get("/", (req, res) => {
	res.json({ message: "Express.js running on Cloudflare Workers!" });
});

app.listen(3000);
export default httpServerHandler({ port: 3000 });

此代码初始化 Express 并创建基本的健康检查端点。关键导入 import { env } from "cloudflare:workers" 允许你从代码中的任何位置访问 绑定(binding),如 D1 数据库。httpServerHandler 将 Express 与 Workers 运行时集成,使应用能够在 Cloudflare 网络上处理 HTTP 请求。

接下来,执行 typegen 命令为 Worker 环境生成类型定义:

npm run cf-typegen

6. 实现读取操作

添加从数据库检索成员的端点。在健康检查端点之后,向 src/index.ts 文件添加以下路由:

src/index.tstypescript
// GET all members
app.get('/api/members', async (req, res) => {
	try {
		const { results } = await env.DB.prepare('SELECT * FROM members ORDER BY joined_date DESC').all();

		res.json({ success: true, members: results });
	} catch (error) {
		res.status(500).json({ success: false, error: 'Failed to fetch members' });
	}
});

// GET a single member by ID
app.get('/api/members/:id', async (req, res) => {
	try {
		const { id } = req.params;

		const { results } = await env.DB.prepare('SELECT * FROM members WHERE id = ?').bind(id).all();

		if (results.length === 0) {
			return res.status(404).json({ success: false, error: 'Member not found' });
		}

		res.json({ success: true, member: results[0] });
	} catch (error) {
		res.status(500).json({ success: false, error: 'Failed to fetch member' });
	}
});

这些路由使用 D1 绑定(env.DB)准备 SQL 语句并执行。由于你在文件顶部从 cloudflare:workers 导入了 env,它可在整个应用中访问。D1 绑定上的 preparebindall 方法允许你安全地查询数据库。有关所有可用方法,请参阅 D1 Workers Binding API

7. 实现创建操作

添加创建新成员的端点。向 src/index.ts 文件添加以下路由:

src/index.tstypescript
// POST - Create a new member
app.post("/api/members", async (req, res) => {
  try {
    const { name, email } = req.body;

    // Validate input
    if (!name || !email) {
      return res.status(400).json({
        success: false,
        error: "Name and email are required",
      });
    }

    // Basic email validation (simplified for tutorial purposes)
    // For production, consider using a validation library or more comprehensive checks
    if (!email.includes("@") || !email.includes(".")) {
      return res.status(400).json({
        success: false,
        error: "Invalid email format",
      });
    }

    const joined_date = new Date().toISOString().split("T")[0];

    const result = await env.DB.prepare(
      "INSERT INTO members (name, email, joined_date) VALUES (?, ?, ?)"
    )
      .bind(name, email, joined_date)
      .run();

    if (result.success) {
      res.status(201).json({
        success: true,
        message: "Member created successfully",
        id: result.meta.last_row_id,
      });
    } else {
      res
        .status(500)
        .json({ success: false, error: "Failed to create member" });
    }
  } catch (error: any) {
    // Handle unique constraint violation
    if (error.message?.includes("UNIQUE constraint failed")) {
      return res.status(409).json({
        success: false,
        error: "Email already exists",
      });
    }
    res.status(500).json({ success: false, error: "Failed to create member" });
  }
});

此端点验证输入、检查电子邮件格式,并将新成员插入数据库。它还通过检查唯一约束违规来处理重复电子邮件地址。

8. 实现更新操作

添加更新现有成员的端点。向 src/index.ts 文件添加以下路由:

src/index.tstypescript
app.put("/api/members/:id", async (req, res) => {
  try {
    const { id } = req.params;
    const { name, email } = req.body;

    // Validate input
    if (!name && !email) {
      return res.status(400).json({
        success: false,
        error: "At least one field (name or email) is required",
      });
    }

    // Basic email validation if provided (simplified for tutorial purposes)
    // For production, consider using a validation library or more comprehensive checks
    if (email && (!email.includes("@") || !email.includes("."))) {
      return res.status(400).json({
        success: false,
        error: "Invalid email format",
      });
    }

    // Build dynamic update query
    const updates: string[] = [];
    const values: any[] = [];

    if (name) {
      updates.push("name = ?");
      values.push(name);
    }
    if (email) {
      updates.push("email = ?");
      values.push(email);
    }

    values.push(id);

    const result = await env.DB.prepare(
      `UPDATE members SET ${updates.join(", ")} WHERE id = ?`
    )
      .bind(...values)
      .run();

    if (result.meta.changes === 0) {
      return res
        .status(404)
        .json({ success: false, error: "Member not found" });
    }

    res.json({ success: true, message: "Member updated successfully" });
  } catch (error: any) {
    if (error.message?.includes("UNIQUE constraint failed")) {
      return res.status(409).json({
        success: false,
        error: "Email already exists",
      });
    }
    res.status(500).json({ success: false, error: "Failed to update member" });
  }
});

此端点允许更新现有成员的姓名、电子邮件或两者。它根据提供的字段构建动态 SQL 查询。

9. 实现删除操作

添加删除成员的端点。向 src/index.ts 文件添加以下路由:

src/index.tstypescript
// DELETE - Delete a member
app.delete("/api/members/:id", async (req, res) => {
  try {
    const { id } = req.params;

    const result = await env.DB.prepare("DELETE FROM members WHERE id = ?")
      .bind(id)
      .run();

    if (result.meta.changes === 0) {
      return res
        .status(404)
        .json({ success: false, error: "Member not found" });
    }

    res.json({ success: true, message: "Member deleted successfully" });
  } catch (error) {
    res.status(500).json({ success: false, error: "Failed to delete member" });
  }
});

此端点按 ID 删除成员,如果成员不存在则返回错误。

10. 本地测试

启动开发服务器以在本地测试 API:

npm run dev

开发服务器将启动,你可以在 http://localhost:8787 访问 API。

打开新的终端窗口并使用 curl 测试端点:

Get all memberssh
curl http://localhost:8787/api/members
{
	"success": true,
	"members": [
		{
			"id": 1,
			"name": "Alice Johnson",
			"email": "[email protected]",
			"joined_date": "2024-01-15"
		},
		{
			"id": 2,
			"name": "Bob Smith",
			"email": "[email protected]",
			"joined_date": "2024-02-20"
		},
		{
			"id": 3,
			"name": "Carol Williams",
			"email": "[email protected]",
			"joined_date": "2024-03-10"
		}
	]
}

测试创建新成员:

Create a membersh
curl -X POST http://localhost:8787/api/members \
  -H "Content-Type: application/json" \
  -d '{"name": "David Brown", "email": "[email protected]"}'
{
	"success": true,
	"message": "Member created successfully",
	"id": 4
}

测试获取单个成员:

Get a member by IDsh
curl http://localhost:8787/api/members/1

测试更新成员:

Update a membersh
curl -X PUT http://localhost:8787/api/members/1 \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice Cooper"}'

测试删除成员:

Delete a membersh
curl -X DELETE http://localhost:8787/api/members/4

11. 部署到 Cloudflare Workers

部署到生产环境之前,对远程(生产)数据库执行架构文件:

npx wrangler d1 execute members-db --remote --file=./schemas/schema.sql

现在将应用部署到 Cloudflare 网络:

npm run deploy
⛅️ wrangler 4.44.0
───────────────────
Total Upload: 1743.64 KiB / gzip: 498.65 KiB
Worker Startup Time: 48 ms
Your Worker has access to the following bindings:
Binding                  Resource
env.DB (members-db)      D1 Database

Uploaded express-d1-app (2.99 sec)
Deployed express-d1-app triggers (5.26 sec)
  https://<your-subdomain>.workers.dev
Current Version ID: <version-id>

部署成功后,Wrangler 将输出 Worker 的 URL。

12. 测试生产部署

使用提供的 URL 测试已部署的 API。将 <your-worker-url> 替换为实际的 Worker URL:

Test production APIsh
curl https://<your-worker-url>/api/members

你应该看到在生产数据库中创建的相同成员数据。

在生产环境中创建新成员:

Create a member in productionsh
curl -X POST https://<your-worker-url>/api/members \
  -H "Content-Type: application/json" \
  -d '{"name": "Eva Martinez", "email": "[email protected]"}'

你的 Express.js 应用与 D1 数据库现已在 Cloudflare Workers 上运行。

结论

在本教程中,你使用 Express.js 和 D1 数据库构建了成员注册表 API,并将其部署到 Cloudflare Workers。你实现了完整的 CRUD 操作(创建、读取、更新、删除),并学会了如何:

  • 为 Cloudflare Workers 设置 Express.js 应用
  • 创建并配置带绑定的 D1 数据库
  • 使用 D1 预编译语句实现数据库操作
  • 在本地和生产环境中测试 API

后续步骤

这篇文档对您有帮助吗?