注意
この記事の内容は、開発途中のものです。
今後、変更などを行う可能性が高いのでご注意ください。
はじめに
車両の燃費、整備履歴、経費などを一か所で管理したいと思い、
Webアプリケーション「vehicle-management」を開発しています。
個人利用を前提とした小規模なアプリですが、Cloudflare Workers + D1を使うことで、 サーバーレスかつ低コストで運用できる構成にしています。
この記事では、技術スタックや主な機能、設計上の工夫について紹介します。
技術スタック
| 項目 | 技術 |
|---|---|
| ランタイム | Cloudflare Workers |
| フレームワーク | Hono 4 |
| データベース | Cloudflare D1(SQLite) |
| ストレージ | Cloudflare R2 |
| バリデーション | Zod |
| 言語 | TypeScript(strict mode) |
| 認証 | Cloudflare Access |
| フロントエンド | AdminLTE 3 + Bootstrap 4 + jQuery |
| グラフ | Chart.js 4 |
| PWA | Service Worker |
| テスト | Vitest + Playwright |
| CI/CD | GitHub Actions |
バックエンドにはHonoを採用しました。Cloudflare Workersとの親和性が高く、 軽量ながらミドルウェアやルーティングの機能が充実しています。
データベースにはCloudflare D1(SQLiteベース)を使っています。
SQLiteの手軽さとCloudflareのエッジ配信を組み合わせることで、個人利用の規模では十分な性能が得られています。
主な機能
車両管理
複数の車両を登録・管理できます。車検証のQRコードをスキャンして登録情報を読み取る機能も実装しました。
給油記録
給油日、走行距離、給油量、単価を記録し、燃費を自動計算します。
小数点以下の走行距離にも対応しており、過去のデータも含めて燃費の推移をグラフで確認できます。
整備記録
整備日、走行距離、費用、店舗情報を記録します。
タグベースのカテゴリ分類(オイル交換、タイヤローテーションなど)に対応しており、 デフォルトで20種類以上のタグを用意しています。
整備間隔のリマインダー機能も実装しました。
月数または走行距離で間隔を設定でき、車両ごとに個別のスケジュールを上書きすることも可能です。
書類管理
車検証、保険証書などの書類をアップロードして管理できます。
PDF、画像(JPG、PNG)、Wordファイル(DOCX)に対応しており、ファイルはCloudflare R2に保存されます。
有効期限を設定すると、期限切れが近い書類のアラートを表示します。
経費管理
ユーザー定義のカテゴリで経費を分類できます。
カテゴリごとに色を設定でき、車両別・カテゴリ別の費用集計が可能です。
ダッシュボード・レポート
ダッシュボードでは、登録車両数、月間費用、燃費のサマリーを表示しています。
レポート機能では、次のグラフを提供しています。
- 燃費推移グラフ(折れ線)
- 費用比較チャート(ドーナツ)
- 月間総費用の推移(積み上げ棒グラフ)
データはCSV形式でエクスポートも可能です。
バックアップ・リストア
データベース全体をJSON形式でエクスポート・インポートできます。
バックアップの履歴はローカルに保持され、復元時にはユーザー検証を行います。
アーキテクチャ
レイヤー構成
アプリケーションは、次の4層構成で設計しています。
Routes → Services → Validators → Repositories → D1 Database
- Routes: HTTPリクエストのルーティングとレスポンスの整形
- Services: ビジネスロジックの実装
- Validators: Zodによる入力バリデーション
- Repositories: D1データベースへのアクセス(クエリの組み立て)
依存関係の注入にはファクトリパターンを使っています。
createServices()関数でサービス群を生成し、ミドルウェア経由で各ルートに注入します。
エラーハンドリング
カスタムエラークラスを定義し、エラーの種類に応じたHTTPステータスコードと JSON形式のレスポンスを返す設計にしています。
ValidationError: 入力値の検証エラー(400)NotFoundError: リソースが見つからない(404)ConflictError: 競合(409)ForbiddenError: アクセス権限なし(403)AuthenticationError: 認証エラー(401)
楽観的ロック
同時更新による競合を防ぐため、楽観的ロック(version token)を採用しています。
更新時にバージョントークンを検証し、競合が発生した場合はConflictErrorを返します。
データベース設計
主要なテーブルは次のとおりです。
| テーブル | 用途 |
|---|---|
| users | ユーザー情報 |
| vehicles | 車両情報 |
| fuel_records | 給油記録 |
| maintenance_records | 整備記録 |
| maintenance_tags | 整備カテゴリ(タグ) |
| maintenance_schedules | 車両別の整備間隔 |
| expense_categories | 経費カテゴリ |
| vehicle_expenses | 経費記録 |
| vehicle_documents | 書類メタデータ |
| vehicle_registrations | 車検証データ |
設計上の特徴として、次の点を挙げます。
- 論理削除:
deletedフラグとdeleted_atタイムスタンプによる論理削除 - 楽観的ロック:
version_tokenカラムによる競合検出 - 外部キー制約: データ整合性の維持
- インデックス: 頻繁にクエリされるカラムへのインデックス設定
マイグレーションは11ファイルで管理しており、 D1のマイグレーション機能を使ってスキーマの変更を追跡しています。
認証
認証にはCloudflare Accessを利用しています。
Cloudflare Accessが発行するJWTを検証し、ユーザーのメールアドレスを取得します。
アプリケーション側では、初回アクセス時に利用規約の同意フローを表示し、 同意後にユーザーレコードを作成します。
フロントエンド
フロントエンドは、AdminLTE 3 + Bootstrap 4をベースにした従来型のHTMLページです。
APIとの通信はjQueryの$.ajax()で行い、グラフ描画にはChart.js 4を使っています。
PWA(Progressive Web App)にも対応しており、Service Workerによるオフラインサポートを提供しています。
テスト
テストは2種類を実装しています。
- ユニットテスト(Vitest): バリデーター、サービス、リポジトリの単体テスト(13ファイル)
- E2Eテスト(Playwright): 車両CRUD、給油記録、整備記録、ダッシュボードなど主要フローのテスト(8スペック)
カバレッジの目標として、バリデーター100%、サービス90%以上、リポジトリ80%以上を設定しています。
デプロイ
GitHub Actionsで自動デプロイを構成しています。
- developブランチ: 開発環境(
vms-dev.223n.tech)に自動デプロイ - masterブランチ: 本番環境(
vms.223n.tech)に自動デプロイ
PRごとに型チェック、リント、フォーマット、ユニットテスト、ビルドを自動実行しています。
おわりに
Cloudflare Workers + D1 + R2の構成で、車両管理に必要な機能をひととおり実装しました。
Cloudflareのエコシステム内で完結しているため、インフラの管理コストが低く、 個人利用の範囲では無料枠内で運用できています。
Honoの軽量さとZodの型安全なバリデーションの組み合わせは、 Cloudflare Workers上のAPI開発に適しており、開発体験も良好でした。
今後は、整備スケジュールの通知機能や、車検証のOCR読み取りなど、 さらなる機能の追加を検討しています。