このチュートリアルでは、D1 を使ってスタッフディレクトリを構築します。このアプリケーションでは、組織の従業員情報を参照でき、管理者はアプリ内で新しい従業員を追加できます。 そのためには、まず D1 データベース を用意してデータを管理し、HonoX Framework ↗ と Cloudflare Pages でアプリケーションを開発・デプロイします。
このチュートリアルを進める前に、次を用意してください。
今はセットアップを進めない場合は、GitHub で 完成済みのコード ↗ を参照できます。
このチュートリアルでは、フルスタックの Web サイトと Web API を作るメタフレームワーク HonoX ↗ を使います。プロジェクトで HonoX を使うには、hono-create コマンドを実行します。
次のコマンドで開始します。
npm create hono@latestセットアップ中に、プロジェクトディレクトリの名前とテンプレートの選択を求められます。選択するときは x-basic テンプレートを選びます。
プロジェクトのセットアップが終わると、次のような生成ファイルの一覧を確認できます。これは HonoX アプリケーションの典型的な構成です。
.
├── app
│ ├── global.d.ts // global type definitions
│ ├── routes
│ │ ├── _404.tsx // not found page
│ │ ├── _error.tsx // error page
│ │ ├── _renderer.tsx // renderer definition
│ │ ├── about
│ │ │ └── [name].tsx // matches `/about/:name`
│ │ └── index.tsx // matches `/`
│ └── server.ts // server entry file
├── package.json
├── tsconfig.json
└── vite.config.tsプロジェクトには、アプリ本体、ルート、サーバー設定のディレクトリに加え、パッケージ管理、TypeScript、Vite 用の設定ファイルがあります。
プロジェクト用のデータベースを作成するには、Cloudflare の CLI ツール Wrangler を使います。D1 の操作には wrangler d1 コマンドが使えます。次のコマンドで、staff-directory という名前のデータベースを作成します。
npx wrangler d1 create staff-directoryデータベースを作成したら、アプリケーションとデータベースを連携するために、Wrangler 設定ファイル で バインディング を設定します。
このバインディングにより、アプリケーションから D1 データベース、KV 名前空間、R2 バケットなどの Cloudflare リソースを操作できます。設定するには、プロジェクトのルートディレクトリに Wrangler ファイルを作成し、基本情報を入力します。
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "staff-directory",
// Set this to today's date
"compatibility_date": "2026-09-20"
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "staff-directory"
# Set this to today's date
compatibility_date = "2026-09-20"次に、データベースのバインディング詳細を Wrangler ファイルに追加します。アプリケーション内でデータベースを参照するバインディング名(ここでは DB)と、データベース作成時に表示された database_name および 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 設定も調整します。ローカル環境で 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'"このコマンドで、コマンドラインから直接クエリや操作を実行できます。
初期データの投入やバッチ処理などでは、コマンドを書いた 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 の 3 つのテーブルを作成します。初期データを入れるには INSERT INTO を使います。スキーマファイルの準備ができたら、D1 データベースに適用します。実行するスキーマファイルは --file フラグで指定します。
wrangler d1 execute staff-directory --file=./schema.sqlローカルでスキーマを実行し、ローカルディレクトリにデータを投入するには、上記コマンドに --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() は、GET、POST、PUT、DELETE など異なる HTTP メソッドを扱うルートを定義するためのヘルパーです。
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 で呼び出しています。これで取得したデータをページに表示できます。
/admin ページから新しい従業員を作成するには、export POST ルートを使います。
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バケットを作成したら、Wrangler ファイルに R2 バケットのバインディングを追加します。
{
"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-09-20",
"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-09-20"
[[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 リポジトリ ↗ を参照してください。
