跳转到内容
搜索文档

构建员工目录应用

最后更新 查看 MarkdownAgent 设置

在本教程中,您将学习如何使用 D1 构建员工目录。该应用允许用户访问组织员工信息,并赋予管理员在应用内直接添加新员工的能力。 为此,您首先需要设置 D1 数据库 以无缝管理数据,然后使用 HonoX 框架Cloudflare Pages 开发并部署您的应用。

前提条件

继续本教程之前,请确保您具备以下条件:

  • Cloudflare 账户,如果没有,请先注册再继续。
  • 已安装最新版本的 npm

如果您现在不想完成设置,可在 GitHub 上查看完整代码

1. 安装 HonoX

在本教程中,您将使用 HonoX(用于创建全栈网站和 Web API 的元框架)构建应用。要在项目中使用 HonoX,运行 hono-create 命令。

要开始,运行以下命令:

npm create hono@latest

在设置过程中,系统会要求您提供项目目录名称并选择模板。选择 x-basic 模板。

2. 初始化 HonoX 应用

项目设置完成后,您可以看到如下生成的文件列表。这是 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 的配置文件。

3. 创建数据库

要为您的项目创建数据库,请使用 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(),
			],
		};
	}
});

4. 与 D1 进行交互

要与您的 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');

上述查询将创建三个表:locationsdepartmentsemployees。要向这些表填充初始数据,可使用 INSERT INTO 命令。在准备好包含这些命令的 schema 文件后,您可以将其应用到 D1 数据库。为此,请使用 --file 标志指定要执行的 schema 文件:

wrangler d1 execute staff-directory --file=./schema.sql

要本地执行该 schema 并向您的本地目录播种数据,请向上述命令传递 --local 标志。

5. 编写 SQL 语句

按照前面步骤设置 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 文件。

6. 开发 UI

该应用使用 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 方法(如 GETPOSTPUTDELETE)的路由辅助函数。

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 文件导入 findAllEmployeesfindAllLocationsfindAllDepartments 函数,以及如何使用绑定 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 存储桶中:

  1. 创建一个 R2 存储桶。
  2. 将图像上传到此存储桶。
  3. 从存储桶中获取该图像的公开 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

7. 部署您的 HonoX 应用程序

在您的应用准备好进行部署后,您可以使用 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 仓库 获取完整源代码。

员工目录演示

这篇文档对您有帮助吗?