Tech
Journal

個人開発サービスをリリースしたので技術スタックを紹介(titochat)

はじめに

個人開発のアプリをリリースしたので、一区切りとしてアプリの簡単な紹介と、技術スタックを共有したいと思います。

維持費をかけづらい状況の個人開発のため、コストをかけないで一定のスケールや移行はできる、みたいな軸がベースにあるため、会社・仕事における意思決定とは違う部分も多いためそこはその前提でお読み頂ければと思います。

つくったもの

作成したアプリはこちら: https://www.titochat.app

自身で発行したAPIキーを使って複数のAIと同時に会話できるツールを作りました。

  • 常に回答を比較することで、より良いモデルと会話を続けることが可能
  • 使用した分だけの料金を支払えば良い

といったメリットがあり、日々進化するモデルに対して低コストで比較を行ったり、最新のモデルを試すことができます。(トレードオフ的にですが高度な機能は利用できません)

現在はOpenAIのChatGPTとAnthropicのClaudeに対応しています。

同様の機能を持つアプリは他にもありますが、細かな操作でストレスを感じて作ってみたかった気持ちもあり自作してみました。

titochat-demo.gif

アプリケーション構成

全体として、フロントエンド(アプリ)、バックエンド、フロントエンド(LP)の3つの環境をmonorepoで管理しており、この記事に出てくる範囲では以下のように構成されています。

titochat-tech-stack.png

使用している主要技術の一覧

  • アプリ フロントエンド
    • フレームワーク: Remix
    • ホスティング: Cloudflare Pages + Workers
    • エラー送信: Sentry
  • バックエンドとインフラ
    • フレームワーク: Hono
    • データベース: PostgreSQL
    • ホスティング: Dokku on Hetzner
    • トランザクションメールサービス: Mail Baby + SMTP2GO
    • ログとモニタリング: GCP Cloud Trace、Cloud Logging、Error Reporting
    • サーバーモニタリング: Netdata Cloud
  • ランディングページ
    • フレームワーク: Astro
    • ホスティング: Firebase Hosting

個人開発のコストを考慮しながら選定を行い、学習を兼ねてやりすぎた部分もあります。バックエンドとフロントエンドはmonorepo内でtRPCの型を共有しています。

技術選定の詳細

アプリフロントエンド

最初はTanstack Router + ViteのSPAをHonoバックエンド上のstaticファイルホスティングで提供し、バックエンドと統合する構成としていました。将来的に履歴を管理しやすくするため、SSRに移行できるようにRemixに移行しました。

フルスタックで開発することも考えましたが、以下の理由からフロントエンドとバックエンドを分離することにしました:

  • OpenTelemetryのデータはバックエンド側で送信したかったが、フロントエンド側には不要だった
  • ログの使い方も両者で違って良かった
  • レートリミットも共通設定や詳細設定を分けたかった
  • フロントエンドはCDNに乗る方が良く、バックエンドはDBの近くで実行される方が良かった

フロントエンドは最後にはシンプルとは言えないものの、フレームワーク依存が少なくなったのは良かったかと思っています。ただ、SSRのためにRemixを導入したものの、共有をON/OFFするような機能である場合は、外部キャッシュに乗ってしまうことも考えるとSSRでOGPを出す要件を満たす必要がこのアプリではなかったかもしれません。(そもそもまだ履歴保存する機能がありませんが…HAHA…)

フロントエンドのホスティング

フロントエンドをバックエンドと分離したため、CDN/エッジ実行に寄せようと、Cloudflare Pages + Workersにデプロイしています。一度使ってみたかったという理由もあります。

Amplifyも良いホスティング環境だと思っていましたが、Gen2のドキュメントが難解だったり、CDKベースとなるCloudFormationには抵抗があったため、今回は見送りました。Next.jsのPages RoutesやAmplifyのGen1も好印象だったため、自分目線では好きだった気がするものがアップデートでなくなっていくなぁという悲しさがあります。両方今後も長期的にサポートするようなことは書いてあったりするものの、新しいものより先になくなるとは思うので新規に採用することには抵抗を感じてしまいます。

バックエンド

TypeScriptとの相性やAPIの使いやすさからHonoを第一候補としていました。当初AnthropicのSDK/APIがCORSに対応しておらずproxyが必要でしたが、用意されているMiddlewareとしてはHonoにはなさそうだったためExpressやFastifyを使用していましたが、作っている途中でAnthropic側の意思決定によりCORSが許可されるようになったため後でHonoに戻しました。

機能的にはどのフレームワークでも実現可能ですが、TypeScriptとの相性を考慮するとHonoが適していると感じました。ただし、安定性を求める観点では他のフレームワークも検討の余地がありそうで、この点で言えばNestJSも選択肢に入ってきそうに感じます。

細かい例で言えば、Prismaを使用する場合、複数のリポジトリを跨いでトランザクションを実行したい場合、クライアントを引き回す必要がありそうですが、NestJSではこれに対応した手法が既に用意されています。すごい。

上記を参考にしつつ、AsyncLocalStorageを経由したトランザクションクライアント管理機構を最終的に自分でも自作しました。
こういう系の細かい部分の課題はRailsとよく戦っているのを見かけるもののまだ勝ててない/枯れてない感じを少し感じます(詳しくないので怪しい/対応策はなくはないことは理解しつつ)

何気に苦労したのは実DB上を使いつつ並列テストを実行する方法でした。vitest-environment-vprisma に助けられつつ、テストごとにトランザクション下で実行しつつRollbackする手法に最終的に落ち着きました。

バックエンドのホスティング

地域に限定されないアプリケーションであれば、グローバル目線で展開したいという点と、自分が使用したいのでレイテンシーを重視して、西アメリカのサーバーが良いと考えました。レイテンシーとしては120ms程度であれば自分的には耐えられそうでした。

HetznerなどのVPSのピュアな環境ではus-west/west2系で実現できたものの、RailwayやFly.ioのus-west系も試してみたのですが、レイテンシーが最大で2倍になったりしてしまい予想と大きく違ったため、採用を断念しました...(Fly.ioでは自分のプロバイダーの経路解決に怪しさがありました)。

Railwayはユーザー体験、課金体系がとても好きで、レイテンシーとリージョン対応が良さそうであればお金を払ってすべてをまとめたいくらいでしたが、レイテンシは思ったより低くなく、近いリージョンもシンガポールまでしかなく残念です。今後対応されたりするといいなぁ…。

西アメリカのサーバーの中でHetznerは歴史がありつつパフォーマンスも平均点以上はちゃんと安定して出せて何故かとても安いので気に入っています。

hetzner-2410270-us-west.png

Dokkuは個人用Herokuとして以前から気に入って使っていたので、流用しました。ホスティング時にはビルドプロセスを避けるため、GitHub ActionsでDockerのビルド、ECRを経由してデプロイしています。

最近このジャンルではCoolifyが有名ですが、以前試したときは動作が安定しなかったため、今回は見送りました。ただ使用感はとても良かったので、安定/枯れてきたら積極的に移行したいなと思います。

現在はユーザーを持つアプリがないため、VPSの€7.05(3vCPU、4GB RAM)のもので10以上のコンテナを動かしていますがかなり余裕があります。直前までは€3.85の2vCPU、2GB RAMでしたが、それでぎりぎりでした。

トランザクションメール

これが意外と苦労しました。有名なサービスの一部は法人格が必要だったり、そもそもどれも高く感じではしまい送信数あたりの価格遷移表を作りつつずっと唸っていました。ただ安いものを使ってみるとスパム判定されてしまって探し直しになってしまったりして結構安かろう悪かろうにシビアなジャンルなんだなと思いました。

当初Zeptomail(¥300/10k email)を使ってみて設定も簡単でうまくいったと思ったのですがリリース直前にoutlookのサーバーだとIPがハードバウンスされていたりで届かない事が判明、こちらでは何もできそうになかったので違うサービスにしましたが、たまたま引いたくらいであれば使いやすく安いので戻したい気持ちがあります。サポートにIPの件は相談しても中々解決の動きは今のところ確認できず希望薄いかもなとは思っています。

移行先としてはMail Baby($1固定月額+¢20cent/1k mail)とSMTP2GO(1000/m 200/day free~)を試して最終的にこれらを採用しています。
Mail Babyは送る前にスパム判定が入っているのですが、これに何故か引っかかってしまいサポートと一緒に調整したりするのにとても苦労しました。まだ急なスパム判定されたりするのでその時のフォールバック先としてSMTP2GOを使っています。(性格には試したついでに残しています)。SMTP2GOの方は使いやすさやレポート画面もほどほどに使いやすく、送信料が増えた将来的なコスパも良いので払えるようになったら課金して移行してもいいのかなという気持ちです。

もし稼げるサービスになれば、もう少し信頼性の高そうなサービスへ切り替えたい気持ちもあります。

結局は認証にしか使っていないという意味でいえば、IDaaS系では認証周りのメールは合わせて無料なため、無料枠に乗るのが一番良いなとは思ってはしまいますが、別用途でメールを送りたい場合どうせ必要になるので未だ迷い中です。

ログと監視

完全にやりすぎている系です。

インフラの監視は以前Datadogで無料でVPSのメトリクスとコンテナ10個までの監視が可能でしたが、コンテナ数を超えたためNetData Cloudを利用しました(価格理由)。正直、今はなくてもそれほど困りませんが、コンテナ数無制限で$4.5/月/インスタンスなので料金体系的には良いです。ただまだVPS自体のスペックが低いので代わりにVPSのスペックを上げれば監視不要なほどに余裕がでる、みたいな感じで変な状態です。モニタリングの画面を見るのは楽しいです。

netdata.png

OpenTelemetryの送信先は検討しましたが、料金体系と見やすさを考慮し、最もコスト効率が良かったのは意外にもGCP Monitoring系のGCP TraceとLoggingの組み合わせでした。特に今現在頻繁に見ることもないものではあります。

gcp-trace.png

こちらの記事が参考になりました。この記事の半分以上を実際に送信して試しつつ、記事の通りですがGCPのものであまり困らなさそうで料金が良さそうという感じで着地しました。

フロントエンドはエラーの経緯のみやすさと慣れから一旦ほぼ脳死でSentryにしています。

ランディングページ

lp-first-view.png

ランディングページは基本ペライチ+数ページで動的要素はない、という方針でした。
一旦省略形としてRemix内にページを作成しましたが、LPはインデックスしたかったりsitemapが必要そうだったり、色なども微妙に違うということで最終的に切り離しました。Astroを試してみたかったというのも正直あります。

CI/CDやインフラの管理範囲が増えたことによる複雑さは上がりましたが、実装変更時のわかりやすさが向上したという点ではトータルではトントンがないか、やや良くなった気がしています。

ただAstroはよかったのかと言われるとその他でもできそう、みたいな気持ちは残りました。ただsitemap用のプラグインなど静的サイトを作るときのエコシステムが結構厚い感触は受けたので、具体的な乗り換える理由がでなければアップデートも活発な印象もあるので問題ないようにも思います。そもそも使い込む用途でもないので評価できるほど使ってない、という感触もあります。

ランディングページのホスティング

当初、Cloudflare PagesにアプリのフロントエンドとLPを同時にホスティングしましたが、無料プランでは並列ビルドができず、不意なデプロイタイミングのズレが発生していました。LPはどこにでも移動できるため、Firebase Hostingに変更しました。自宅から見た場合、Firebaseの方がレイテンシが低く、常に少ない誤差だったことも理由の一つです。(Pages: 50ms〜80ms、Hosting: 15ms〜25ms)

昔なかった気がしますが、FirebaseのCLIツールからのGithubのSecrets自動設定およびプレビューを含むActionの自動生成等があり、以前よりも進化していそうでした。

また、昔からかわかりませんが複数のサイトをFirebaseの1プロジェクトにまとめることができるようになっており、手頃な静的サイトは一つのプロジェクトに集約してみています。

上記から静的サイトのホスティングについては結構Firebaseが思った以上によかったという感触のまま終わりました。

以前、非商用目線での記事を書いたことがありますが、商用目線ではおサイフ事情を含めると一番良い選択肢かもしれません。

この記事を振り返りってみると今回のFirebase Hostingで個人的に気に入っているポイントとしては、ビルド時間の制約がGithub Actionに依存するため、ビルドとホスティングの両方がPay As You Goな料金体系になってさらにレイテンシーも低かったということかもしれません。

リリースまでに得られた学び

(一言でいえばこんな記事をかけるくらい迷わずにさっさと中身作るべきに尽きます)

記事に書くときりがないので省略している部分もまだまだあり、色々ぐるぐる無限に検証していたのは単純に楽しかったのと色々試せてよかったです。

一方で基本的には夢に見るようなユーザーは最初からはこないのであまり技術を悩んだりカリカリに突き詰めてもそれがコアな機能でなければ意味がなさそうという感じでした。(一方である程度こだわりたい自分もいるわけですが)

技術意外の部分ではリリース考え始めてからはユーザーつかないのどうするんだっけということでマーケの勉強を始めるいいきっかけになりました。あまりマーケティング・営業みたいな所にいい印象はなかったものの、勉強してみるとこれ全員やったほうがいいやつだったと過去やらなかったことを後悔したりもしました。あとそういう目線でSNSを見ると結構意識してアカウント育ててる人多いのか…?と思いました。出遅れがすごい。

最後にLPを作って色々価値とはなんだっけ…という部分で内容を考えることが難しかったので先にLPを作ってみる/プレスリリースを作ってみるというのは何度も聞いたことがありましたが、確かにありかなと思いました。次なにか作るときはやってみても良いかもと思いました。

とはいえ繰り返しになってしまいますが、個人としては一番の敵は横で出てくる新しいライブラリにあまり惑わされず、枯れたもので機能を作るってことですかね…そういう意味では勉強ついでだと機能に集中できず、サービスを使ってもらうということは叶わないかもしれないので割り切りも必要な気がしました。ここはだからNode.jsは嫌だみたいなのがたまに海外で盛り上がっているような気がしなくもないですが、惑わされてしまう個人としてはわかる所があったりします。

何にせよ色々課題であることをやっているせいなのかもしれませんが全部意思決定自分でできるので楽しかったです!もう少しユーザーが付くように改善、または他の作ってみたいものも型は一定できたのでどんどん作っていければと思います!

最後にこの記事の内容が楽しい、みたいな人がいれば是非雑談したいのでお声がけください!

それでは!

リンク集

アプリケーションフレームワーク・ライブラリ

ホスティング・インフラ

メールサービス

モニタリング・ロギング

share