たけのこ!たけのこ! ニョッキッキ!
はじめに
みなさん、たけのこニョッキというゲームを知っていますか?大学生のとき居酒屋でやってたあのゲームですね。
突然、全世界でたけのこニョッキがしたくなったので、NyokkiというWEBサービスを作りました。全世界の人にニョッキしてほしいので、全て英語で作成しています。
とてもシンプルで簡単なゲームであり、会員登録も不要なのでぜひ遊んでみてください。
タケノコとAIとわたし。
技術的なことはさておき、このサービスをなぜ作ったのかというと、小さくてもいいから国境を越えて楽しんでもらえるものを作りたいと思ったからです。そうすると、言語的なものではなく、ゲーム性があるものがよく、なおかつ誰でも遊べるものが良いと思いました。
その中で実装も楽なものはないかと探していたところ、居酒屋のとなりで大学生がたけのこニョッキをやっており、このゲームが時代を越えて受け継がれていることに感動しました。生まれを調べてみると、フジテレビのネプリーグでやっていたゲームでした。つまり日本生まれの伝統的なゲームと言っても過言ではないですね。
この日本の素晴らしきたけのこニョッキを世界に広めようと志したわけですが、私は多少は英語の読み書きはできるつもりではいますが、いかんせん自信がありません。ビジネスや論文のような文体ならまだしも、人に楽しんでもらうための文体の英語は書ける気がしません。そこで文明の利器を利用したいと思います。したがって、Nyokkiの英語は全て、みなさんご存知GPT-4が作成しました。
さらに、背景に利用している画像も、あまり普段見ないような背景にしようと思い、Stable Diffusionをローカルサーバーで立ち上げて、作成したものを利用しています。
技術スタック
フロントエンド: Next.js
デプロイ: Vercel
バックエンド: Supabase
データベース: PostgreSQL
相棒: GPT-4, Stabel Diffusion
Supabaseについて
最近はFirebaseやAmplifyなどを使うことが多く、FirestoreやDynamoDBなどのNoSQLを使うことが多いです。なので、ORMをゴリゴリ触る機会も少なく、TypeORMとかPrismaとか触ってみたいなあと思っていました。
Supabaseは"The Open Source Firebase Alternative"をうたっており、Firebaseの代替として利用できるオープンソースのクラウドサービスです。FirebaseとSupabaseの大きな違いはデータベースです。FirebaseはNoSQLのFirestoreを利用していますが、SupabaseではRDBMSのPostgreSQLを利用しています。これまでは、全てをサーバーレスでアプリケーションを作成するためには、NoSQLを選択することが一般的でしたが、正規化や検索の観点からRDBを使いたくなるユースケースもしばしばありました。ここにSupabaseが登場することによって、RDBを利用する選択肢が追加されたことになります。Firebaseの方が歴史も長く有名で、機能数も勝っていますが、SupabaseにもAuthやStorageのような基本的なサービスはあるため、軽くアプリケーションを作ってみようというユースケースには十分でしょう。
Supabaseにはコスト的な利点もあります。従来、自分でサーバーを立ててRDBMSを利用する場合にはFargateの利用など考えると、最低でも月数千円のランニングコストが必要でした。また、Firebaseの場合も、とても大きな無料枠はあるものの、一定限度を超えると従量課金となるため、たとえば攻撃を受けた場合や設定を間違えた場合に多額の請求がくるリスクもあります。
それに対して、SupabaseはFREEプランがあり、お財布と心臓に優しいです。ユーザー数が増えてきた際にはFreeプランでは心許ないこともありますが、スケーリングできるのも良いですね。また、Firebaseとは異なりオープンソースですので、いざとなったら任意のサーバーにデプロイし直せるのもよいですね。
Vercelについて
実は最近Vercelもフロントエンドだけでなく、バックエンド領域に進出してきています。
今回はVercelはPostgresベースのVercel PostgresとRedisベースのVercel KVを出しており、今回の選択肢に入っていたのですが、Supabaseいいよと聞いていて、とにかくSupabaseを使ってみたかったので今回は利用を見送りました。もう少し経ったら使ってみたいです。
Supabaseでのクエリ
詳しくはドキュメントみてください。軽いチュートリアルだけ紹介します。
const { data, error, status } = await supabase
.from('countries')
.select(`
id,
name,
cities ( id, name )
`)
.eq('id', user?.id)
ほぼSQLで見たことあるような、単語が並んでいるかと思います。
このクエリは、countriesとcitiesの二つのテーブルがあり、countriesがid, name, city_idをもち、citiesがidとnameをもっているような場合に、joinするときのクエリです。つまり、"Tokyo"に紐づく市町村全部とってくるイメージです。Firestoreの場合はサブコレクション等を利用したり、CollectionGroupを利用したりして取ってくるわけですが、柔軟性はやはりSQLに軍配が上がりますね。
アクセス制御に関してはRow Level Security (RLS) を利用します。Firestoreに馴染み深い人にとってはFirestoreのRulesのようなものを想像していただくとわかりやすいと思います。
create policy "Individuals can view their own todos."
on todos for select
using ( auth.uid() = user_id );
こんな感じでアクセス制御ができます。GUIでも登録可能です。
さらにデータベース関数と呼ばれる、より複雑なクエリに対しても対応可能な機能があります。
create or replace function add_planet(name text)
returns bigint
language plpgsql
as $$
declare
new_row bigint;
begin
insert into planets(name)
values (add_planet.name)
returning id into new_row;
return new_row;
end;
$$;
こんな感じでSQLをゴリゴリ書いて、client側でsupabase.rpc("add_planet")
で呼び出すことができます。
Supabaseの開発体験
正直言ってとてもよかったです。以下の手順で開発できます。
- SQL or ダッシュボードから操作して、データベースのスキーマを登録する。
-
supabase gen types
を利用して、スキーマからローカルにtypescriptの型を自動生成する。
このsupabase get types
がデータベースからのCRUDの型を自動生成してくれるわけですが、これがとてもよかったです。さらにデータベース関数の引数の型と返り値の型まで自動生成してくれるので、感覚的にはCloud functionsの型まで自動生成してくれるような感覚でした。また、この型を利用してRESTで返すかGraphQLで返すかも選択できるので、Typescript好きとしてはたまらなかったです。
Supabaseの課題
開発中、なぜかダッシュボードには入れなくなることがありました。数時間で収まりましたが、Twitterでも同様の報告があったりしたので、何かあったのだろうと思います。やはり安定性と信頼性に関しては、時間のみが解決してくれることもあり、リスクとリターンを考えて積極的に使っていきたいですね。
おわりに
まだまだ発展途上ですが、Supabaseとてもよかったです。新しい技術を触るのはとても楽しいですね。
ぜひみなさんもニョッキしてみてください。改善点のコメントをはじめとした様々なコメントお待ちしております。
Discussion