少し前から、Hono + React + TanStack Router + TanStack Query + Chart.js + Drizzle ORM あたりをさわってきました。
- Hono + React + Chart.js + TanStack Router + TanStack Query を使って、Hono製APIのレスポンスをPie chartとして表示してみた - メモ的な思考的な
- TypeScript + Bun + SQLite + Drizzle ORM な環境にて、Drizzle Kit の各コマンドを試してみた - メモ的な思考的な
- TypeScript + Bun + SQLite な環境にて、SQLのDDLをDrizzle ORM で書いてみたり、初期データの投入(seed)をしてみた - メモ的な思考的な
今まではローカルのみでアプリ開発していたため、次はどこかにデプロイしたくなりました。
そこで、以前から気になっていた Cloudflare Pages と D1にアプリを乗せてみたところ、色々ハマったことがあったため、メモを残します。
目次
- 環境
- 前置き
- アプリの作成
- Hono + React + Chart.js + TanStack Router + TanStack Query なアプリを実装
- Cloudflare D1のデータを表示するようアプリを修正
- ソースコード
環境
- Windows11 WSL2
- React 18.3.1
- Chart.js 4.4.2
- react-chartjs-2 5.2.0
- Hono 4.3.1
- TanStack Router 1.31.17
- TanStack Query 5.34.1
- Drizzle ORM 0.30.10
- Drizzle Kit 0.20.17
- Wrangler 3.47.0
なお、今回は Bun ではなく npm
を使います。
ここまでの記事から問題なく動くような気もしますが、現在のCloudflareの公式ドキュメントでは Bun が控えめだったので、 npm にしておきました。
前置き
アプリの構成
今回のアプリは
- Hono の上で React を動かす
- フロントエンドとバックエンドを1つのリポジトリにする
- 参考
- Hono の中で Drizzle ORM を使い、D1からデータを取得する
- フロントエンドのルーティングは TanStack Router を使う
という構成とします。このアプリを Cloudflare Pages にデプロイします。
Cloudflare Pages へのデプロイ方法
Cloudflareへアプリをデプロイする場合、
などがありました。
今回はCLIでさくっとデプロイしたいことから、 C3
もしくは Wrangler
を使うことになりそうでした。
次に、C3 と Wrangler の違いを調べたところ、公式ドキュメントに
The Cloudflare Developer Platform ecosystem has two command-line interfaces (CLI):
- C3: To create new projects.
- Wrangler: To build and deploy your projects.
C3 & Wrangler · Build applications with Cloudflare Workers · Learning paths
との記載がありました。
「今回のように新しく作成アプリは C3 を使えばいいのかな」と思っていたところ、Honoの著者のyusukebeさんの記事にて
C3(Create Cloudflare CLI)コマンドでもHonoを選べますが今のところそれだとWorkersのテンプレートになるのでcreate honoで。
とありました。
2023年10月の記事なので、もしかしたら現在では C3 で良いのかもしれません。
ただ、Cloudflare に詳しくないこともあり、今回は以下の方法でアプリの作成とデプロイを行うことにしました。
npm create hono@latest
のcloudflare-pages
テンプレートでアプリを作成- デプロイだけ
Wrangler
を使う
ちなみに、 C3
でHonoテンプレートを使った直後の状態は、以下のリポジトリに置いておきました。
https://github.com/thinkAmi-sandbox/hono_app_by_cloudflare_c3-example
Viteまわりの設定がないので、 npm create hono@latest
の方が作りやすそうな印象です。
Cloudflare アカウントの作成
事前に作成しておきます。
また、2FAも設定しておきます。
アプリの作成
Hono + React + Chart.js + TanStack Router + TanStack Query なアプリを実装
まずは、以前の記事のアプリを作り、Cloudflare にデプロイするところまでやってみます。
Hono + React + Chart.js + TanStack Router + TanStack Query を使って、Hono製APIのレスポンスをPie chartとして表示してみた - メモ的な思考的な
Hono のセットアップ
今回は thinkami-react-hono-d1
というアプリ名でHonoをセットアップしました。
テンプレートは cloudflare-pages
を選んでいます。
$ npm create hono@latest Need to install the following packages: create-hono@0.7.1 Ok to proceed? (y) create-hono version 0.7.1 ? Target directory thinkami-react-hono-d1 ? Which template do you want to use? cloudflare-pages ✔ Cloning the template ? Do you want to install project dependencies? yes ? Which package manager do you want to use? npm ✔ Installing project dependencies 🎉 Copied project files Get started with: cd thinkami-react-hono-d1
必要なライブラリをインストール
Reactまわりです。
$ npm i react react-dom $ npm i -D @types/react @types/react-dom
Chart.jsまわりです。
$ npm i chart.js react-chartjs-2
TanStack Router まわりは以下の3つに関係するものを入れます。
- TanStack Router本体
- DevTool
- Vite plugin
- Router CLI
なお、現在はバージョンが進んでいるので問題ないですが、バージョン 1.31.9
ではうまく動作しません。当初気づかず、時間を溶かしました。。。
A component suspended while responding to synchronous input in version 1.31.9 · Issue #1554 · TanStack/router
$ npm i @tanstack/react-router @tanstack/router-vite-plugin @tanstack/router-devtools @tanstack/router-cli
TanStack Query まわりです。
$ npm i @tanstack/react-query
以上で、ひとまずインストールは終わりです。
実装する上で悩んだこと
ここでの実装は、以前の記事の実装を移植しただけになります。
そのため、ここではコミットだけ置いておきます。
https://github.com/thinkAmi-sandbox/react_hono_with_cloudflare_pages_d1-example/commit/a90dc6a6002cac9b5bc300187485266dbc460d56
ただ、Cloudflareへデプロイするにあたり、自分が悩んだことをまとめておきます。
サーバ側のファイル名は src/index.tsx のままにする
今回、フロントエンドまわりのファイルは client
ディレクトリの中に入れました。
そこで、バックエンドも server
みたいなディレクトリに入れてもよいのかも...と考えて試してみたところ、
- 522 エラーになる
- 「Nothing is here yet. If the project exists, it may not be ready yet. Please check back later.」が出続ける
となってしまい、うまくいきませんでした。
そのため、サーバ側のファイルは src/index.tsx
のままにしてあります。
TanStack Router で Code Splitting を使う場合は、ビルド後のファイルは assets へ出力しない
TanStack RouterでCode Splittingを使う場合、suffixを .lazy.tsx
とすると容易に実現できます。
Code Splitting | TanStack Router React Docs
ただ、以前の記事の rollupOptions
の設定だと、 .lazy.tsx
のファイルはビルド後は assets
ディレクトリへ出力されます。
そして、 assets
ディレクトリに出力したままデプロイすると、動かした時に
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html
というエラーが出てしまい動作しません。
そこで、今回は rollupOptions に chunkFileNames
を設定し、 static
ディレクトリへ出力するようにしました。
- javascript - Vite - change ouput directory of assets - Stack Overflow
- output.chunkFileNames | Configuration Options | Rollup
if (mode === 'client') { return { build: { rollupOptions: { input: './src/client/main.tsx', output: { entryFileNames: 'static/client.js', chunkFileNames: 'static/[name]-[hash].js', // これ } } }, // ... } } else {
defineConfigのサーバ側の plugin には pages も入れる
最初、うっかり plugin
に pages()
を入れ忘れてしまい、
error during build: RollupError: Could not resolve entry module "index.html".
というエラーを出し続けてしまいました。
そこで hono-spa-react
リポジトリと差分を見ていたところ、この場所に pages()
が不足していると気づきました。
https://github.com/yusukebe/hono-spa-react/commit/1f71afde1aeaef4d998247577cddb98761b21a54#diff-6a3b01ba97829c9566ef2d8dc466ffcffb4bdac08706d3d6319e42e0aa6890ddR23
TanStack Routerの Devtools は development のときだけ使うようにする
Cloudflareにデプロイした後も TanStack Router の Devtools が表示されていたので、おや?となりました。
そこで、TanStack Routerの公式ドキュメントを見て、 developement のときだけ動くようにしました。
Only importing and using Devtools in Development | Devtools | TanStack Router React Docs
TanStack Router の routeTree.gen.ts は、初回は CLI で作る
TanStack Router のルーティングが動いていないので調べたところ、 routeTree.gen.ts
が無いことに気づきました。
そのため、Router CLI で tsr generate
を実行し、ファイルを生成しました。
Router CLI | File-Based Routes | TanStack Router React Docs
ローカルでの動作確認
npm run dev
したところ、Pie chartが表示されました。
WranglerでCloudflare Pagesへデプロイ
続いて、vite build --mode client && vite build && wrangler pages deploy dist
(package.jsonでは npm run deploy
) にて Cloudflare Pagesへデプロイします。
デプロイする際、TanStack Queryのワーニングが出ますが、今回はいったん置いておきます。
$ npm run deploy > deploy > vite build --mode client && vite build && wrangler pages deploy dist ♻️ Generating routes... ✅ Processed routes in 144ms vite v5.2.11 building for client... node_modules/@tanstack/react-query/build/modern/useQueries.js (1:0): Module level directives cause errors when bundled, "use client" in "node_modules/@tanstack/react-query/build/modern/useQueries.js" was ignored. ... ✓ 125 modules transformed. dist/static/index.lazy-PW47UpKj.js 0.25 kB │ gzip: 0.21 kB dist/static/link-CzmguEkQ.js 2.90 kB │ gzip: 1.48 kB dist/static/chart.lazy-BYg6oVDi.js 174.51 kB │ gzip: 60.61 kB dist/static/client.js 212.83 kB │ gzip: 67.83 kB ✓ built in 1.17s vite v5.2.11 building SSR bundle for production... ✓ 24 modules transformed. dist/_worker.js 22.46 kB ✓ built in 160ms
初回デプロイなこともあり、Cloudflare Pages のプロジェクト作成が必要になったので、質問に答えていきます。
今回 production branch name は main
にしました。
The project you specified does not exist: "thinkami-react-hono-d1". Would you like to create it?" ❯ Create a new project ✔ Enter the production branch name: … main ✨ Successfully created the 'thinkami-react-hono-d1' project. ▲ [WARNING] Warning: Your working directory is a git repo and has uncommitted changes To silence this warning, pass in --commit-dirty=true 🌏 Uploading... (5/5) ✨ Success! Uploaded 5 files (2.53 sec) ✨ Compiled Worker successfully ✨ Uploading Worker bundle ✨ Deployment complete! Take a peek over at https://f21b42fb.thinkami-react-hono-d1.pages.dev
Cloudflare Pages で動作確認
払い出されたURLにアクセスしたところ、ローカルと同じように表示されました。
こちらは /
へのアクセスしたときの表示です。TanStack RouterのDevtoolsは非表示になっています。
こちらは /chart
の表示です。こちらも良さそうです。
Cloudflare D1のデータを表示するようアプリを修正
以前作ったアプリがCloudflare Pages上で動くようになったため、次はCloudflare D1のデータを表示できるようアプリを修正します。
WranglerでCloudflare D1をセットアップ
D1のドキュメントに従い、 wrangler d1 create
で作成します。
https://developers.cloudflare.com/workers/wrangler/commands/#create
今回のD1は my-d1
という名前にします。
$ wrangler d1 create my-d1 ⛅️ wrangler 3.53.1 ------------------- ✅ Successfully created DB 'my-d1' in region APAC Created your new D1 database. [[d1_databases]] binding = "DB" # i.e. available in your Worker on env.DB database_name = "my-d1" database_id = "73b700ec-6f6e-40f3-b01f-bb3fd5864b7c"
次に、Wranglerの実行ログに出たD1の情報を wrangler.toml
の末尾に追記します。
name = "thinkami-react-hono-d1" pages_build_output_dir = "./dist" # 以下を追記 [[d1_databases]] binding = "DB" # i.e. available in your Worker on env.DB database_name = "my-d1" database_id = "73b700ec-6f6e-40f3-b01f-bb3fd5864b7c"
Drizzle ORMをインストール
Drizzle ORM の公式ドキュメントにある Cloudflare D1 の項目に従い、Drizzle ORMとKitをインストールします。
Cloudflare D1 | Drizzle ORM - SQLite
$ npm i drizzle-orm $ npm i -D drizzle-kit
drizzle.config.ts の作成
Cloudflare D1では、マイグレーションはCloudflare D1が提供しているものを使います。
Migrations · Cloudflare D1 docs
そこで、Drizzle Kitを使って migrations
ディレクトリにマイグレーションファイルを出力できれば良さそうです。
ただ、そのディレクトリはDrizzle Kitのデフォルトとは異なることから、 drizzle.config.ts
にて、マイグレーションファイルの出力先 out
を指定しておきます。
Migrations folder | Drizzle ORM - Configuration
なお、今回もテーブルごとのスキーマファイルにすることから、 schema
も設定しておきます。
Schema files paths | Drizzle ORM - Configuration
import type {Config} from "drizzle-kit" export default { schema: "./src/schema/*", out: "./migrations", } satisfies Config
Drizzle ORM向けのスキーマを作成
今回は Pie chart のデータソースを D1 に入れておきます。そこで、 colors
と apples
の2つのテーブルを用意します。
まずは src/schema/colors.ts
です。IDと色名だけ保持します。
import {integer, sqliteTable, text} from "drizzle-orm/sqlite-core"; export const colors = sqliteTable("colors", { id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }), name: text("name"), })
続いて、 src/schema/apples.ts
です。こちらでは colors
テーブルへの外部キー制約を付けています。
import {integer, sqliteTable, text} from "drizzle-orm/sqlite-core"; import {colors} from "./colors"; export const apples = sqliteTable("apples", { id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }), name: text("name"), colorId: integer("color_id").references(() => colors.id), quantity: integer("quantity") })
Drizzle Kitでマイグレーションファイルを自動生成
D1はSQLiteであることから、Drizzle Kitの drizzle-kit generate:sqlite
を使ってD1向けのマイグレーションファイルを生成します。
Generate migrations | Drizzle ORM - List of commands
$ drizzle-kit generate:sqlite drizzle-kit: v0.20.17 drizzle-orm: v0.30.10 No config path provided, using default 'drizzle.config.ts' Reading config file 'path/to/drizzle.config.ts' 1 tables publishers 2 columns 0 indexes 0 fks [✓] Your SQL migration file ➜ migrations/0000_tidy_phantom_reporter.sql 🚀
migrations
ディレクトリの中に1つのマイグレーションファイルができました。
$ tree migrations/ migrations/ ├── 0000_tidy_phantom_reporter.sql └── meta ├── 0000_snapshot.json └── _journal.json 1 directory, 3 files
0000_tidy_phantom_reporter.sql
を開くと、2つのスキーマに対するSQLが記載されています。
CREATE TABLE `apples` ( `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `name` text, `color_id` integer, `quantity` integer, FOREIGN KEY (`color_id`) REFERENCES `colors`(`id`) ON UPDATE no action ON DELETE no action ); --> statement-breakpoint CREATE TABLE `colors` ( `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `name` text );
マイグレーションの実行
前回の記事では Drizzle の migrate()
を使ってマイグレーションしました。
一方、Cloudflare D1で マイグレーションをするには、Wranglerの apply
コマンドを使います。
migrations apply | Commands - Wrangler · Cloudflare Workers docs
マイグレーション適用状況を確認
マイグレーションする前に、適用状況を確認します。Wranglerでは wrangler d1 migrations list
を使えば良さそうです。
migrations list | Commands - Wrangler · Cloudflare Workers docs
まずはローカル環境から確認します。ローカルに対して実行する場合は --local
を付与します。
$ wrangler d1 migrations list my-d1 --local ⛅️ wrangler 3.53.1 ------------------- Migrations to be applied: ┌────────────────────────────────┐ │ Name │ ├────────────────────────────────┤ │ 0000_tidy_phantom_reporter.sql │ └────────────────────────────────┘
続いて本番のD1を確認します。
ドキュメントには書いてありませんが、本番のD1を確認するには --remote
オプションが必要です。
$ wrangler d1 migrations list my-d1 --remote ⛅️ wrangler 3.53.1 ------------------- Migrations to be applied: ┌────────────────────────────────┐ │ Name │ ├────────────────────────────────┤ │ 0000_tidy_phantom_reporter.sql │ └────────────────────────────────┘
ローカル環境に対し、マイグレーションを実行
続いてマイグレーションを migrations apply
で実行します。
migrations apply | Commands - Wrangler · Cloudflare Workers docs
ひとまずローカルに対してのみ実行しますので、 --local
オプションを付けておきます。
$ wrangler d1 migrations apply my-d1 --local ⛅️ wrangler 3.53.1 ------------------- Migrations to be applied: ┌────────────────────────────────┐ │ name │ ├────────────────────────────────┤ │ 0000_tidy_phantom_reporter.sql │ └────────────────────────────────┘ ✔ About to apply 1 migration(s) Your database may not be available to serve requests during the migration, continue? … yes 🌀 Mapping SQL input into an array of statements 🌀 Executing on local database my-d1 (73b700ec-6f6e-40f3-b01f-bb3fd5864b7c) from .wrangler/state/v3/d1: 🌀 To execute on your remote database, add a --remote flag to your wrangler command. ┌────────────────────────────────┬────────┐ │ name │ status │ ├────────────────────────────────┼────────┤ │ 0000_tidy_phantom_reporter.sql │ ✅ │ └────────────────────────────────┴────────┘
マイグレーションが成功し、ローカルの .wrangler
ディレクトリの中にSQLiteのファイルが生成されました。
生成された SQLite ファイルを確認すると、 colors
と apples
の2テーブルがありました。
念のため、本番環境のD1のマイグレーション適用状況も確認すると、まだ未適用でした。
$ wrangler d1 migrations list my-d1 --remote ⛅️ wrangler 3.53.1 ------------------- Migrations to be applied: ┌────────────────────────────────┐ │ Name │ ├────────────────────────────────┤ │ 0000_tidy_phantom_reporter.sql │ └────────────────────────────────┘
今回は、ローカル環境での動作確認完了後、本番環境をマイグレーションすることにします。
ローカル環境に対し、seedデータを投入
前回の記事では、Drizzle ORMにて、自分で seed 投入用プログラムを作りました。
一方、D1の場合はseed用の .sql
ファイルを用意することで Wrangler から投入できるようです。
Build a Staff Directory Application · Cloudflare D1 docs
そこで、ルートに seeds
ディレクトリを作り、その中に seed.sql
を作成します。
INSERT INTO colors (name) VALUES ('firebrick'), ('gold'), ('pink'), ('mediumseagreen'); INSERT INTO apples (name, color_id, quantity) VALUES ('奥州ロマン', 1, 1), ('シナノゴールド', 2, 5), ('ピンクレディ', 3, 3), ('ブラムリー', 4, 2);
続いて、ローカル環境のSQLiteへseedデータを投入します。
$ wrangler d1 execute my-d1 --local --file=./seeds/seed.sql ⛅️ wrangler 3.53.1 ------------------- 🌀 Mapping SQL input into an array of statements 🌀 Executing on local database my-d1 (73b700ec-6f6e-40f3-b01f-bb3fd5864b7c) from .wrangler/state/v3/d1: 🌀 To execute on your remote database, add a --remote flag to your wrangler command.
ローカル環境の各テーブルを見てみると、seedデータが存在していました。
colorsテーブル
applesテーブル
name
列のフォントが怪しいですが、今はこのままで進めます。
Honoアプリからローカル環境のD1へ接続できるよう vite.config.ts を修正
ローカル環境のD1へ接続する方法を調査
Cloudflare Pages では、Bindingsという機能を使ってPagesからD1へのアクセスが可能になるようです。
D1 databases | Bindings · Cloudflare Pages docs
また、ローカル開発時に Wrangler の開発サーバを使っていれば、いい感じにローカルのSQLiteへ接続できるようです。
Interact with your D1 databases locally | Bindings · Cloudflare Pages docs
ただ、今回のHonoアプリはViteの開発サーバで動作しています。
どうすればいいのだろうと思ったところ、yusukebeさんの記事に情報がありました。
「Bindingsはどうするの?」。実はローカルに限っては対応しています。というかPagesはwrangler --remoteできませんので、Pagesでできる範疇はすべてできます。
例えば、KVなんかこのようにvite.config.tsを編集すると使えるようになります。
(略)
D1もちょっと工夫すれば、ローカルのSQLiteを参照するようにして使えます。
デプロイ先で使いたければ、ダッシュボードからBindingsを有効にすればOKです。
次に、ローカル環境のD1向けの設定を調べたところ、HonoXのissueに情報がありました。
Now, we can use the new API
getPlatformProxy()
in Wrangler. This will automatically read variables from wrangler.toml without having to write Bindings in vite.config.ts. The vite.config.ts can be written simply as follows:
https://github.com/honojs/honox/issues/39#issuecomment-1955716487
issueのコメントからリンクされていたCloudflare Workersのドキュメントを読むと、 getPlatformProxy()
では D1 database bindings もサポートされていました。
getPlatformProxy | API · Cloudflare Workers docs
以上より、ローカル環境のD1へ接続するには getPlatformProxy()
を使えば良さそうと分かりました。
vite.config.ts を修正
HonoXのissueのコメントに従い、 vite.config.ts
のうち、サーバ側の設定の devServer
に対し、
- env
- plugins
の設定を追加しました。
import pages from '@hono/vite-cloudflare-pages' import devServer from '@hono/vite-dev-server' import {defineConfig} from 'vite' import {TanStackRouterVite} from "@tanstack/router-vite-plugin" import {getPlatformProxy} from "wrangler"; export default defineConfig(async ({ mode }) => { const { env, dispose } = await getPlatformProxy() if (mode === 'client') { // ... } else { return { // ... plugins: [ // ... // env と plugins を設定 devServer({ entry: 'src/index.tsx', env: env, plugins: [ { onServerClose: dispose } ] }) ] } } })
HonoでD1の中身を返すよう修正
今まではAPIのエンドポイント /api/apples
にてハードコーディングした値を返しています。
そこで、ハードコーディングではなくD1の値を返すよう修正します。
今回は、 apples テーブルと colors テーブルを INNER JOINした結果を返すよう修正します。
ただ、現在のD1 + Drizzle ORMの組み合わせだとバグがあることからメモを残しておきます。
D1 + Drizzle ORMを使う場合、Join系にバグがあるので回避
apples テーブルと colors テーブルをINNER JOINしようと、Drizzle ORMの innerJoin()
を使って書きました。
Joins [SQL] | Drizzle ORM - Joins
const results = await db.select({ name: apples.name, color: colors.name, quantity: apples.quantity, }).from(apples).innerJoin(colors, eq(apples.colorId, colors.id)).all() console.log(results)
INNER JOINした結果を確認したところ、「name
列に色名が設定されている」など意図しないデータができていました。
[ { name: 'firebrick', color: 1, quantity: undefined }, { name: 'gold', color: 5, quantity: undefined }, { name: 'pink', color: 3, quantity: undefined }, { name: 'mediumseagreen', color: 2, quantity: undefined } ]
SQLがおかしいのかなと思い、all()
の代わりに toSQL()
メソッドで発行されるSQLを確認してみます。
Printing SQL query | Drizzle ORM - Goodies
const p = await db.select({ name: apples.name, color: colors.name, quantity: apples.quantity, }).from(apples).innerJoin(colors, eq(apples.colorId, colors.id)).toSQL() console.log(p)
すると、コンソールには以下が出力されました。SQLは正しそうです。
{ sql: 'select "apples"."name", "colors"."name", "apples"."quantity" from "apples" inner join "colors" on "apples"."color_id" = "colors"."id"', params: [] }
次にGithubのissueを見たところ、すでにissueが上がっていました。約1年前のissueですが、まだOpenなようです。
- [BUG]: Broken shifted columns with leftJoin and same column name (on D1) · Issue #555 · drizzle-team/drizzle-orm
- [BUG]: Shifting column values when querying through junction table · Issue #838 · drizzle-team/drizzle-orm
- 🐛 BUG: when trying to left join 2 tables that have same name columns - second column is not returned · Issue #3160 · cloudflare/workers-sdk
この問題のワークアラウンドについては、上記issueや以下の記事に書いてありました。今回は SELECT する時に別名を付ける方針でいくことにしました。
Cloudflare D1とDrizzleの組み合わせてで困ったこと
Drizzle ORMで列に別名を付けるためには、 as<T>()
が使えるのでためしてみます。
sql``.as
const results = await db.select({ name: apples.name, color: sql`${colors.name}`.as('colorName'), // 別名を付ける quantity: apples.quantity, }).from(apples).innerJoin(colors, eq(apples.colorId, colors.id)).all() console.log(results)
ログを確認すると、今度は期待通りの値が取得できていました。
なお、文字に豆腐っぽいのが出ていますが、ここでは気にしないことにします。
[ { name: '奥州ロマン', color: 'firebrick', quantity: 1 }, { name: 'シナノゴールド', color: 'gold', quantity: 5 }, { name: 'ピンクレディ', color: 'pink', quantity: 3 }, { name: 'ブラムリー', color: 'mediumseagreen', quantity: 2 } ]
Hono APIのエンドポイントを修正
上記を踏まえ、APIエンドポイント /api/apples
にて D1 よりデータを取得・変形して、呼び出し元へ返すよう修正します。
ちなみに、てきとうな実装になってますが、今回はサンプルコード的な実装なので気にしないことにします。
あと、テーブルの値をそのまま返してしまうとハードコーディングしているときと同じ結果になることに気づき、 quantity の値を +1
して返すようにしています。
const appleRoute = app.get('/api/apples', async (c) => { const db = drizzle(c.env.DB) const results = await db.select({ name: apples.name, color: sql`${colors.name}`.as('colorName'), quantity: apples.quantity, }).from(apples).innerJoin(colors, eq(apples.colorId, colors.id)).all() console.log(results) const labels = results.map(r => r.name) ?? [] // テーブルから値を取得していることを分かりやすくするため、テーブルの値 + 1 を設定 const quantities = results.map(r => r.quantity ? r.quantity + 1 : 0) ?? [] const colorNames = results.map(r => r.color) ?? [] return c.json({ labels: labels, datasets: [ { label: '購入数', data: quantities, backgroundColor: colorNames, borderColor: colorNames, borderWidth: 1 } ] }) })
ローカル環境での動作確認
実装が終わったので、ローカル環境で動作確認してみます。
Pie chartが表示され、かつ、テーブルの値が +1
されていました。良さそうです。
本番環境のD1をマイグレーション
本番環境のD1をマイグレーションするため、 d1 migrations apply
を --remote
オプション付きで実行します。
migrations apply | Commands - Wrangler · Cloudflare Workers docs
$ wrangler d1 migrations apply my-d1 --remote ⛅️ wrangler 3.53.1 ------------------- Migrations to be applied: ┌────────────────────────────────┐ │ name │ ├────────────────────────────────┤ │ 0000_tidy_phantom_reporter.sql │ └────────────────────────────────┘ ✔ About to apply 1 migration(s) Your database may not be available to serve requests during the migration, continue? … yes 🌀 Mapping SQL input into an array of statements 🌀 Parsing 3 statements 🌀 Executing on remote database my-d1 (73b700ec-6f6e-40f3-b01f-bb3fd5864b7c): 🌀 To execute on your local development database, remove the --remote flag from your wrangler command. 🚣 Executed 3 commands in 0.7013ms ┌────────────────────────────────┬────────┐ │ name │ status │ ├────────────────────────────────┼────────┤ │ 0000_tidy_phantom_reporter.sql │ ✅ │ └────────────────────────────────┴────────┘
デプロイして動作確認
あとは Cloudflare Pages へデプロイして動作確認するだけ...と思いきや、悩んだことがあったのでメモしておきます。
ビルドがハングするので、ビルドとデプロイを分離し、デプロイ
今まで同じように npm run deploy
したところ、フロントエンドのビルドが終わったところでハングしたような挙動になりました。
$ npm run deploy > deploy > vite build --mode client && vite build && wrangler pages deploy dist ♻️ Generating routes... ✅ Processed routes in 163ms ... dist/static/client.js 212.83 kB │ gzip: 67.83 kB ✓ built in 1.16s (ここでずっと止まっている)
事例がないかを調べましたが見当たりませんでした。
そこで、表示を見る限り client.js
のビルドは成功している感じだったため、ビルド単体で実行してみました。
しかし、それでもまた同じところでハングしてしまいました。
次に、フロントエンドとバックエンドのビルドを分けて実行してみました。
"scripts": { "build:frontend": "vite build --mode client", "build:backend": "vite build", "deploy": "wrangler pages deploy dist", },
すると、フロントエンドはもちろん、バックエンドを各単体でビルドした場合でも
- 表示はハングする
- ビルドの成果物はきちんとできてそう
となりました。
なお、バックエンドの場合は、以下のあたりで止まります。
dist/_worker.js 76.74 kB ✓ built in 365ms (ここでずっと止まっている)
そこで、ビルドの成果物さえできていればとりあえず問題ないだろうと考え、
- (1)
build:frontend
でフロントエンドをビルド- ハングするが、ビルドの成果物ができたところでキャンセルする
- (2)
build:backend
でバックエンドをビルド- ハングするが、ビルドの成果物ができたところでキャンセルする
- (3)
npm run deploy
でデプロイ
の順に実行したところ、問題なく完了しました。
seed投入前の本番環境で動作確認
この時点の本番環境は
- D1のマイグレーションは成功
- seed は投入していないので、各テーブルの中身は空
- デプロイも(いちおう)成功
という状態です。
そこで、動作確認したところ、chartのページはエラーは出ないものの Pie chart は表示されていませんでした。
seedは投入していないので、この表示は正しいという認識です。
本番環境のD1へ seed を投入
次に、 --remote
オプションを付けて、本番環境のD1へ seed を投入します。
execute | Commands - Wrangler · Cloudflare Workers docs
$ wrangler d1 execute my-d1 --remote --file=./seeds/seed.sql ⛅️ wrangler 3.53.1 ------------------- 🌀 Mapping SQL input into an array of statements 🌀 Parsing 2 statements 🌀 Executing on remote database my-d1 (73b700ec-6f6e-40f3-b01f-bb3fd5864b7c): 🌀 To execute on your local development database, remove the --remote flag from your wrangler command. 🚣 Executed 2 commands in 0.3165ms
seed投入後の本番環境で動作確認
ブラウザをリロードしたところ、Pie chartが表示されました。
これで、Hono + React + TanStack Router + TanStack Query + Chart.js + Drizzle ORMなアプリを、Cloudflare Pages と D1 に乗せることができました。
ソースコード
Githubに上げました。
https://github.com/thinkAmi-sandbox/react_hono_with_cloudflare_pages_d1-example
Cloudflare Pagesのデプロイブランチを main
にしたこともあり、今回はプルリクを作りませんでした。