在本教程中,您将学习如何使用 D1 构建员工目录。该应用允许用户访问组织员工信息,并赋予管理员在应用内直接添加新员工的能力。 为此,您首先需要设置 D1 数据库 以无缝管理数据,然后使用 HonoX 框架 ↗ 和 Cloudflare Pages 开发并部署您的应用。
继续本教程之前,请确保您具备以下条件:
如果您现在不想完成设置,可在 GitHub 上查看完整代码 ↗。
在本教程中,您将使用 HonoX ↗(用于创建全栈网站和 Web API 的元框架)构建应用。要在项目中使用 HonoX,运行 hono-create 命令。
要开始,运行以下命令:
npm create hono@latest在设置过程中,系统会要求您提供项目目录名称并选择模板。选择 x-basic 模板。
项目设置完成后,您可以看到如下生成的文件列表。这是 HonoX 应用的典型项目结构:
.
├── app
│ ├── global.d.ts // 全局类型定义
│ ├── routes
│ │ ├── _404.tsx // 404 未找到页面
│ │ ├── _error.tsx // 错误页面
│ │ ├── _renderer.tsx // 渲染器定义
│ │ ├── about
│ │ │ └── [name].tsx // 匹配 `/about/:name`
│ │ └── index.tsx // 匹配 `/`
│ └── server.ts // 服务器入口文件
├── package.json
├── tsconfig.json
└── vite.config.ts项目包括用于应用代码、路由和服务器设置的目录,以及用于包管理、TypeScript 和 Vite 的配置文件。
要为您的项目创建数据库,请使用 Cloudflare 命令行工具 Wrangler,它支持用于 D1 数据库操作的 wrangler d1 命令。使用以下命令创建一个名为 staff-directory 的新数据库:
npx wrangler d1 create staff-directory创建您的数据库后,您需要在 Wrangler 配置文件 中设置 绑定 (binding) 以将您的数据库与您的应用集成。
此绑定使您的应用能够与 Cloudflare 资源(例如 D1 数据库、KV 命名空间和 R2 存储桶)进行交互。要进行配置,请在项目的根目录中创建一个 Wrangler 配置文件并输入基本的设置信息:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "staff-directory",
// Set this to today's date
"compatibility_date": "2026-08-17"
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "staff-directory"
# Set this to today's date
compatibility_date = "2026-08-17"接着,将数据库绑定详细信息添加到 Wrangler 配置文件中。这包括指定一个绑定名称(在此例中为 DB,用于在应用中引用该数据库),以及创建数据库时提供的 database_name and database_id:
{
"d1_databases": [
{
"binding": "DB",
"database_name": "staff-directory",
"database_id": "f495af5f-dd71-4554-9974-97bdda7137b3"
}
]
}[[d1_databases]]
binding = "DB"
database_name = "staff-directory"
database_id = "f495af5f-dd71-4554-9974-97bdda7137b3"您现在已经配置了您的应用,使其能够通过命令行或直接在您的代码库中访问并与您的 D1 数据库进行交互。
您还需要调整 vite.config.js 中的 Vite 配置文件。添加以下配置设置,确保 Vite 在本地环境中正确配置以使用 Cloudflare 绑定:
import adapter from "@hono/vite-dev-server/cloudflare";
export default defineConfig(({ mode }) => {
if (mode === "client") {
return {
plugins: [client()],
};
} else {
return {
plugins: [
honox({
devServer: {
adapter,
},
}),
pages(),
],
};
}
});要与您的 D1 数据库交互,您可以使用 wrangler d1 execute 命令直接执行 SQL 命令:
wrangler d1 execute staff-directory --command "SELECT name FROM sqlite_schema WHERE type ='table'"上面的命令允许您直接从命令行运行查询或操作。
对于诸如初始数据播种(seeding)或批处理等操作,您可以传递包含命令的 SQL 文件。为此,请在项目的根目录中创建一个 schema.sql 文件,并在其中插入您的 SQL 查询:
CREATE TABLE locations (
location_id INTEGER PRIMARY KEY AUTOINCREMENT,
location_name VARCHAR(255) NOT NULL
);
CREATE TABLE departments (
department_id INTEGER PRIMARY KEY AUTOINCREMENT,
department_name VARCHAR(255) NOT NULL
);
CREATE TABLE employees (
employee_id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(255) NOT NULL,
position VARCHAR(255) NOT NULL,
image_url VARCHAR(255) NOT NULL,
join_date DATE NOT NULL,
location_id INTEGER REFERENCES locations(location_id),
department_id INTEGER REFERENCES departments(department_id)
);
INSERT INTO locations (location_name) VALUES ('London, UK'), ('Paris, France'), ('Berlin, Germany'), ('Lagos, Nigeria'), ('Nairobi, Kenya'), ('Cairo, Egypt'), ('New York, NY'), ('San Francisco, CA'), ('Chicago, IL');
INSERT INTO departments (department_name) VALUES ('Software Engineering'), ('Product Management'), ('Information Technology (IT)'), ('Quality Assurance (QA)'), ('User Experience (UX)/User Interface (UI) Design'), ('Sales and Marketing'), ('Human Resources (HR)'), ('Customer Support'), ('Research and Development (R&D)'), ('Finance and Accounting');上述查询将创建三个表:locations、departments 和 employees。要向这些表填充初始数据,可使用 INSERT INTO 命令。在准备好包含这些命令的 schema 文件后,您可以将其应用到 D1 数据库。为此,请使用 --file 标志指定要执行的 schema 文件:
wrangler d1 execute staff-directory --file=./schema.sql要本地执行该 schema 并向您的本地目录播种数据,请向上述命令传递 --local 标志。
按照前面步骤设置 D1 数据库并配置 Wrangler 文件后,您的数据库可通过 DB 绑定在代码中访问。这允许您通过准备和执行 SQL 语句直接与数据库交互。在以下步骤中,您将学习如何使用此绑定执行常见数据库操作,例如检索数据和插入新记录。
export const findAllEmployees = async (db: D1Database) => {
const query = `
SELECT employees.*, locations.location_name, departments.department_name
FROM employees
JOIN locations ON employees.location_id = locations.location_id
JOIN departments ON employees.department_id = departments.department_id
`;
const { results } = await db.prepare(query).run();
const employees = results;
return employees;
};export const createEmployee = async (db: D1Database, employee: Employee) => {
const query = `
INSERT INTO employees (name, position, join_date, image_url, department_id, location_id)
VALUES (?, ?, ?, ?, ?, ?)`;
const results = await db
.prepare(query)
.bind(
employee.name,
employee.position,
employee.join_date,
employee.image_url,
employee.department_id,
employee.location_id,
)
.run();
const employees = results;
return employees;
};要查看应用中使用的所有查询的完整列表,请参考代码库中的 db.ts ↗ 文件。
该应用使用 hono/jsx 进行渲染。您可以使用 JSX 渲染中间件在 app/routes/_renderer.tsx 中设置一个渲染器(Renderer),作为您的应用入口点:
import { jsxRenderer } from 'hono/jsx-renderer'
import { Script } from 'honox/server'
export default jsxRenderer(({ children, title }) => {
return (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
<Script src="/app/client.ts" async />
</head>
<body>{children}</body>
</html>
)
})在定义 TypeScript 全局类型定义的 global.d.ts 文件中添加前面定义的绑定,从而确保整个应用中的类型一致性:
declare module "hono" {
interface Env {
Variables: {};
Bindings: {
DB: D1Database;
};
}
}此应用使用 Tailwind CSS ↗ 进行样式设计。要使用 Tailwind CSS,请参考 TailwindCSS 文档 ↗,或者按照 GitHub 上提供的步骤 ↗操作。
要显示员工列表,请从您的 db.ts 文件中调用 findAllEmployees 函数,并在 routes/index.tsx 文件中执行该调用。文件中现存的 createRoute() 函数是用作定义处理不同 HTTP 方法(如 GET、POST、PUT 或 DELETE)的路由辅助函数。
import { css } from 'hono/css'
import { createRoute } from 'honox/factory'
import Counter from '../islands/counter'
const className = css`
font-family: sans-serif;
`
export default createRoute((c) => {
const name = c.req.query('name') ?? 'Hono'
return c.render(
<div class={className}>
<h1>Hello, {name}!</h1>
<Counter />
</div>,
{ title: name }
)
})文件中的现有代码包含一个使用 Counter 组件的占位符。您应该将该部分替换为以下代码块:
import { createRoute } from 'honox/factory'
import type { FC } from 'hono/jsx'
import type { Employee } from '../db'
import { findAllEmployees, findAllDepartments, findAllLocations } from '../db'
const EmployeeCard: FC<{ employee: Employee }> = ({ employee }) => {
const { employee_id, name, image_url, department_name, location_name } = employee;
return (
<div className="max-w-sm bg-white border border-gray-200 rounded-lg shadow-md">
<a href={`/employee/${employee_id}`}>
<img className="bg-indigo-600 p-4 rounded-t-lg" src={image_url} alt={name} />
//...
</a>
</div>
);
};
export const GET = createRoute(async (c) => {
const employees = await findAllEmployees(c.env.DB)
const locations = await findAllLocations(c.env.DB)
const departments = await findAllDepartments(c.env.DB)
return c.render(
<section className="flex-grow">
<h1 className="mb-4 text-3xl font-extrabold text-gray-900 dark:text-white md:text-5xl lg:text-6xl mt-12">
<span className="text-transparent bg-clip-text bg-gradient-to-r to-blue-600 from-sky-400">{`Directory `}</span>
</h1>
//...
</section>
<section className="flex flex-wrap -mx-4">
{employees.map((employee) => (
<div className="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 px-2 mb-4">
<EmployeeCard employee={employee} />
</div>
))}
</section>
</section>
)
})此代码片段展示了如何从 db.ts 文件导入 findAllEmployees、findAllLocations 和 findAllDepartments 函数,以及如何使用绑定 c.env.DB 来调用这些函数。通过这些,您可以检索并在页面上显示获取到的数据。
使用 export POST 路由通过 /admin 页面创建一个新员工:
import { createRoute } from "honox/factory";
import type { Employee } from "../../db";
import { getFormDataValue, getFormDataNumber } from "../../utils/formData";
import { createEmployee } from "../../db";
export const POST = createRoute(async (c) => {
try {
const formData = await c.req.formData();
const imageFile = formData.get("image_file");
let imageUrl = "";
// TODO: process image url with R2
const employeeData: Employee = {
employee_id: getFormDataValue(formData, "employee_id"),
name: getFormDataValue(formData, "name"),
position: getFormDataValue(formData, "position"),
image_url: imageUrl,
join_date: getFormDataValue(formData, "join_date"),
department_id: getFormDataNumber(formData, "department_id"),
location_id: getFormDataNumber(formData, "location_id"),
location_name: "",
department_name: "",
};
await createEmployee(c.env.DB, employeeData);
return c.redirect("/", 303);
} catch (error) {
return new Response("Error processing your request", { status: 500 });
}
});在创建新员工的过程中,上传的图像可以先存储在 R2 存储桶中,然后再添加到数据库中。
要将图像存储在 R2 存储桶中:
- 创建一个 R2 存储桶。
- 将图像上传到此存储桶。
- 从存储桶中获取该图像的公开 URL。接着将该 URL 保存到您的数据库中,从而关联存储在 R2 存储桶中的图像。
使用 wrangler r2 bucket create 命令创建一个存储桶:
wrangler r2 bucket create employee-avatars创建存储桶后,将 R2 存储桶绑定添加到 Wrangler 配置文件中:
{
"r2_buckets": [
{
"binding": "MY_BUCKET",
"bucket_name": "employee-avatars"
}
]
}[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "employee-avatars"将 R2 绑定传递到 global.d.ts 文件中:
declare module "hono" {
interface Env {
Variables: {};
Bindings: {
DB: D1Database;
MY_BUCKET: R2Bucket;
};
}
}要将上传的图像存储在 R2 存储桶中,您可以使用 R2 提供的 put() 方法。此方法允许您将图像文件上传到您的存储桶:
if (imageFile instanceof File) {
const key = `${new Date().getTime()}-${imageFile.name}`;
const fileBuffer = await imageFile.arrayBuffer();
await c.env.MY_BUCKET.put(key, fileBuffer, {
httpMetadata: {
contentType: imageFile.type || "application/octet-stream",
},
});
console.log(`File uploaded successfully: ${key}`);
imageUrl = `https://pub-8d936184779047cc96686a631f318fce.r2.dev/${key}`;
}有关完整的代码库,请参阅 GitHub ↗。
在您的应用准备好进行部署后,您可以使用 Wrangler 将您的项目构建并部署到 Cloudflare 网络。通过运行 wrangler whoami 命令,确保您已登录您的 Cloudflare 账户。如果尚未登录,Wrangler 会提示您登录并创建一个 API 密钥,以便从您的计算机自动发起通过身份验证的请求。
成功登录后,确认 Wrangler 配置文件与下面的代码块配置类似:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "staff-directory",
// Set this to today's date
"compatibility_date": "2026-08-17",
"r2_buckets": [
{
"binding": "MY_BUCKET",
"bucket_name": "employee-avatars"
}
],
"d1_databases": [
{
"binding": "DB",
"database_name": "staff-directory",
"database_id": "f495af5f-dd71-4554-9974-97bdda7137b3"
}
]
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "staff-directory"
# Set this to today's date
compatibility_date = "2026-08-17"
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "employee-avatars"
[[d1_databases]]
binding = "DB"
database_name = "staff-directory"
database_id = "f495af5f-dd71-4554-9974-97bdda7137b3"运行 wrangler deploy 以将您的项目部署到 Cloudflare。部署后,您可以通过访问为您提供的已部署 URL 来测试您的应用是否正常运行。您的浏览器应该会显示带有您创建的基础前端的应用。如果您的数据库中还没有填充任何数据,请转到 /admin 页面以添加新员工,这应该会在您的主页中显示该新员工。
在本教程中,您构建了一个员工目录应用,用户可以在其中查看组织内的所有员工。请参阅 Staff directory 仓库 ↗ 获取完整源代码。
