Hono + React + TanStack Router + TanStack Query + Chart.js + Drizzle ORMなアプリを、Cloudflare Pages と D1 に乗せてみた

少し前から、Hono + React + TanStack Router + TanStack Query + Chart.js + Drizzle ORM あたりをさわってきました。

 
今まではローカルのみでアプリ開発していたため、次はどこかにデプロイしたくなりました。

そこで、以前から気になっていた Cloudflare Pages と 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 を動かす
  • Hono の中で Drizzle ORM を使い、D1からデータを取得する
  • フロントエンドのルーティングは TanStack Router を使う

という構成とします。このアプリを Cloudflare Pages にデプロイします。

 

Cloudflare Pages へのデプロイ方法

Cloudflareへアプリをデプロイする場合、

などがありました。

今回はCLIでさくっとデプロイしたいことから、 C3 もしくは Wrangler を使うことになりそうでした。

 
次に、C3 と Wrangler の違いを調べたところ、公式ドキュメントに

CLI

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で。

Honoの新しいCloudflare Pagesスターターについて

とありました。

 
2023年10月の記事なので、もしかしたら現在では C3 で良いのかもしれません。

ただ、Cloudflare に詳しくないこともあり、今回は以下の方法でアプリの作成とデプロイを行うことにしました。

  • npm create hono@latestcloudflare-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つに関係するものを入れます。

なお、現在はバージョンが進んでいるので問題ないですが、バージョン 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 ディレクトリへ出力するようにしました。

  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 も入れる

最初、うっかり pluginpages() を入れ忘れてしまい、

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 CLItsr 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 に入れておきます。そこで、 colorsapples の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で マイグレーションをするには、Wranglerapply コマンドを使います。
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 ファイルを確認すると、 colorsapples の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です。

 

Bindings | Honoの新しいCloudflare Pagesスターターについて

 
次に、ローカル環境の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なようです。

 
この問題のワークアラウンドについては、上記issueや以下の記事に書いてありました。今回は SELECT する時に別名を付ける方針でいくことにしました。
Cloudflare D1とDrizzleの組み合わせてで困ったこと

 
Drizzle ORMで列に別名を付けるためには、 as<T>() が使えるのでためしてみます。
sql``.as() | Drizzle ORM - Magic sql`` operator

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 にしたこともあり、今回はプルリクを作りませんでした。