Supabase+Remix+Cloudflare WorkersでHello, World
GitHub
ソースを上げました。
今回の目標
表題にもありますが、3つのシステムを組み合わせてHello, Worldするまでやってみたいと思います。具体的にはSQLのSELECTでHello, WorldというデータをSupabaseから取ってきて表示するところまでですね。それぞれの役割をざっくり表すと以下の通りです。
- Supabae データベース担当
- Remix ビュー・コントローラー担当
- Cloudflare Workers サーバー担当
こうして見るとRemixだけ担当範囲が広いような気がします。
事前の注意ですが、3つとも活発に更新するためバージョンが合わないとうまく動かない可能性が高いです。人間でたとえるなら、三角関係ってことですね。なので、試してみたい方がいらっしゃいましたらバージョンにご注意ください。また、同様の理由でこの記事の賞味期限が短いことにもご留意ください。
RemixとWorkersを動かすところから始めます。それでは行ってみましょう。
RemixとWorkersの環境構築
Remixが公式で用意されています。
以下の通り実行すればインストールできます。ただ、Supabaseとの相性を考えるとリポジトリをgit cloneするのをおすすめします。Remix@1.2.1では後述のcross-fetchからWeb API Fetchへの入れ替えでエラーが出ました。このリポジトリにはRemixとSupabaseとCloudflare Workersのバージョンが記載されていますが、Remix部分の中身がないので1から作る必要があります。npx create-remix@latest
#Cloudflare Workersを選択
#Typescriptを選択
#npm install?をYes
git clone https://github.com/smallStall/RSCStarter
cd RSCStarter
npm install
git cloneしたら、Cloudflare Workersの設定ファイルwrangler.tomlがセキュリティの関係上抜けているので、ルートに作成します。
name = "remix-supabase-cloudflare-workers-starter"
type = "javascript"
zone_id = ""
account_id = ""
route = ""
workers_dev = true
[site]
bucket = "./public"
entry-point = "."
[build]
command = "npm run build:worker"
watch_dir = "build/index.js"
[build.upload]
format="service-worker"
Remix+WorkersでHello, World
次にHello, Worldを書きます。以下の通り、ファイルを作ります。
import { useLoaderData, Scripts } from "remix";
import type { LoaderFunction } from "remix";
export const loader: LoaderFunction = () => {
return { message: "Hello, Remix!" };
};
export default function Index() {
const data = useLoaderData();
return (
<html lang="jp">
<body>
<h1>{data.message}</h1>
<Scripts />
</body>
</html>
);
}
実行は以下の通りにすればstartでブラウザのタブが開きます。
npm run dev
#ターミナルをもう1つ開く
npm start
これでHello, Remix!と表示されればOKです。このコードの詳細は以下のページに書きました。
Supabaseのテーブル作成
公式のHPに行ってsign upし、projectを作成します。詳細は省いちゃいます。
さて、Supabaseのprojectを作成できたので、テーブルを作ります。
table editorを開きます。
「Create a new table」ボタンを押し、出てきたパネルに入力します。
この状態ではセキュリティがまずいんですが、重要なデータを入れるわけではないので、今は良しとします。
「Save」ボタンを押せば、テーブルが作成されます。
作成されたら、「Insert row」ボタンを押して下記の通りデータを入力しました。
saveしてもう一行入れておきます。
合計で2行入りました。
次にSupabaseのSite URLのところにlocalhostを登録します。Authenticationアイコン→Settingsタブを選択します。
Site URLにlocalhostを入力します。
これでSupabase側の準備は完了です。
RemixからSupabaseに接続する
ここがけっこう難しく感じました。がんばります。
データベースに接続するにはSupabaseのcreateClient関数を実行します。
これをRemixのloader内で行います。全体の流れはそんな感じです。
createClientの引数はsupabaseURL、supabaseKey、optionです。supabaseURLとsupabaseKeyについては、SupabaseのSettingsアイコン→APIタブの順に選択すると表示されます。
supabaseURLとsupabaseURLを環境変数に設定します。まず開発環境の環境変数を設定します。
npm i dotenv
ルートに.envファイルを作成し、supabaseURLとsupabaseKeyをコピペします。
SUPABASE_URL=supabaseURLをコピペ
SUPABASE_ANON_KEY=supabaseKeyをコピペ
.envの中身が公開されると困るので.gitignoreに加えてあります。
/.env
型を定義します。app配下にファイルを作成します。
export {};
declare global {
const SUPABASE_ANON_KEY: string;
const SUPABASE_URL: string;
}
これで開発環境において環境変数が使えるようになりました。
rootを次の通り書き換えます。
import { useLoaderData } from "remix";
import type { LoaderFunction } from "remix";
import { createClient } from "@supabase/supabase-js";
export const loader: LoaderFunction = async () => {
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
fetch: (...args) => fetch(...args),
});
return { message: "Hello, Remix!" };
};
export default function Index() {
const data = useLoaderData();
return <h1>{data.message}</h1>;
}
この状態ではまだcreateClientを実行しただけですので、データベースにアクセスする準備しかしていません。createClientのoptionにあるfetchのところに注目してみます。何やら呪文のようなものが書かれていますね。
fetch: (...args) => fetch(...args),
ここではSupabaseのfetch(cross-fetch, Node.jsベースのfetch)をRemixのfetch(Web Fetch API)に書きかえています。
より詳しくはこちらのIssueで議論されていました。fetchを置き換える最も汎用性が高い書き方のようです。 postgrest-jsというのはsupabase-jsのうちpostgrestに関わる部分です。supabase-jsは色々なものをまとめているので、postgrest-jsの方が情報が早い場合がありそうです。 Cloudflare WorkersにおいてもWeb Fetch APIが使用されています。 これらのことを踏まえて、root.tsxにコードを付け加えます。import { useLoaderData, Scripts } from "remix";
import type { LoaderFunction } from "remix";
import { createClient } from "@supabase/supabase-js";
type Message = {
title: string;
};
export const loader: LoaderFunction = async () => {
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
fetch: (...args) => fetch(...args),
});
const { data } = await supabase.from<Message>("message").select("title");
return data;
};
export default function Index() {
const messages = useLoaderData<Message[]>();
return (
<html lang="jp">
<body>
<div>
{messages.map((message) => (
<h1>{message.title}</h1>
))}
<Scripts />
</div>
</body>
</html>
);
}
本当はtypeやcreateClientは別ファイルにした方がコードの見通しが良いのですが、まずは動かすのを最優先にしました。
これでHello, Supabase, Remix, and Workersと表示されていればRemix経由でデータベースから引っ張ってきたことになります。エラーはないけどうまく表示されない場合、ブラウザのキャッシュが効いている可能性があります。スーパーリロードを試せば更新されるかもしれません。
selectは何を返していのかが気になります。公式を見てみると、{data, error}を返すようですね。
このdataというのは何を指しているんでしょうか?Supabaseのコードをざっくり覗いてみますと、PostgrestResponseSuccessインターフェイスのdataのようです。
interface PostgrestResponseSuccess<T> extends PostgrestResponseBase {
error: null
data: T[]
body: T[]
count: number | null
}
話をroot.tsxに戻します。useLoaderDataによりResponseを受け取り、Message型の配列が帰ってきます。配列をmapでh1タグに展開しています。
若干よくわからないところもありますが、大まかな流れはそんな感じのようです。
Cloudflare Workersにデプロイ
CloudflareにSign upします。詳細は割愛します。
Cloudflareのダッシュボードが開いたら、次にWorkersのCLIをダウンロードします。npm install -g @cloudflare/wrangler
wranglerコマンドでログインします。
wrangler login
Allow Wrangler to open a page in your browser? y
デプロイする前に本番環境でも環境変数が使えるようにします。ターミナルを開いて環境変数を入力します。
wrangler secret put SUPABASE_URL
#supabaseURLを入力
wrangler secret put SUPABASE_ANON_KEY
#supabaseKEYを入力
wranglerの設定ファイル、wrangler.tomlに必要事項を入力します。入力するのは機密情報なので.gitignoreに入れてあります。
/wrangler.toml
ターミナルで以下を実行します。
wrangler whoami
これでアカウント名とアカウントIDが表示されます。
アカウントIDをwrangler.tomlにコピペします。zone_id = ""
account_id = "ここにコピペ"
route = ""
これで役者は揃ったのでデプロイしましょう。publishというコマンドです。ちょっとオシャレですね。
wrangler publish
さらにうまく行けば、Hello, なんちゃらが表示されます。これでサイトがデプロイされました。素晴らしい!
次回
今回はとりあえず動かすのを最優先にしたため、サイトとしての機能はほとんどありませんでした。次回からはCRUDや認証周りをやってみようと思います。書きました。
現場からは以上です。
Discussion