勉強目的で自作URL短縮サービスを作ってみた

## 前提
開発経験がほとんどない運用寄りの IT エンジニアの私が、学習目的で、AI の力を借り、ChatGPT にコードの雛形を生成させながらウェブサービスを作ってみようと思い立った。仕事で開発フローに部分的に関わることはあっても全体の流れは経験できていないので、モダンなツールを使いながら一通り経験してみたい。手始めに 10 数時間掛けて、自作 URL 短縮サービス (ミニ Bitly) を作ってみた。  
設計やコードは ChatGPT o4-mini-high から出力されたものをベースにしつつ、エラーや不具合をレビューしながら進めた。

## 全体像
### アプリケーションの全体像
- 目的：元URLを短縮コード（例：abcdef）に変換し、オリジナルへリダイレクトできるシンプルな URL 短縮サービス。
- 主な機能
    - POST /api/shorten：Pydantic→ランダムコード生成→SQLAlchemyでDB登録→短縮URL返却
    - GET /{code} ：DB検索→HTTP 302リダイレクト
    - 静的フロント：Vanilla JS＋HTML/CSSでフォームと結果表示
- 技術スタック
    - 言語／フレームワーク：Python 3.11、FastAPI、Uvicorn
    - DB：PostgreSQL（ローカルはDocker Compose上） + SQLAlchemy + Alembic
    - コンテナ：Docker + docker-compose
    - リバースプロキシ：nginx (ローカル), Render LB, Proxy (公開版)
    - フロント：Vanilla JS
    - テスト：pytest + pytest-asyncio + httpx
### 成果物
![recording.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4076557/c29eb7d2-1f86-4751-a49e-3ed1d047eee3.gif)
### アーキテクチャ
- ローカル開発版
```
┌──────────┐     HTTP     ┌───────────┐     HTTP /api   ┌──────────┐
│ Browser  │ ───────────> │   nginx   │ ─────────────> │  FastAPI │
│ (POST /api/shorten)    │(80/TCP)   │                │ (uvicorn) │
│ (GET /abcdef)           │           │                │           │
└──────────┘               │           │                └──────────┘
   │                       │           │                     │
   │ static files          │ proxy     │ fetch/save         │
   └──────────────────────>│           │<──────────────────┘
                           │           │    SQLAlchemy  
                           │           │     Query/Insert
                           │           │                     ┌─────────────┐
                           │           │                     │  PostgreSQL │
                           │           │        SQL          │ (Docker)    │
                           │           │<──────────────────┘
                           └──────────────────────────────────>│             │
                                                               └─────────────┘
```
途中で、独自のリバースプロキシでまずリクエストを受け取る Render 版との整合性を取るために、nginx を挟まず静的ファイルも FastAPI から配信する形に変更。
```
┌──────────┐     HTTP     ┌────────────┐     SQLAlchemy    ┌─────────────┐
│ Browser  │ ───────────> │  FastAPI   │<────────────────> │ PostgreSQL  │
│          │              │ (Uvicorn)  │     ORM操作       │  (Docker)   │
│          │              │  port: 80  │                   └─────────────┘
│          │              │            │
│ (GET /)  │              │  StaticFiles via /static, /    │
│ (POST /api/shorten)     │  API: /api/shorten, /{code}     │
└──────────┘              └────────────┘

```
- Render 版
```
┌──────────┐       HTTPS       ┌─────────────────┐       ┌─────────────┐
│ Browser  │ ────────────────> │ Render Router   │ ─────> │ FastAPI App │
│          │                    │ (LB & Proxy)    │       │ (uvicorn)   │
└──────────┘                    └─────────────────┘       └─────────────┘
     │                                                           │
     │ static + index.html                                       │ SQLAlchemy
     │<──────────────────────────────────────────────────────────>│
     │                                                           │
     │                                                           ▼
     │                                                    ┌─────────────┐
     │                                                    │ RenderDB    │
     └──────────────────────────────────────────────────> │ PostgreSQL  │
                                                          └─────────────┘

```

### 実装手順、所要時間
| フェーズ                  | タスク内容                                           | 所要時間 |
|-------------------------|---------------------------------------------------|--------|
| **準備**                  | GitHubリポジトリ作成、仮想環境、Dockerfile作成               | 1h     |
| **環境構築**               | Docker Compose で `db`/`web`/`nginx` 起動、最小 FastAPI 動作確認 | 2h     |
| **DB設計**                | SQLAlchemy モデル作成、Alembic 初期化＆マイグレーション適用     | 1h     |
| **API 実装**              | `POST /api/shorten`、`GET /{code}` のコア機能                  | 2h     |
| **curl テスト**           | `curl` で動作確認                         | 0.5h   |
| **フロント＆静的配信**        | Vanilla JS フォントエンド、nginx 設定調整                      | 1.5h   |
| **テスト整備**             | pytest ユニットテスト＋統合テスト                          | 2h     |
| **Render デプロイ準備**     | StaticFiles via FastAPI、リポジトリ整理                       | 0.5h   |
| **Render デプロイ**         | RenderDB 作成、Web Service 設定、環境変数設定、動作確認         | 1h     |
| **合計**                  |                                                   | ≈11.0h |

## 主な学び
コードは ChatGPT に生成してもらったものを加工しながら進めたが、特定分野を深掘りできたというよりは浅く広くメタな学びが得られた。
- GitHub, VS Code など基本的なツールの使い方
    - 触れたことはあったが、基本から抜けているので復習込みで久しぶりに触った。
    - 「.gitignore のベストプラクティス」、「VS Code でターミナル出すショートカットなんだっけ」みたいな初歩的な部分も調べながら進めた。
    - GitHub Actions でテストコードを走らせたり、プッシュされた
コードを自動的に Render にデプロイする、みたいな CI/CD の基本的な概念も実際に自分で設定して実感できた。
- Docker, docker-compose の便利さを実感
    - 検証のために nginx イメージをそのまま使ったりしたことはあったが、自分で開発したコードベースを Docker コンテナ化したのは何気に初だったかもしれない。docker-compose でまとめて起動して連携させたり、ストレージをくっつけたりといったことは初めてやった。超便利。Render へのデプロイもスムーズにできた。
- Python 仮想環境 (venv)
    - たまにローカルで動かしてる Python スクリプトが数個あったが、あまり環境を意識せずに動かしていた。今回のプロジェクトの初期で何も考えずにいろいろライブラリを入れたりアップデートしていたら、元々動いていたローカルのスクリプトが動かなくなって焦った。venv でシステム全体に影響を与えずに依存関係を分離するの重要。開発環境の再現性・移植性も高まる。
- 環境変数の管理
    - プライベートリポジトリとはいえ、コードベースは GitHub 管理したのでどの情報はコードの中に直に書かずに.env に入れるのかというのを意識した。今まで環境変数の意義よくわかってなかったから学びになった。
- DB の難しさを再認識
    - 「DB マイグレーション」の概念自体を全くわかっていなかった。ローカル -> Render に移した時に当初マイグレーションをして無くて「テーブルが存在しない」エラーに遭遇した。SQLAlchemy で作ったモデルを、alembic を利用してマイグレーションすることでエラー回避。
    - Pytest 実施時にダミーのデータベースを作る必要があった
- Pytest
    - これも ChatGPT が出してきたコードをそのまま使っただけだが、ちょこちょこ修正しながらユニットテスト・統合テストを全て通すまでなかなか時間が掛かった。テストの中では、DB は In-memory SQLite を使った。
- Heroku ではなく Render を採用  
  - 無料ティアの充実、小規模プロジェクトのコストゼロ運用が可能。  
  - GitHub 連携や自動ビルド設定が数ステップで完了し、迅速にデプロイできる点を高く評価。

## 所感
- 難しい：データベースの運用、環境変数の管理
- 実際のプロジェクトを通じ、モダンなバックエンド・フロントエンド技術をハンズオンで習得できた。
- ChatGPT o4-mini-high 非常に優秀。トークン数が枯渇してコンテキストが失われるのが不安だったが、独立性の高い質問は別チャットウィンドウで訊くなどして節約してやったら特に問題は起きなかった。

## 今後
また似たようなミニプロジェクトをやってみて、エンジニアとしての幅を広げたい。
現時点では深める方向性は仕事でやりつつ、個人プロジェクトは広げる方向性で考えている。ウェブサービスを見る際やプロトタイプ作成時に、適切なツールや実装パターンをすぐに思い浮かべられるようになることを一旦の目標としている。

### 触ってみたい、気になる
- コンテナデプロイ
    - Vercel
    - ECS
    - Kubernetes / EKS
- Observability 周り
    - OpenTelemetry
    - Prometheus
- Go 言語
- AI Codign
    - Cline / Cursor
- Supabase
- 認証
    - Clerk
- React / Next.js