🐙

Remix + Cloudflare Pages + D1 の最初の一歩目

2023/04/29に公開

最近 Cloudflare のサービスを耳にすることが増えました。
その中で Clouflare D1 に特に興味を持ったので軽く触ってみることにしました。

今回は使ってみたかった Remix と合わせて、 Remix + Cloudflare Pages + D1 の最初の一歩目を紹介できればと思います。

本当に軽くしか触れてませんが、作ったものは下記のリポジトリにあります。
https://github.com/creamstew/remix-cloudflare-pages-d1

準備

最初に下記 2 つの準備が必要です。

  • Cloudflare のアカウントを取得する
  • Cloudflare Workers 等の CLI ツールである Wrangler をインストールしておく

Cloudflare のアカウントは下記から取得しましょう。
https://www.cloudflare.com/ja-jp/

Wrangler は下記のコマンドでインストールできます。

npm install -g wrangler

Remix プロジェクトを作成

下記のコマンドでプロジェクトを作成します。

npx create-remix@latest

プロジェクト名を入力すると、デプロイ方法を選択するよう促されます。
Cloudflare Pages を選択しましょう!

$ cd <YOUR_PROJECT>
npm run dev

http://127.0.0.1:8788 にアクセスすると下記の画面が出ると思います。

これで Remix プロジェクト作成完了です。

ここまでの流れや Cloudflare Pages へのデプロイ方法なども下記に載ってるのでこちらを見てください。
https://developers.cloudflare.com/pages/framework-guides/deploy-a-remix-site/

Cloudflare D1 の作成 ~ バインディング

D1 のデータベースを作成するのは簡単です。

wrangler d1 create <DATABASE_NAME>

これだけです。これでローカルに D1 のデータベースができます。

データベースを作成すると、Worker の設定ファイルである wrangler.toml に下記の設定が追加されるはずです。

wrangler.toml
[[ d1_databases ]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "<DATABASE_NAME>"
database_id = "<UUID>"

この設定で D1 データベースと Worker のバインディング(連携)が可能になります。
https://developers.cloudflare.com/workers/platform/bindings/

クエリ実行

データベースを作ったので次はテーブルとデータを少し作成します。
Remix のプロジェクト直下に schema.sql というファイルを作ります。

schema.sql
DROP TABLE IF EXISTS Customers;
CREATE TABLE Customers (CustomerID INT, CompanyName TEXT, ContactName TEXT, PRIMARY KEY (`CustomerID`));
INSERT INTO Customers (CustomerID, CompanyName, ContactName) VALUES (1, 'Alfreds Futterkiste', 'Maria Anders'), (4, 'Around the Horn', 'Thomas Hardy'), (11, 'Bs Beverages', 'Victoria Ashworth'), (13, 'Bs Beverages', 'Random Name');

作成した sql ファイルをローカルで実行します。

wrangler d1 execute <DATABASE_NAME> --local --file=./schema.sql

ちゃんと実行できていれば、下記のような SELECT 文を投げてみると結果が返ってきます。

wrangler d1 execute <DATABASE_NAME> --local --command='SELECT * FROM Customers'

┌────────────┬─────────────────────┬───────────────────┐
│ CustomerID │ CompanyName         │ ContactName       │
├────────────┼─────────────────────┼───────────────────┤
│ 1          │ Alfreds Futterkiste │ Maria Anders      │
├────────────┼─────────────────────┼───────────────────┤
│ 4          │ Around the Horn     │ Thomas Hardy      │
├────────────┼─────────────────────┼───────────────────┤
│ 11         │ Bs Beverages        │ Victoria Ashworth │
├────────────┼─────────────────────┼───────────────────┤
│ 13         │ Bs Beverages        │ Random Name       │
└────────────┴─────────────────────┴───────────────────┘

Remix でデータ表示

クエリが実行できることを確認できたので、Remix で D1 にアクセスして取得したデータを表示していきます。
まず package.json の設定を一部書き換えます。

package.json
< wrangler pages dev ./public
---
> wrangler pages dev ./public --local --persist

persist オプションを付けることで .wrangler/state サブディレクトリにデータが永続化されます。

次に Remix の routes/_index.tsx ファイルを編集します。

変更前はこのようになってるはずです。

routes/_index.tsx
import type { V2_MetaFunction } from "@remix-run/react";

export const meta: V2_MetaFunction = () => {
  return [{ title: "New Remix App" }];
};

export default function Index() {
  return (
    <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
      <h1>Welcome to Remix</h1>
      <ul>
        <li>
          <a
            target="_blank"
            href="https://remix.run/tutorials/blog"
            rel="noreferrer"
          >
            15m Quickstart Blog Tutorial
          </a>
        </li>
        <li>
          <a
            target="_blank"
            href="https://remix.run/tutorials/jokes"
            rel="noreferrer"
          >
            Deep Dive Jokes App Tutorial
          </a>
        </li>
        <li>
          <a target="_blank" href="https://remix.run/docs" rel="noreferrer">
            Remix Docs
          </a>
        </li>
      </ul>
    </div>
  );
}

このファイルを下記のように編集します。

routes/_index.tsx
import type { LoaderArgs } from "@remix-run/cloudflare";
import { json } from "@remix-run/cloudflare";
import { useLoaderData } from "@remix-run/react";

type Customer = {
  CustomerID: number;
  CompanyName: string;
  ContactName: string;
};

export const loader = async ({ context }: LoaderArgs) => {
  const db = context.DB as D1Database;

  const { results } = await db
    .prepare("SELECT * FROM Customers")
    .all<Customer>();

  return json({
    customers: results ?? [],
  });
};

export default function Index() {
  const { customers } = useLoaderData<typeof loader>();

  return (
    <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
      <h1>Welcome to Remix</h1>
      <ul>
        {customers.map((customer) => (
          <li key={customer.CustomerID}>
            {customer.CompanyName}, {customer.ContactName}
          </li>
        ))}
      </ul>
    </div>
  );
}

編集後に

npm run dev

でもう一度 http://127.0.0.1:8788 にアクセスすると下記の画面が出ると思います。

D1 のデータを取得して表示出来てることが確認できるはずです。

編集後のコードに関して軽く説明しておきます。

type Customer = {
  CustomerID: number;
  CompanyName: string;
  ContactName: string;
};

は、sql を実行して作成したテーブルの内容を表しています。

export const loader = async ({ context }: LoaderArgs) => {
  const db = context.DB as D1Database;

  const { results } = await db
    .prepare("SELECT * FROM Customers")
    .all<Customer>();

  return json({
    customers: results ?? [],
  });
};

loader でバインディングを利用しています。
context から

wrangler.toml
[[ d1_databases ]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "${DATABASE_NAME}"
database_id = "${DATABASE_ID}"

の DB に直接アクセスしています。
あとは sql でデータを取得して map で展開してるだけですね!

これでローカルでの最初の一歩目は終了です。

Cloudflare Pages での連携

こちらについても軽く触れておきます。

Cloudflare Pages へのデプロイ方法は下記をご覧ください。
https://developers.cloudflare.com/pages/framework-guides/deploy-a-remix-site/

やることはシンプルで先ほどローカルで流した sql を本番環境でも流します。

wrangler d1 execute <DATABASE_NAME> --file=./schema.sql

本番環境に sql 流せてる確認してください。

wrangler d1 execute <DATABASE_NAME> --command='SELECT * FROM Customers'

確認できたら、Cloudflare Pages > 設定 > Functions を開き変数名 DB に作成したデータベースを選択したらバインディング完了です。

これで Cloudflare Pages でも D1 のデータを表示できているはずです。

最後に補足として、

wrangler.toml は .gitignore に含まれてません。
そのため database_name や database_id などの情報がそのままだとむき出しになります。

そのような場合は .dev.vars に環境変数の情報を記載してその環境変数を wrangler.toml 内で式展開させると、
セキュアな情報をリポジトリに含まずに済みます。

.dev.vars
DATABASE_NAME = <DATABASE_NAME>
DATABASE_ID = <DATABASE_ID>
wrangler.toml
[[ d1_databases ]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "${DATABASE_NAME}"
database_id = "${DATABASE_ID}"

https://developers.cloudflare.com/workers/platform/environment-variables/

以上になります。
D1 意外と簡単に触れました。まだベータ版だと思いますが、D1 のこれからに期待してます!

Discussion