🍲

SvelteKit + Supabase で闇鍋アプリを3日で作ってみた

2023/02/06に公開

こんにちは、kosei28です。

今回は、SvelteKitとSupabaseで「YamiPot」という闇鍋アプリを3日くらいで作ってみました。
https://yamipot.com/

つくったもの


遊び方はとてもシンプルで、鍋を作り、具材を追加し、鍋から具材を1個ランダムに取り出すというだけです。

技術

  • 言語
    • TypeScript
  • フレームワーク
    • SvelteKit
  • UI
    • TailwindCSS
    • Headless UI
  • バックエンド
    • Supabase
  • ホスティング
    • Cloudflare Pages

ミッション

今回は、バックエンドとしてSupabaseを採用したので、とあるミッションを自分に課しました。

それは、バックエンドを書かない! というものです。

せっかくBaaSを使っているんだから、自前でバックエンドを書かずに1つのアプリを作ってみようと挑戦してみることにしました。
Supabaseもだいぶ人気になってきてるし、API作らなくても行けるんじゃねと思ったわけです。
しかし、意外というか、やはりというか、辛いところはまだまだあるようでした・・・

アクセス制御をどうやるか

1つ目の問題はアクセス制御です。

SupabaseはデータベースにPostgreSQLを採用しています。
PostgreSQLにはRLS(Row Level Security)という機能があり、Supabaseではこれを使ってデータベースへのアクセス制御を実現しています。

YamiPotでは、RLSでINSERT時にauth.uid() = user_idとしてuser_idが認証されたユーザーのidと一致しているか検証しています。

バリデーションをどうやるか

バックエンドを書かずにバリデーションをどうやるか、ということも問題になります。

PostgreSQLではCHECK制約を使うことができます。
そのため、Check制約を使えばデータベースへのバリデーションを実現することはできます。
YamiPotでも文字数の検証はCHECK制約で行っています。

しかし、APIのバリデーションとして考えると柔軟性に欠けます。
例えば、created_atカラムは基本的にnow()をデフォルトにして、INSERTするときに指定することはほとんど無いと思います。
Supabaseでは、このcreated_atを明示的に指定させない方法がありません。(少なくとも私にはわかりませんでした。やり方を知っている人がいたら教えてください🙇‍♂)

そこで、レコードの挿入時にはRLSでnow() >= created_atとすることで、未来の日時を指定することを防いでいます。
過去の日時を指定する分には他の投稿に埋もれるだけですが、未来の日時を指定されると、新しい投稿があっても、その投稿が上位に表示され続けるという問題が起こってしまうためです。

また、レコードの更新はできないようにしました。
更新できるようにして、created_atの更新を防ぐ方法がどうしても思いつかなかったので、断念しました。

どうやってランダムに取得するか

レコードをランダムに取得する方法はいくつかあると思います。
今回は面倒くさかったので、ORDER BY random()でやることにしました。
Supabaseでは、クライアントライブラリでrandom()を指定することはできないので、VIEWを作ることで実現します。
VIEWはCHECK制約と同様、SQL Editorで作る必要がありますが、作ったVIEWはTable Editorから確認することができます。

アカウントの削除ができない

Supabaseでアカウントを削除するには、Admin APIを使う必要があります。
これは、個人的には喜ばしいことです。
Firebase Authenticationなどはユーザー自身によるアカウントの削除を防ぐ方法がないため、バックエンドでアカウントの削除を完全に制御することができません。

しかし、今回はバックエンドを書かない縛りで開発しているため、アカウントの削除機能を実装することはできませんでした。
アカウントの削除をしたいというユーザーはそこまでいないだろうとも思ったので、アカウントの削除は問い合わせてもらって対応するという方針にしました。

今後アプリ開発するときは、最初はこんな感じで機能を最小限に絞ってもいいかもしれないですね。

Supabaseで型生成

Supabaseは最近触っていなかったので知らなかったのですが、supabase-jsがv2になりCLIで型を生成できるようになっていました。
TypeScriptでの開発がしやすく、とても良かったです。

Cloudflareが強すぎる

今回は、ホスティングにCloudflare Pagesを使いました。
それに合わせて、Cloudflareの他のサービスもいくつか使ってみたのですが、便利すぎたので紹介します。

Cloudflare Pagesの楽さとパフォーマンス

SvelteKitでは、デフォルトで@sveltejs/adapter-autoというアダプターが使われており、以下のホスティングサービスに対応しています。

  • Cloudflare Pages
  • Netlify
  • Vercel
  • Azure Static Web Apps

Cloudflare Pagesへのデプロイは、GitHubのリポジトリを指定して、環境変数を設定するだけでできたのでとても楽でした。

通常、データーベースとの通信はhttpではないため、Cloudflare Workersなどのエッジ環境では使えません。
しかし、今回はSupabaseでhttpで通信しているため、Cloudflare Pagesを使うことができました。
これは、バックエンドを自前で書いたとしても、Supabaseを採用する強力なメリットとなりそうです。

また、エッジで動いているためパフォーマンスがとても良く、PageSpeed Insightsですべての項目において100を達成することができました。

Cloudflare Accessでお手軽アクセス制御

Cloudflare Accessを使えばサイトへのアクセスを制御することができます。
Cloudflare Pagesではボタン1つでプレビュー環境へのアクセスを自分のみに制限することができますが、高度な設定や本番環境でのアクセス制御などを行う場合は、Cloudflare Zero Trustから設定する必要があります。
私は、GitHubアカウントでログインできるようにしています。

Cloudflare Zarazを使ってみた

Cloudflare ZarazはGoogle Analyticsなどをコードを変更せずに設定できるサービスです。
今回初めて使いましたが、簡単に設定して使うことができました。
しかし、SPAだからなのかは分かりませんが、ページ遷移しても反映されていないようでした。
また、地域情報も取得できていませんでした。
Zarazの仕組みはまだよく理解していませんが、クライアントで実行しているわけではないのなら、SPAのページ遷移を取得するのは難しそうですね。

ドメインが安い

今回yamipot.comというドメインも、Cloudflareで取得しました。
今までドメインはGoogle Domainsで取得していたのですが、Google Domainsでは年1540円なのに対して、Cloudflareでは年9.15ドルと少し安かったです。
Cloudflareを使うなら、ドメインもCloudflareで取得すると良いかもしれません。

まとめ

今回は、SvelteKitとSupabaseを使って闇鍋アプリをバックエンドを書かずに開発してみました。

やはり、BaaSだけでやる辛さはありましたが、Supabaseの便利さもよく分かりました。
Supabaseはバックエンドで使っても便利そうなので、今度tRPCと組み合わせて使ってみたいです。

Cloudflareも、簡単にいろいろなことができて、とても便利でした。
そして、とにかく安い!他の類似サービスと比べてもめちゃくちゃ安いと思います。
皆さんも快適なCloudflareライフをしてみてはいかがでしょうか?

というわけで、こんな感じで闇鍋アプリ「YamiPot」を開発してみたので、ぜひ遊んでみてください。
https://yamipot.com/

Discussion