スタッフディレクトリアプリケーションの構築
このチュートリアルでは、D1を使用してスタッフディレクトリを構築する方法を学びます。このアプリケーションは、ユーザーが組織の従業員に関する情報にアクセスできるようにし、管理者がアプリ内で新しい従業員を直接追加できるようにします。 これを行うには、まずデータをシームレスに管理するためのD1データベースをセットアップし、その後HonoXフレームワーク ↗とCloudflare Pagesを使用してアプリケーションを開発およびデプロイします。
このチュートリアルを進める前に、以下のものを用意してください:
今すぐセットアップを行いたくない場合は、完成したコード ↗をGitHubで確認してください。
このチュートリアルでは、アプリケーションを構築するために、フルスタックウェブサイトとWeb APIを作成するためのメタフレームワークであるHonoX ↗を使用します。プロジェクトでHonoXを使用するには、hono-createコマンドを実行します。
始めるには、以下のコマンドを実行してください:
npm create hono@latestセットアッププロセス中に、プロジェクトディレクトリの名前を提供し、テンプレートを選択するよう求められます。選択する際は、x-basicテンプレートを選んでください。
プロジェクトがセットアップされると、以下のような生成されたファイルのリストが表示されます。これはHonoXアプリケーションの典型的なプロジェクト構造です:
.├── app│ ├── global.d.ts // グローバル型定義│ ├── routes│ │ ├── _404.tsx // ページが見つかりません│ │ ├── _error.tsx // エラーページ│ │ ├── _renderer.tsx // レンダラー定義│ │ ├── about│ │ │ └── [name].tsx // `/about/:name`にマッチ│ │ └── index.tsx // `/`にマッチ│ └── server.ts // サーバーエントリファイル├── package.json├── tsconfig.json└── vite.config.tsプロジェクトには、アプリコード、ルート、およびサーバーセットアップ用のディレクトリが含まれており、パッケージ管理、TypeScript、およびViteの設定ファイルも含まれています。
プロジェクト用のデータベースを作成するには、Cloudflare CLIツールWranglerを使用します。これはD1データベース操作のためのwrangler d1コマンドをサポートしています。以下のコマンドを使用して、staff-directoryという名前の新しいデータベースを作成します:
npx wrangler d1 create staff-directoryデータベースを作成した後、アプリケーションとデータベースを統合するために、Wrangler設定ファイルにbindingを設定する必要があります。
このバインディングにより、アプリケーションはD1データベース、KVネームスペース、およびR2バケットなどのCloudflareリソースと対話できます。これを設定するには、プロジェクトのルートディレクトリにwrangler.tomlファイルを作成し、基本的なセットアップ情報を入力します:
name = "staff-directory"compatibility_date = "2023-12-01"次に、データベースバインディングの詳細をwrangler.tomlファイルに追加します。これには、アプリケーション内でデータベースを参照するために使用されるバインディング名(この場合はDB)を指定し、データベース作成時に提供されたdatabase_nameとdatabase_idを含めます:
[[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'"上記のコマンドを使用すると、コマンドラインから直接クエリや操作を実行できます。
初期データのシーディングやバッチ処理などの操作を行うには、コマンドを含む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.tomlファイルを構成した後、データベースは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).all(); 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を使用します。app/routes/_renderer.tsxでJSXレンダリングミドルウェアを使用してレンダラーを設定し、アプリケーションのエントリポイントとして機能させます:
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: R2で画像URLを処理する
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("リクエストの処理中にエラーが発生しました", { status: 500 }); }});新しい従業員を作成するプロセス中に、アップロードされた画像は、データベースに追加される前にR2バケットに保存できます。
画像をR2バケットに保存するには:
- R2バケットを作成します。
- このバケットに画像をアップロードします。
- バケットから画像の公開URLを取得します。このURLはデータベースに保存され、R2バケットに保存された画像にリンクされます。
wrangler r2 bucket createコマンドを使用してバケットを作成します:
wrangler r2 bucket create employee-avatarsバケットが作成されたら、wrangler.tomlファイルにR2バケットバインディングを追加します:
[[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(`ファイルが正常にアップロードされました: ${key}`); imageUrl = `https://pub-8d936184779047cc96686a631f318fce.r2.dev/${key}`;}完全なコードベースについてはGitHubを参照 ↗してください。
アプリケーションがデプロイの準備が整ったら、Wranglerを使用してプロジェクトをCloudflareネットワークにビルドおよびデプロイできます。wrangler whoamiコマンドを実行してCloudflareアカウントにログインしていることを確認してください。ログインしていない場合、WranglerはAPIキーを作成してログインするように促します。このAPIキーを使用して、コンピュータから自動的に認証されたリクエストを行うことができます。
ログインが成功したら、wrangler.tomlファイルが以下のコードブロックと同様に構成されていることを確認してください:
name = "staff-directory"compatibility_date = "2023-12-01"
[[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ページに移動して新しい従業員を追加し、これによりホームページに新しい従業員が表示されるはずです。
このチュートリアルでは、ユーザーが組織内のすべての従業員を表示できるスタッフディレクトリアプリケーションを構築しました。完全なソースコードについてはスタッフディレクトリリポジトリ ↗を参照してください。
