「B+ Fitness」の技術構成
「B+ Fitness (ビープラスフィットネス)」という自宅でパーソナルトレーニングを受けられるWebサービスを開発しました。開発時にはCursorを活用してAIの恩恵を享受しましたが、サービス自体は人対人でトレーニングセッションを行うもので、AIを使ったサービスというわけではありません。
サービス開発に至った経緯などはnoteを書いたので、そちらを参照していただくとして、こちらの記事ではcatnoseさんに習って技術構成を紹介したいと思います。
これからWebサービスを開発する方々のご参考に少しでもなれば幸いです。
サービス開発を始める際にしずかなインターネットの技術構成を参考にさせていただいたので、結構重なっている部分はあるかと思います。
アプリケーション
Next.jsを使ったモノレポ構成で、バックエンドもNextでカバーしています。
特にモバイルアプリ化などをする予定もないので、Webhookを受け取って処理するような場合以外は基本的にAPIルートは生やさず、ServerコンポーネントからServer Actionsを直接使っています(app/_actions/user/actions.tsのように配置)。
フロントエンドはReact, Tailwind CSS, shadcn/uiを使っています。
データベース
Supabaseを使っています。
以前に作ったWebサービスではPlanetScaleを使っていましたが、無料枠がなくなってしまったので、今回はSubabaseを使うことにしました。
サービス初期はできる限り費用を抑えたいので、無料で使い始められるサービスはありがたいですね。
ORMにはPrismaを使っています。
デプロイ環境
アプリケーション
Cloud Runでデプロイしています。
ProductionとStaging用にGoogle Cloud上で2つのプロジェクトを作って環境を分けています。
Cloud Buildでトリガーを設定できるので、Github上でプルリクがmainブランチにマージされたらBuildが走るようにしています。

Stagingでは自動でデプロイまでされる設定にしていますが、Productionではビルド実行前に承認を必要とすることで、まずはStagingで動作確認をしてから手動でビルドを承認し、Productionにデプロイされるフローになっています。

それぞれの環境変数はGoogle CloudのSecret Managerを使って管理してます。
2プロジェクト合わせても今のところは毎月1,500円以下で運用ができています。
データベース
mainブランチにマージされたプルリクにmigrationファイルの変更が含まれる場合、Github Actionsが走って、migrationが実行されるようにしています。
Stagingでは自動実行されるようにしていますが、アプリケーションと同様、Productionでは手動実行にすることで、先にStagingで動作確認できるようにしています。
(※)このデプロイフローに関してはかなり改善の余地があると思います。毎回手動で実行しなければならいので、もし人間がアプリケーションのデプロイだけしてデータベースのmigrationの実行を忘れたりすると本番でサーバーエラーが発生したりしますし、後方互換性を担保しない形でカラムを落とすなどの変更を加えた場合には、アプリケーションとデータベースのデプロイの順序や実行完了までの時間差があったりすると、一時的にエラーが発生しうる状態です。
テスト
VitestとReact Testing Libraryを使ってそれぞれのServer Actionに対してテストコードを書いています。
app/_actions/user/actions.tsに対してapp/_actions/user/actions.test.tsのようにテストファイルを作るイメージです。
プルリクエストの作成時にGitHub Actionsでテスト(lintも含む)が自動実行されるようにしています。テストコードの中ではMockはあまり使わず、テスト用のデータベースをDockerで用意して実行しています。
認証
NextAuthを使ってGoogleログインのみを実装しています。
これに関しては様々なオプションがあった方が良いとは思いますが、Googleログインがあれば最大多数はカバーできるだろうという判断でこの方針にしました。
メールアドレスとパスワードでのログインはセキュリティ的にも微妙ですし、認証メールとかも実装しないといけないので、個人的にやりたくなかったです。
決済
Stripeを使って実装しています。
最初にパーソナルトレーニングの都度予約機能を実装した際は、決済ページのデザインの統一性などを考慮してStripe Elementsでカスタマイズしましたが、月額プラン(サブスク)を実装するときには決済ロジックも複雑になるので、最終的にCheckout Sessionを使うようにしました。
メール配信
Resendとreact emailを使っています。
同じ開発元なだけあって、実装はかなり簡単にできます。
ただし、Resendは1日に100件、月に3000件を超えると料金がかかり、料金自体も結構高いので、サービスが成長してきたら別の配信サービスに移行することを考えた方が良いかもしれません。
開発体制
技術構成とは異なりますが、開発体制もご紹介したいと思います。
このサービスは当初、私個人で全て開発していましたが、ある程度サービスの方向性とコードベースが固まったタイミングで他のエンジニアにも参加してもらいました。
もちろん日本のエンジニアの給与水準だと報酬をお支払いする余裕はないので、まだ給与水準の安いインドネシア人のエンジニアに手伝ってもらっています。
以前ジャカルタに居住していたので、現地の労働リソースにもアクセスできるメリットを活用させていただきました。
外国人に物理的に日本に来てもらうのはVISAの問題もあって大変なことですが、ITであれば比較的簡単に仕事を依頼できるので、人手不足の日本の状況を考えるとIT分野で率先して海外人材を活用することは大いにアリだと思っています。
もちろん技術レベルは人によりますし、タスクの分け方やコミュニケーションなどの大変さはあります。また、今でも全プルリクエストのレビューはしているので、結局時間はある程度かかってしまいますが、個人で開発に没頭していた時よりは他のことに取り組める時間が増えていると実感しています。
最後に
以上のような感じで、サービスの構成としてはできる限り初期コストがかからないように選定しました。
エンジニアとして働いている方々の中でも、すでに軌道に乗っているシステムの開発をしている場合にはインフラには触らないこともあるかと思いますし、多数のユーザーをすでに抱えているサービスの構成が必ずしも新規サービスにおいてはベストではないということもあると思うので、少しでもご参考になれば幸いです。
最後は宣伝となってしまいますが、エンジニアはパソコンの前から動かずに運動不足になりがちだと思うので、運動不足解消に課題があったら是非「B+ Fitness」を使ってみてください。
ここまで読んでいただき、ありがとうございました。
Discussion