世界を動かす技術を、日本語で。

貧者のバックエンド・アズ・ア・サービス(BaaS):Firebase/Supabase/Pocketbaseに類似

概要

Pennybase は、 軽量なBaaS として、 Firebase/Supabase/Pocketbase の代替となるGo製ソリューション。 標準ライブラリのみ で実装され、ファイルベースの CSVストレージ を採用。 認証・RBAC・リアルタイム更新 などの基本機能を1000行未満で実現。 REST API やテンプレート描画、フックによる拡張性も確保。 MITライセンス で公開、シンプルさ重視の設計。

Pennybaseの特徴

  • Firebase/Supabase/Pocketbase のようなBaaS機能を Go言語 で実装
  • 外部依存なし・標準ライブラリのみ で動作
  • CSVファイル による バージョン管理付きストレージ 方式
  • REST API でCRUD操作・リアルタイム更新の提供
  • 認証(セッションCookie/Basic認証)RBAC/所有権ベースの権限管理
  • スキーマバリデーション (数値/テキスト/リスト型対応)
  • Goテンプレート によるHTMLレンダリング
  • フック関数 で拡張性確保
  • 静的ファイル配信 機能

データ構造・ストレージ

  • データはCSVファイル に1レコード1行で保存
  • 追記専用設計 で各更新は新バージョンとして保存
  • 最新バージョンはメモリ上のインデックス で高速アクセス
  • 1列目:レコードID、2列目:バージョン番号、以降はデータ項目
  • _schemas.csv でJSONフィールドとCSVカラムのマッピング・バリデーション定義
    • 型は text/number/list のみ対応
    • バリデーションに 正規表現 や最小/最大値指定

ユーザー・認証管理

  • _users.csv で認証情報・ロール管理
    • ユーザーID(ユーザー名)・バージョン・ソルト・パスワード(SHA-256+Base32)・ロール
  • API経由でユーザー追加不可、手動編集のみ
  • 認証方式Basic認証 または セッションCookie
    • /api/login でセッション生成、 /api/logout で無効化

権限管理(RBAC)

  • _permissions.csv でアクセス制御ルール定義
    • リソース名・アクション・フィールド・許可ロール 等を指定
    • owner フィールド指定で所有者も許可
  • ルール不一致時はアクセス拒否

REST APIエンドポイント

  • GET /api/{resource}?sort_by={field} :リスト取得・ソート可
  • GET /api/{resource}/{id} :単一レコード取得
  • POST /api/{resource} :新規作成("create"権限必要)
  • PUT /api/{resource}/{id} :更新("update"権限必要)
  • DELETE /api/{resource}/{id} :削除("delete"権限必要)
  • GET /api/events/{resource} :SSEによるリアルタイム更新配信("read"権限必要)

静的アセット・テンプレート

  • staticディレクトリ からHTML/CSS/JS等の静的ファイルを配信
  • templatesディレクトリ でGoテンプレートを利用可能
    • テンプレート内で .User, .Store, .Request, .ID, .Authorize 等の変数/関数が利用可
    • 権限チェック やレコード取得もテンプレート内で柔軟に実装

拡張フック(Hooks)

  • create/update/delete時に1つのフック関数 を呼出
    • trigger:アクション種別、resource:リソース名、user:実行者、res:操作対象データ
    • 追加バリデーションやデータ加工 が可能
    • エラー返却でアクション中断

コントリビューション・ライセンス

  • MITライセンス で公開
  • バグ修正・テスト・例のみ歓迎、新機能追加は原則なし
  • シンプル・明快・正確なコード維持が方針

Pennybaseの活用シーン・メリット

  • 小規模プロジェクトやプロトタイピング 用途に最適
  • 外部DBや複雑な依存不要 で導入が容易
  • シンプルなRBAC/認証/ストレージ を短時間で構築可能
  • Go言語学習・BaaS仕組み理解 にも有用

まとめ

  • Pennybase超軽量・依存ゼロ のBaaSで、 CSV+Goのみ で本格的なバックエンド機能を実現
  • 認証・RBAC・リアルタイム・テンプレート 等、必要十分な機能を1000行未満で実装
  • シンプル設計拡張性 の両立が特徴
  • MITライセンス で自由に利用・改変可能

Hackerたちの意見

このアプローチのシンプルさが好きだな。目的のためにtrailbaseをフォローしてるよ: https://trailbase.io 簡単なバックエンドの選択肢がいろいろ出てきてるのが嬉しい。pennybaseからtrailbase、pocketbaseまで。どれかが最終的にsqliteの代わりにpostgresを実装してくれるといいんだけどね。

Pocketbaseに貢献する代わりに新しいプロジェクトを作る理由がわからない。Pocketbaseと似てるのに、何が新しいの?

ミニマリズム。小さくて、データはvimsha256sumで完全に管理できる。設定を考えると、ユーザーリストが数十人の小規模な家庭用アプリには合ってると思う。

なんで?ってずっと思ってたけど、結局は著者の選択なんだよね。私たちはそれを二者択一として考えがちだけど、実際にはこれか何もないという選択肢だったのかもしれない。あなたの投稿の二行目が好きなんだけど、比較はどうなの?私の意見では、これが本当に小さい(1k loc未満はすごい)ってことが一番のポイントかも。もしかしたら、オッカムの剃刀に従ってSQLを捨てて、最もシンプルなSQL(SQLite)も完全に無視して、CSVにしたのかも。SQLiteが一番シンプルで埋め込み可能なSQLデータベースだと思ってたから、こんなことを言う日が来るとは思わなかった。ちょっと脱線しちゃったかもしれないけど、SQLiteとPocketbaseのやってることが本当に好きなんだ。SQLiteとユーザごとのデータベースを考えると、このアーキテクチャについて考えるだけで幸せになるよ。SQLiteが大好き!

Pocketbaseにはないものを何をもたらすの? NIHが主な理由だね。PocketBaseが優れている大きな理由は、機能や貢献が厳しく制限されているからだと思う。みんなにはエコシステムに貢献してほしいな。エコシステムは大きくて成長してるから。

公平に言うと、このプロジェクトは最近書いたブログ記事にリンクされてるから、ほんとに小さな個人的/教育的なプロジェクトなんだ。k8sのAPIサーバーが提供するようなAPIを試してみたくて、カスタムリソースの動的スキーマ、均一なREST APIの生成、明確に定義されたRBACルール、ウォッチ/リアルタイム通知、入場フックでのビジネスロジックのカスタマイズなどを実験したよ。できるだけ小さくしようともしたしね。だから、Pocketbaseや他のものと競争するつもりはないんだ。ただ、似たようなアーキテクチャでミニマムなバックエンドを構築するには何が必要かを見てみたいだけなんだ。「データベース」の選択は、まさにその目標によって決まってる。意図的にインターフェースにしたから、もっと良いデータベースも存在するし、少しのコード変更で接続できるよ。でも、最初はGoの標準ライブラリが提供するものを使ったし、CSVはデバッグが簡単だからね。

なんでハウスシェアに参加せずに、自分の家を買ったり借りたりするの?

Chromeがデスクトップとモバイルの両方でFile System Access APIをサポートするようになった今、バックエンドってまだ必要かな?ユーザーデータをファイルとして保存するウェブアプリを書き始めたけど、このアプローチがすごく気に入ってる。デスクトップとAndroidでは完璧に動くよ。iOSは本物のChromeが使えないから(たぶんヨーロッパだけ)、データを「Origin private file system」に保存するオプションも用意してる。幸い、同じAPIを使えるから実装も楽だった。唯一の欠点は、ユーザーが選んだディレクトリにファイルを置けないこと。だから、そのモードでは古典的なダウンロードリンクでバックアップをサポートしてる。これで、ユーザーはデータをクラウドに入れなくて済むし、全部自分のデバイスに残るよ。

TIL!クラウドなしのアプリを作るのが好きで、「エクスポート」ボタンを使ってローカルストレージに依存してる。このアプローチがまさに探してたものだ。ローカルファーストのアプリについて読んだことの多くは、コラボレーション機能のためのデータ同期を解決することを含んでたけど、ローカルの永続性だけが必要ならこんなにシンプルだとは思わなかった。

複数のデバイスやブラウザを使ってる人はどうなるの?ローカルストレージを何年も使ってるけど、特にマルチプレイヤーでは導入が難しくなってるよね。

Chromeがデスクトップとモバイルの両方でFile System Access APIをサポートしている今、バックエンドはまだ必要なのかな?これでローカルDBにアクセスできるようになるのかな?アプリが自分のデバイスにあるDBと直接やり取りできるようになって、デバイス間でDBが同期できるといいな。そうすれば、すべてのデバイスでデータを持っていられるけど、データは常に自分のデバイスの中に留まるからね。もちろん、これはネイティブアプリでやるのは比較的簡単だけど、ブラウザ上で動くWebアプリでもできるといいな。ところで、Chromeはログインしているときにローカルストレージをデバイス間で同期するの?

少なくともAndroidの面では、アプリが自分のストレージターゲットに書き込めるようにしてほしいな。理由は、すでにSyncthing-Forkを使って親Syncディレクトリ(ObsidianやOpenTracksなど)を監視して、バックアップシステムに送ってるから。実際、これによりアプリがローカルファーストになって、ネットワークアクセスなしでも動けるようになるんだ。でも、自動バックアップはできるようにしたい。もしこれをもう少し形式化するものがあれば、開発者もアプリを「自分のネットワークを持ち込む」みたいな感じで作れるかも。もしかしたら、すでに誰かがやってるかもしれないね。

何か例ある?

もう一つ重要なファイルは_users.csvで、ユーザーの認証情報や役割が含まれている。フォーマットは他のリソースと同じだけど、特別な_usersコレクション名がついてる。APIを通じて新しいユーザーを追加することはできず、このファイルを編集して手動で作成する必要がある: admin,1,salt,5V5R4SO4ZIFMXRZUL2EQMT2CJSREI7EMTK7AH2ND3T7BXIDLMNVQ====,"admin" alice,1,salt,PXHQWNPTZCBORTO5ASIJYVVAINQLQKJSOAQ4UXIAKTR55BU4HGRQ====, > ここにはユーザーID(ユーザー名)、バージョン番号(常に1)、パスワードハッシュ用のソルト、そしてパスワード自体(SHA-256でハッシュ化され、Base32でエンコードされている)が含まれている。最後の列は、そのユーザーに割り当てられた役割のリストだ。パスワードハッシュの処理は10年くらいしてないけど(SSOのおかげで)、SHA-256みたいな高速ハッシュは良くないんじゃない?最後にやった時はbcryptがスタンダードだったけど。これは単なる例で、実際のコードでは使われてないのかな?

確かにbcryptが好ましいけど、これはただのシンプルなバックエンドだからね。最初に気になったのは、golangの組み込みSQLiteサポートの代わりにCSVをストレージとして使うことだった。SQLite接続はsqlite://data.dbの接続文字列だけでできるからね。

例に入っているなら、誰かのプロダクションコードに入ることは間違いないね。

他の人が推測しているように、私はGoの標準ライブラリが提供するものに制限したよ。個人的/教育的なプロジェクトだから、k8sのAPIサーバーやさまざまな人気のBaaSに似たアーキテクチャで遊んでみたかっただけなんだ。ローカルホストの外で動かすつもりはなかったから、パスワードのセキュリティやデータベースの選択は気にしてなかったし、標準ライブラリにあるもので「十分良ければ」それでいいと思ってた。もう少し柔軟にしようともしたよ。bcryptを使うには、自分のpennybase.HashPasswd関数を提供すればいいし、SQLiteを使うにはpennybase.DBインターフェースの5つのメソッドを実装すればいい。完璧ではないけど、700行のコードサイズであれば、あまり難しくなくカスタマイズできるはずだよ。

SHA-256のような高速ハッシュはそれにとって悪いの? 高速ハッシュは、データベースが侵害されたときや、ユーザーが異なるサイトでユニークなパスワードを使えない場合にだけ問題になる。ハッシュが時間がかかるのは、オフライン攻撃のシナリオでユーザーを守るためのもの。自分のCPU時間を彼らのために使っているようなもんだよ。オンライン攻撃の文脈では、攻撃者が毎秒何十億回も試すのを防ぐのは簡単だし、ハッシュ操作を一定の時間かかるように見せることもできる。

これいいね。Ruby on Rails -> サーバーレス(Firebase/Herokuなど) -> Pocketbaseとか?みんなはどんなアプリを作ってるの?Pocketbaseやtrailbaseで動いてるそこそこ大きなウェブサイトはあるのかな?

なんでCSVじゃなくて改行区切りのJSON配列なの?ストレージフォーマットに曖昧さがあるのは長い目で見て良くないよ… JSONラインはどこでも簡単にパースできるし。

振り返ってみると、JSONLの方が開発者として扱いやすかったかも。でもCSVを選んだことは後悔してないよ。DBインターフェースはプラグイン可能だから、必要ならJSONLも使えるし、APIレイヤーでデータストレージ(モデル)とデータ転送オブジェクト(DTO)のフォーマットを分けたかったんだ。実際のデータベースみたいにね。CSVは確かに限界があって脆弱だけど、データ変換や検証の部分がもっと明確になったと思う。

それともSQLite?

CSVデータベースは面白いね。多分、デバッグが一番簡単なデータベースだと思う。ただ、なんでSQLiteじゃないの?CSVはホストが書き込みの途中でクラッシュしたら壊れやすいし。

標準ライブラリ以外の依存関係はないんじゃないかな。

「安い」代替品に関する一番の問題は、みんな同じ道を辿ることだよ。最初は安く始めて市場を獲得して、ロックインしたらコストを上げる。オープンソースも悪用されてるし。最初は全部オープンソースって言っておいて、プレミアムサービスはクローズドソースになる理由が出てくるんだよね。

これ、そんなに安いって言ってるわけじゃないし、料金を取るとも言ってないと思う。クリックしてみたら笑えるかも。ロックイン後にコストが上がる心配はしなくていいと思うよ。

「安く始めて、市場を集めたら、ロックイン後にコストを上げる。」コストと価格の用語の違いを理解しておくと、ビジネスプランを書くときや会計士、CFOとコミュニケーションを取るときに役立つよ。「コスト」は企業が購入する供給品や投入物に対して支払う金額。「価格」はその企業が販売する製品に対して買い手に請求する金額。企業はコストを抑えたいし、高い価格から利益を得る。 (「コストを上げる」と言ったとき、ちょっと考えさせられるね)人々は一般的に自分の生活を企業のようには運営しないから、「コスト」と「価格」が同じように見えがちだけど、上記に加えて、企業にとっての「コスト」は実際の支出の合計を反映していて、「価格」はまだ取引されていないものの広告みたいなもの。「コスト」は会計用語で、総収入 - 総コスト = 総利益。「価格」はマーケティング用語で、1個1ドル、12個で10ドル! (もちろん、これは理解不能にこじつけることもできるけど、それは「ビジネスコミュニケーション」では避けるべきこと。常にコミュニケーションをスムーズにして、要点に早くたどり着くようにしよう。)

Pocketbaseはもう貧乏人のBaaSだし、他の二つに比べてミニマリストだよね。> 人間が読めるCSVにデータが保存される ほぼ完璧な小さな候補が二つあるのにデータベースを使わない選択をするのは本当に不思議だよ。プラットフォーム特有のバイナリが気に障るなら、Wasmビルドを使えるし。

君の主なポイントには同意するよ。PBには削るべき余分な部分がたくさんあるわけじゃないしね。プロジェクトのBDはスコープを厳しく制限してるから、そこが良さの一因だと思う。CSVの件は、なんか学術的な演習みたいに感じるな。SQLiteデータベースをテキストエディタで開けないのはちょっと薄い気がする。多くのツールはテキストエディタより軽量なのに、データベース(どんな形式でも)を「読む」ことが目的になることはあまりないからね。多分、クエリを実行したいんだろうから、まずはCSVをDuckDBにインポートして、「WHERE active=1」でいろいろクエリを書いてみるのがいいと思うよ。

Pocketbase以外の候補は何かある?

2025年に、CSVが「小さい」からデータベースの合理的な代替になるって思うのは、ちょっとおかしいよね。全くもって信じられない。

FTS5を有効にしたwasmビルドのSQLiteをデプロイしたばかりなんだけど、これがすごいんだ。基本的にクライアントサイドのElasticsearchみたいなもんだよ。ESほど堅牢ではないけど、80%くらいはそれに近い。繰り返すけど、これはあなたのスマホやSQLite対応のデバイスで動くからね。

ローカルでの使用ケースには役立つかも。ローカルで動かして、自分のやりたいことをやる。Excelや好きなツールで編集する。依存関係も一つ減るしね。

スクリプトから連結できて、スプレッドシートで編集やクエリもできる、テキストのCSVフォーマットっていいよね。メモリ内ポインタキャッシュのおかげで速いし、ターゲットスケーリングのカテゴリにいるなら、大きな利点だと思う。

それ、いいアイデアだね。プロジェクトのデータベースとしてGoogle Sheetsをよく使ってるよ。'setup'データと'log'データを読み込む感じ。うまくいってる。大抵は書き込み用に最後に行を追加するだけなんだけど、スプレッドシートアプリで編集できるならCSVも悪くないなって思う。だから、これをサーバー(ファイル共有)に置いて、必要に応じてExcelやNumbers、Google Sheetsで編集して、保存する感じかな。アプリがそれを読み込んで、書き込みで行が追加されるなら、やった!いいアイデアだね。