【AWS】RDSは作らない!AIとバイブコーディングで"ほぼ"0円のサーバーレス個人開発
私はランニングを習慣化しているのですが、
毎朝ベッドの中で天気予報を見ながら
「今日、どうする?」と悩む時間が
とにかく面倒で仕方がありませんでした。
「……ちょっと曇っているな。雨が降るかな?」
「気温はどれくらいだろう」
毎朝ベッドの中でスマホを掴み、
天気予報アプリと雨雲レーダーを往復しながら、
今日のメニューをどうするかウダウダと悩む。
この「迷う時間」こそが
私の脳を朝から疲れさせている!と思いそれを解消する
ランニングスケジュール作成アプリ
をGeminiと一緒に作ってみました。
毎朝ベッドの中で「今日はしる?」と悩むのをやめたい。
天気予報を何度も見に行ってしまう、あの時間の正体
気になって調べてみると、
この朝のちょっとした選択が問題である理由が、
心理学やビジネスコラムで明かされていました。
まさに私のためのような「決断疲れ(Decision Fatigue)」
という言葉に出会ったのです。
心理学やビジネスコラムが明かす「選ぶだけで脳がめちゃくちゃ疲れる」ワケ
「決断疲れ」のベースとなる考え方は、意思決定に使える精神的なエネルギーは有限であるということだ。決断を下すたびにそのエネルギーは消費され、最終的には次の取捨選択をするために必要なエネルギーが完全に枯渇したと感じるまでになる。
人間は、日々の選択や決定を繰り返すだけで
精神的なエネルギーを消費してしまうそうです。
この疲れが溜まると、意思決定のパフォーマンスが低下したり、
物事を先延ばしにしたりといった弊害を引き起こします。
つまり私は、
朝一番に「走るか、休むか」を悩んでいる時点で、
その日に使える脳のエネルギーを無駄遣いしていたわけです。
【アプリ紹介】ボタン1つでランニングスケジュールを即決してくれる相棒!
気象APIのデータから、直近7日間の快適度をガチで減点採点
私が自分で使うために開発したアプリの名前は
「朝ラン計画ナビ(morning-run-navigator)」
です(そのまますぎる名前笑)。

ユーザーが都道府県を選択してボタンをポチッと押すと、
システムが裏側で無料の気象API(Open-Meteo)にアクセス。
朝ランのコア時間帯である「朝5時〜8時」
のピンポイントな気象予測データを取得します。

取得したデータから
「降水確率の最大値」
「風速の最大値」
「体感温度の平均値」
をシビアに計算し、
独自のアルゴリズムで100点満点からの減点方式で採点します。
降水確率が高ければ大減点(傘を持って走るのは無理なので)
風が強すぎても減点(向かい風は私の心が折れるので)
気象条件が完璧なら「快適ランニング日和!」
雨なら「大人しく部屋でストレッチ!」
直近7日間のスケジュールと最適なアクションを、
データに基づいて論理的に弾き出してくれます。

気になる方は一度お試しください!
百聞は一見に如かず。
今回、AWS(Amazon Web Services)を使って公開してみました。
デザインはまだ整理できていませんが・・・
スマホからでもある程度快適に動くはず!
ぜひ触ってみてください!
(※算出ロジックが気になる方向けに、
簡単なスケジュール算出根拠を解説する
logic.html というページも用意しています)
ここからは、どのようなステップを踏んで形になったのか、
Geminiとの奮闘を記録していきます。
【第1段階】まずは画面側から!Streamlitで作った試作を、html,cssへ
まずは使い慣れたPythonだけで最速の実験(Streamlit)
最初の段階では、デザインよりも
「気象APIを叩いて減点計算するロジックが本当に正しく動くか」
を検証したかったため、
使い慣れたPythonだけでWeb画面まで一瞬で作れる
Streamlitを使用しました。
数行コードを書くだけで
APIのデータが画面のテーブルに表示されます。
まずはこの状態でロジックが正しいかを検証していきました。
そこからは、私自身の勉強のためにも!
一般的なWebの標準である
「HTML / JavaScript」へのリファクタリングに舵を切りました。
AIとのバイブコーディングで直面した「HTMLがデザインまみれになる罠」
Geminiと協力しながら機能から作ると、
どうしてもAIはHTMLファイル側に
直接スタイル(インラインデザインや大量の装飾用クラス)
を書き込んでしまう傾向がありました。
気づけば、HTMLがデザインコードまみれの複雑な状態に……。
「最初から指定すればよかった!」と次回への反省です。
「意味や骨組み(HTML)」と「装飾(CSS)」を完全に切り離す
「関心の分離(Separation of Concerns)」を徹底させたい!
ということでHTMLからデザイン要素を剥ぎ取り、
1枚の外部CSS(style.css)へ移行・整理整頓していきました。

この段階では上書きコードを提示してくれました。
修正箇所が多い場合は丸ごと上書きできるよう指示していたので、
Geminiは上書き用のクリーンなコードを提示してくれました。
「めんどくさいなぁ、表示が崩れそうだなぁ」
と思っていましたが、意外とすんなり進みました!
プロンプトで
「デザインに関する記述をHTMLに残すのは最低限にするように」
と指示し直したところ、
3往復ほどでコードの見通しは劇的に良くなり、
めでたくデザイン変更がしやすい仕様のファイル構造にできました。
sessionStorage で安全にデータを引き回す
アプリは
「トップ画面(index.html)」
「結果画面(result.html)」
「ロジック解説(logic.html)」
の3つの独立した画面で構成されています。
画面を切り替える(ページを遷移する)際、
バックエンドから返ってきた計算データを
どうやって次の画面に受け渡すかが課題だなと思っていました。
Geminiはいくつか候補を出してくれましたが、
今回はブラウザのタブが閉じられるまで
データを安全に一時保存できる
"sessionStorage方式"を採用しました。
JavaScriptがAPIからデータを受け取り、sessionStorage に保存
結果画面へジャンプ
移動先でデータを取り出してカードやグラフを描画
この軽量な状態管理によって、非常にクリーンな画面遷移が実現できました。
【第2段階】APIをAWSへ!Macからクラウドへ移るときに踏み抜いた「2つの罠」
pip --platform でバイナリの壁を狙い撃ち
ローカル(Mac)で動いたFastAPIを
AWS Lambda(サーバーレス)へデプロイする段階になり、
私が気をつけなければいけなかったのが
「OSの違いによるライブラリの動作不良」です。
Macの環境で普通に pip install で集めたライブラリ
(特に軽量化のためにコンパイルされているバイナリ部品)は、
LinuxベースのAWS Lambda上では
互換性がなくエラーを起こしてしまうことがあります。
そこで、Linux用のライブラリを名指しでダウンロードするため、
以下のオプションを駆使してパッケージをビルドしました
(結構力技でやってしまいました)。
pip install \
--platform manylinux2014_x86_64 \
--implementation cp \
--python-version 3.12 \
--only-binary=:all: \
--target . \
-r ../requirements.txtこの、OSや環境の違いによる実行エラーを、
pipのオプションでZIPに封じ込めました。
Pythonも最新にするか悩みましたが
使い慣れたバージョンでひとまずデプロイすることにしました。
FastAPIを Mangum で包んで、API Gatewayに丸投げするシンプルな構造
AWS Lambdaは通常
「1つの関数に対して1つの処理」を紐付けますが、
今回はFastAPIのルーティングを
そのままクラウド上に活かしたかったため、
Mangum(マンガム)というラッパーライブラリを仲介させました。
API Gateway側は、
受信したリクエストをすべてそのままLambdaに丸投げする
「プロキシインテグレーション(ANY・/{proxy+} ルート)」
として設定。
届いたリクエストを Mangum が受け取り、
FastAPIが普通に処理を捌く。
この2つのライブラリを合わせて、
最小限のインフラ構成で私のAPIサーバーを誕生させました。
ローカル→AWS Lambdaでたびたび起こる「ファイルが見つからないエラー」
いざAWS上でAPIを叩いたら、
案の定 {"message":"Internal Server Error"} の文字。
原因は、カレントディレクトリ(実行ルート)のズレでした。
ローカルでは backend/ フォルダの中でプログラムを動かしていたため、
隣にあるファイルを普通にインポートしていました。
ただ、AWS Lambda上ではZIPを解凍した一番外側(/var/task)
が実行ルートになってしまうため、
Pythonがファイルを探索できずにクラッシュしていたのです。
そりゃそうですよね!!!忘れてた!!
ということで、これを解決するため、プログラム(main_api.py)の最上部に以下を仕込みました。
import os
import sys
# 作業フォルダ(backend)の絶対パスを、Pythonの検索ルートへ強制的に追加
sys.path.append(os.path.dirname(os.path.abspath(__file__)))これでローカル環境とAWS環境のズレを吸収させ、
エラーを消すことができました。
フォルダ構成は何をやっても何回新しいものを作っても悩みに悩みます。
【第3段階】S3を、CloudFrontのOACで安全に開ける

AWS SAA(ソリューションアーキテクト)の教科書の知識を、そのまま個人開発にぶつけてみた
フロントエンドの画面ファイル(HTMLやCSS)
を公開するにあたり、最近勉強しているAWS SAAで
「セキュアな王道」とされる設計を、
そのまま個人開発に落とし込んでみました。
静的ファイルを格納する Amazon S3 は、
「パブリックアクセス全面ブロック」に設定。
その上で、手前に Amazon CloudFront を設置し、
OAC(Origin Access Control) を使って
「CloudFrontからのリクエストだけが通れる設定」をS3に登録しました。

これにより、S3のバケットURLを直接叩かれてもガードで弾き、
アクセスを安全なCloudFront
(自動的にHTTPからHTTPSへリダイレクトする暗号化ロード)
経由に一本化する、設計にしました。

気気になるお金の話:あえて「データベース」を作らない、賢いコストの削り方
都道府県データくらいなら小さなCSVファイルにして、24時間起動の固定費を完全にリセットできる
Webアプリ開発において、個人開発者が最も頭を悩ませるのが
「データベース(RDSなど)の維持費」ですよね!
AWSのRDSなどは、アクセスが全くない時間でも、
サーバーが24時間起動しているだけで固定費が課金されてしまいます。
今回のアプリで必要なデータは、
都道府県名と「緯度・経度」が記録されたマスタデータ(locations.csv)
のみでした。
そこで、「あえてデータベースを構築しない」構成にしています!
わずか数KBのCSVファイルですからね。
ということでバックエンドのZIPの中にコードと一緒に同梱し、
Pythonの標準ライブラリ(csv)で直接読み込させ、
メモリ上で展開しています。
「月額・缶ジュース1本分」に収まるくらいの料金試算
S3+CloudFront:CloudFront、S3は無料枠が標準付帯するため、個人開発のアクセス規模ならほぼ0円。
Lambda+API Gateway:Lambdaは毎月40万秒、API Gateway(HTTP API)も毎月100万リクエストまで無料。毎日朝ランの計算を回しても無料枠からまぁほぼでないでしょうということで、ほぼ0円。
最初の1年間はこんな感じでAWSの無料枠の恩恵をフルに受け、
独自ドメインを維持するRoute 53のホストゾーン費用
(月約70円〜100円程度のみ)
で運用が可能です。
私はもう無料枠が切れているんですが、
それでもアクセスした瞬間のミリ秒単位しか課金されないので、
何かの手違いでたくさんアクセスが来ても
せいぜい月額100円〜200円(缶ジュース1本分)という、
お財布に極めて優しい
「エコ・インフラ」を構築することができたと思います!
アプリを開発するときに
むやみやたらとEC2サーバーを立ち上げるんじゃなく
上手く無料枠や安いサービスを利用していきたいものです!
