フリーランス向けの案件・請求・入金管理アプリを個人開発しました
この記事は、フリーランス向けの案件・請求・入金管理アプリ Freelance Projects を個人開発して公開した記録です。
公開URLはこちらです。
作ったもの
フリーランスや副業ワーカーが、案件、請求書、入金予定、未入金額をまとめて管理できるWebアプリです。
主な機能は以下です。
- Cognitoによるログイン・新規登録
- 案件登録、編集、削除
- 請求ステータス管理
- 入金予定日、入金日の管理
- 未入金合計、期限超過、月別入金予定の表示
- 請求書設定の保存
- 請求書PDF出力
- 発行済み請求書履歴の保存
- 請求書番号の自動採番、手動指定、重複防止
- 退会・ユーザーデータ削除
なぜ作ったか
フリーランスの案件管理は、最初はスプレッドシートでも十分です。
ただ、案件が増えてくると、次のような確認が少しずつ面倒になります。
- 今月いくら入金される予定か
- どの案件が未入金か
- 入金予定日を過ぎていないか
- 請求書を発行済みか
- 請求書番号が重複していないか
自分用の小さな業務アプリとして使いつつ、AWS個人開発のポートフォリオにもできるものを目指しました。
技術スタック
フロントエンド
- React
- TypeScript
- Vite
- Chakra UI
- React Router
- TanStack Query
- AWS Amplify Auth
バックエンド
- AWS SAM
- API Gateway HTTP API
- Lambda
- Python 3.12
- DynamoDB
- Cognito
配信・運用
- S3
- CloudFront
- CloudFront Origin Access Control
- CloudFrontアクセスログ
- DynamoDB PITR
- DynamoDB削除保護
構成
ざっくりした構成は以下です。
Browser
|
| CloudFront
v
S3 frontend bucket
Browser
|
| Authorization: Cognito ID token
v
API Gateway HTTP API
|
v
Lambda Python 3.12
|
v
DynamoDB
Cognito
|
v
Managed login
フロントエンドはS3に配置し、CloudFront経由で配信しています。
APIはAPI Gateway HTTP API + Lambdaで作り、ログイン済みユーザーのデータだけをDynamoDBから読み書きします。
データ分離
ユーザーごとのデータ分離は、Cognitoの sub を使っています。
フロントエンドから user_id のような値は渡さず、バックエンド側でCognito Authorizerのclaimsからユーザーを取得します。
DynamoDBでは、ユーザー単位のパーティションに案件や請求書設定を保存しています。
請求書番号の重複防止
請求書番号は自動採番も手動指定もできます。
手動指定できるようにすると、同じユーザー内で請求書番号が重複する可能性があります。
そこで、案件保存や請求書発行時に、請求書番号の予約アイテムをDynamoDBへ条件付きで書き込むようにしました。
PK = USER#<user_sub>
SK = INVOICE_NUMBER#<invoice_number>
これにより、同時リクエストでも同じ請求書番号を二重に保存しないようにしています。
請求書PDF
請求書PDFは、案件情報と請求書設定をもとに出力します。
PDF関連の処理は初期表示では読み込まず、必要になったタイミングで遅延読み込みするようにしました。
これにより、通常の画面表示時の読み込みを少し軽くしています。
また、請求書発行時には案件情報と請求書設定をスナップショットとして保存しています。
後から案件情報を編集しても、発行済み請求書は当時の内容で再出力できます。
公開前に気をつけたこと
dev/prodの取り違え防止
AWSアカウントはdevとprodで分けました。
dev:
dev用AWSアカウント
prod:
prod用AWSアカウント
本番デプロイでは、フロントエンドの環境変数がdevのAPIやCognitoを向いていないかを確認しました。
特にViteはビルド時に環境変数が埋め込まれるため、S3へアップロードする前に dist 内のJSを検索して、dev値が混ざっていないことを確認しました。
モック認証の無効化
ローカル開発ではモック認証を使えるようにしています。
ただし、production buildでは有効にならないように、コード側でも import.meta.env.DEV を条件にしています。
数値入力のホイール事故
案件の金額入力を type="number" にしていたところ、入力欄にフォーカスが残ったままスクロールすると、マウスホイールで金額が変わることがありました。
たとえば、10000 と入力したつもりが、スクロールで 9998 になって保存されるようなケースです。
このため、金額と源泉徴収額は以下のようにしました。
<Input
type="text"
inputMode="numeric"
pattern="[0-9]*"
/>
スマホでは数字キーボードを出しつつ、PCではホイールで値が勝手に変わらないようにしています。
テスト
フロントエンドとバックエンドで、最低限のテストとビルド確認を行っています。
cd frontend
npm run build
npm test -- --run
cd backend
pytest
sam validate
まだできていないこと
MVPなので、まだ作っていない機能もあります。
- メール通知
- CSVエクスポート
- 会計ソフト連携
- 領収書PDF
- 独自ドメイン
- 有料プラン
最初から全部作ると終わらないので、今回は「案件、請求、入金予定を管理できる」ことに絞りました。
今後やりたいこと
次にやりたいことは以下です。
- 独自ドメイン化
- X/Twitterカード表示の調整
- スクリーンショットを使ったLP改善
- CSVエクスポート
- 入金予定日のリマインド
- 請求書PDFのレイアウト改善
まとめ
小さなMVPですが、案件登録、入金予定、未入金確認、請求書PDF出力までをひと通り動かせるようになりました。
個人開発では、機能そのものだけでなく、認証、データ分離、dev/prod分離、デプロイ手順、公開前チェックまで含めて作ると、一気に実運用っぽくなります。
まだ改善したいところは多いですが、まずは公開できるところまで持っていけたので、ここから少しずつ育てていきます。
公開URL: