Cloudflare Pages + Remix(v2.8) + Drizzle ORM + D1 お試し
初手 Cloudflare + Remixの雛形の作成
npm create cloudflare@latest my-remix-app -- --framework=remix
cf-remix-drizzle-d1
schema定義
まずなにはともあれschemaファイルを定義します。これはsqlite系のDBであれば共通です。
import { sql } from "drizzle-orm";
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
export const users = sqliteTable('users', {
id: text('id').primaryKey(),
email: text('email').unique(),
name: text('name').notNull(),
customerId: text('customerId').unique(),
createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),
updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(),
});
migrationファイルの生成
これを元にmigrationファイルを作成します。
npm add -D drizzle-kit drizzle-orm
drizzle-kitに食わせるconfigファイルを置きます。プロジェクトルートです。out
の場所にmigraitonファイルが生成されます。
export default {
dialect: "sqlite",
schema: "./db/schema.ts",
out: "./db/migrations",
};
ということで、schemaファイルからmigrationファイルを生成しましょう。
npx drizzle-kit generate
このようなファイルを生成されます。
sqlの中身はこんな感じになっておりschemaファイルと対応が取れていることがわかります。
CREATE TABLE `users` (
`id` text PRIMARY KEY NOT NULL,
`email` text,
`name` text NOT NULL,
`customerId` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);--> statement-breakpoint
CREATE UNIQUE INDEX `users_customerId_unique` ON `users` (`customerId`);
ここで元のschemaファイルのusersにニックネームを追加してみましょう。
export const users = sqliteTable('users', {
id: text('id').primaryKey(),
email: text('email').unique(),
name: text('name').notNull(),
+ nickname: text('nickname'),
customerId: text('customerId').unique(),
createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),
updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(),
});
この状態でもう一度generateしてみましょう。
$ npx drizzle-kit generate
drizzle-kit: v0.20.14
drizzle-orm: v0.29.4
No config path provided, using default 'drizzle.config.ts'
Reading config file '/home/kazuph/src/github.com/kazuph/cf-samples/cf-remix-drizzle-d1/drizzle.config.ts'
1 tables
users 6 columns 2 indexes 0 fks
[✓] Your SQL migration file ➜ db/migrations/0001_wild_magik.sql 🚀
新たにmigrationファイルが追加されたのがわかります。中身を見ると
ALTER TABLE users ADD `nickname` text;
となってます。
ここまででmigrationファイルを生成する流れがわかりました。
migrrateの実行
現時点ではDBの実体がないのでまずD1のDBを作成します。
npx wrangler d1 create mydb
...
[[ d1_databases ]]
binding = "DB"
database_name = "mydb"
database_id = "<YOUR_D1_ID>"
この出力を wranger.toml
に追記してください。またすでに作成している migrations_dir
も設定しています。
name = "cf-remix-drizzle-d1"
compatibility_date = "2024-03-04"
[[ d1_databases ]]
binding = "DB"
database_name = "mydb"
database_id = "<YOUR_D1_ID>"
migrations_dir = "db/migrations"
それではmigrateしてみます。
$ npx wrangler d1 migrations apply mydb --local
⛅️ wrangler 3.31.0
-------------------
Migrations to be applied:
┌────────────────────────┐
│ name │
├────────────────────────┤
│ 0000_high_red_hulk.sql │
├────────────────────────┤
│ 0001_wild_magik.sql │
└────────────────────────┘
✔ About to apply 2 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 mydb () from .wrangler/state/v3/d1:
┌────────────────────────┬────────┐
│ name │ status │
├────────────────────────┼────────┤
│ 0000_high_red_hulk.sql │ ✅ │
├────────────────────────┼────────┤
│ 0001_wild_magik.sql │ 🕒️ │
└────────────────────────┴────────┘
🌀 Mapping SQL input into an array of statements
🌀 Executing on local database mydb () from .wrangler/state/v3/d1:
┌────────────────────────┬────────┐
│ name │ status │
├────────────────────────┼────────┤
│ 0000_high_red_hulk.sql │ ✅ │
├────────────────────────┼────────┤
│ 0001_wild_magik.sql │ ✅ │
└────────────────────────┴────────┘
マイグレーションは d1_migrations
テーブルで管理しているみたいなので、そのtableがあるか確認してみましょう。
$ npx wrangler d1 execute mydb --command="select * from d1_migrations;"
⛅️ wrangler 3.31.0
-------------------
🌀 Mapping SQL input into an array of statements
🌀 Parsing 1 statements
🌀 Executing on remote database mydb ():
🌀 To execute on your local development database, pass the --local flag to 'wrangler d1 execute'
🚣 Executed 1 commands in 0.3032ms
┌────┬─────────────────────────────┬─────────────────────┐
│ id │ name │ applied_at │
├────┼─────────────────────────────┼─────────────────────┤
│ 1 │ 0000_conscious_ironclad.sql │ 2024-03-02 19:29:23 │
└────┴─────────────────────────────┴─────────────────────┘
はい、これでmigrateするところまで確認できました。
テストデータを追加する
wranglerを使ってusersを追加してみます。
npx wrangler d1 execute mydb --command="INSERT INTO users (id, email, name, nickname, customerId, created_at, updated_at) VALUES
('1', 'user@example.com', 'ユーザー太郎', 'taro', 'cust_1234', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);" --local
確認してみましょう。
$ npx wrangler d1 execute mydb --command="select * from users;" --local
⛅️ wrangler 3.31.0
-------------------
🌀 Mapping SQL input into an array of statements
🌀 Executing on local database mydb () from .wrangler/state/v3/d1:
┌────┬──────────────────┬────────┬────────────┬─────────────────────┬──────────┬─────────────────────┐
│ id │ email │ name │ customerId │ created_at │ nickname │ updated_at │
├────┼──────────────────┼────────┼────────────┼─────────────────────┼──────────┼─────────────────────┤
│ 1 │ user@example.com │ ユーザー太郎 │ cust_1234 │ 2024-03-06 08:23:36 │ taro │ 2024-03-06 08:23:36 │
└────┴──────────────────┴────────┴────────────┴─────────────────────┴──────────┴─────────────────────┘
はい、これで参照できるデータが作成できました。
Remixでloadする
ついにRemix上で扱えるようになります。
今回はそのまま _index.ts
に書いちゃいましょう。
その前に wranger.toml
から型定義を生成します。これはすでにremixの雛形生成時にコマンドを追加してくれています。
$ npm run typegen
> typegen
> wrangler types
⛅️ wrangler 3.31.0
-------------------
interface Env {
DB: D1Database;
}
これを実行すると worker-configuration.d.ts
が自動で生成されていてDBが参照できるようになります。
それでは、loaderを追加します。
+ import type { LoaderFunctionArgs, LoaderFunction } from "@remix-run/cloudflare";
+ import { useLoaderData } from "@remix-run/react";
+ import { drizzle } from 'drizzle-orm/d1';
+ import { users } from '../db/schema';
...
+ export const loader: LoaderFunction = async ({ context }: LoaderFunctionArgs) => {
+ const db = drizzle(context.cloudflare.env.DB);
+ const allUsers = await db.select().from(users).all();
+
+ return {
+ allUsers
+ };
+ };
...
export default function Index() {
+ const { allUsers } = useLoaderData<typeof loader>();
return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
<h1>Welcome to Remix (with Vite and Cloudflare)</h1>
<ul>
+ {allUsers.map((user) => (
+ <li key={user.id}>{user.name}</li>
+ ))}
<li>
...
追記が終わったらさっそく実行してみます。
npm run dev
先ほど追加したユーザー太郎が表示されていれば成功です。
本番リリース
まず本番のmydbにmigrationを反映します。
+ "db:migrate": "npx wrangler d1 migrations apply mydb --local",
+ "db:migrate:prod": "npx wrangler d1 migrations apply mydb"
違いは --local
と付いているかです。
実行方法は以下です。
npm run db:migrate:prod
これで本番にもlocalと同じテーブルが作成されました。
テストデータも追加しましょう。
npx wrangler d1 execute mydb --command="INSERT INTO users (id, email, name, nickname, customerId, created_at, updated_at) VALUES
('1', 'user@example.com', 'ユーザー太郎', 'taro', 'cust_1234', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);"
あとはpagesにdeployするだけです。これはすでにコマンドが設定されています。
npm run deploy
デプロイ完了後に数分待つとサイトが表示されるのですが、PagesあるあるでDBや環境変数等が自動では設定できないので、手動でぽちぽちします。
↓
この状態で確かですが待ってもだめで、もう一度deployする必要があります。
npm run deploy
今度はデプロイ完了メッセージのあとに早ければ数秒でサイトが表示されます。
以上です。
DB確認コマンド
PRAGMA table_list
$ npx wrangler d1 execute mydb --command="PRAGMA table_list"
🚣 Executed 1 commands in 0.2478ms
┌────────┬────────────────────┬───────┬──────┬────┬────────┐
│ schema │ name │ type │ ncol │ wr │ strict │
├────────┼────────────────────┼───────┼──────┼────┼────────┤
│ main │ users │ table │ 7 │ 0 │ 0 │
├────────┼────────────────────┼───────┼──────┼────┼────────┤
│ main │ sqlite_sequence │ table │ 2 │ 0 │ 0 │
├────────┼────────────────────┼───────┼──────┼────┼────────┤
│ main │ d1_migrations │ table │ 3 │ 0 │ 0 │
├────────┼────────────────────┼───────┼──────┼────┼────────┤
│ main │ _cf_KV │ table │ 2 │ 1 │ 0 │
├────────┼────────────────────┼───────┼──────┼────┼────────┤
│ main │ sqlite_schema │ table │ 5 │ 0 │ 0 │
├────────┼────────────────────┼───────┼──────┼────┼────────┤
│ temp │ sqlite_temp_schema │ table │ 5 │ 0 │ 0 │
└────────┴────────────────────┴───────┴──────┴────┴────────┘
PRAGMA table_info(TABLE_NAME)
$ npx wrangler d1 execute mydb --command="PRAGMA table_info(users)"
🚣 Executed 1 commands in 0.1754ms
┌─────┬────────────┬──────┬─────────┬───────────────────┬────┐
│ cid │ name │ type │ notnull │ dflt_value │ pk │
├─────┼────────────┼──────┼─────────┼───────────────────┼────┤
│ 0 │ id │ TEXT │ 1 │ │ 1 │
├─────┼────────────┼──────┼─────────┼───────────────────┼────┤
│ 1 │ email │ TEXT │ 0 │ │ 0 │
├─────┼────────────┼──────┼─────────┼───────────────────┼────┤
│ 2 │ name │ TEXT │ 1 │ │ 0 │
├─────┼────────────┼──────┼─────────┼───────────────────┼────┤
│ 3 │ customerId │ TEXT │ 0 │ │ 0 │
├─────┼────────────┼──────┼─────────┼───────────────────┼────┤
│ 4 │ created_at │ TEXT │ 0 │ CURRENT_TIMESTAMP │ 0 │
├─────┼────────────┼──────┼─────────┼───────────────────┼────┤
│ 5 │ nickname │ TEXT │ 0 │ │ 0 │
├─────┼────────────┼──────┼─────────┼───────────────────┼────┤
│ 6 │ updated_at │ TEXT │ 1 │ CURRENT_TIMESTAMP │ 0 │
└─────┴────────────┴──────┴─────────┴───────────────────┴────┘