Alam Sawame

Webアプリ開発

JP / EN / PT

お仕事募集中

日常の買い物で、こんなふうに感じたことはありませんか?

出費を1つずつ記録するのは面倒で、合計も把握しづらい。情報が増えるほど判断に迷って、結局なかなか前に進めない。

この課題を解決するために、買い物計画と予算管理に特化した Web アプリ Kaimono を作りました。


プロジェクト概要

  • プロジェクト種別: 個人開発 SaaS / ポートフォリオ
  • 担当: フルスタックを単独で実装
  • ステータス: 継続開発中
  • ユーザー: ユーザーはまだなし
  • 主なテーマ: マルチテナント構成、認証フロー、i18n 対応ルーティング
  • 技術スタック(要点): Next.js, TypeScript, Prisma, PostgreSQL, NextAuth/Auth.js, Zod

現時点の実装

  • サブドメイン型マルチテナントルーティング(テナント分離)
  • Server Actions で RBAC を強制(OWNER / ADMIN / MEMBER)
  • 3 言語対応: EN / JA / PT-BR(i18n ルーティング)
  • すべての更新処理でサーバー側 Zod バリデーション
  • Prisma スキーマ: 13 モデル / 4 enum(PostgreSQL)
  • Vercel デプロイ(Preview 環境 + Prisma マイグレーション)

主な機能

Kaimono は、次の3機能を中心に設計しています。

  1. 買い物リスト管理
    日用品を管理するシンプルな To-do。
    「今買うもの」を支出管理と同じ場所で扱えるため、アプリの入口として機能します。

  2. 予定品管理
    日用品より高額で、購入前に計画と予算判断が必要なアイテムを管理します。

  3. プロジェクト管理(コア機能)
    予定品を目的ごとにまとめ、プロジェクト単位の総予算を可視化します。
    例: 部屋の模様替え(カーペット、照明、観葉植物など)。
    各アイテムに URL も保存できるので、比較中の商品にすぐ戻れます。

さらに、Kaimono ではサブドメインベースのマルチテナント構成を採用し、他ユーザーのデータが見えないように分離しています。

team.p0r6iz89.cloud

  • team がサブドメイン
  • p0r6iz89.cloud がメインドメイン

共同利用したい場合は、Invitations ページからメンバー招待できます。


技術選定

このプロジェクトで最優先したのは、本質的な課題(計画と予算を見える化して管理しやすくすること)を、できるだけシンプルに解くことでした。

  • Next.js(フルスタック)
  • Server Actions(サーバーサイド関数)
  • Prisma ORM + DB(PostgreSQL など)
  • Zod(Server Actions / フォーム入力のスキーマ検証)

以前は Django + React も使っていましたが、小規模アプリでは「フロントを作りつつ API を都度調整する」コストが開発速度を落とす感覚がありました。

そのため Kaimono では、Server Actions から Prisma 経由で直接 DB とやり取りする構成を採用しています。これによりフロントとバックエンド間の摩擦を減らし、実装を一貫させました。

結果として、安全な CRUD実装の一貫性 を両立できています。


オープンソース

Kaimono はオープンソースで、npm + Docker でローカル起動できます。

npm install
docker compose up -d db
npx prisma migrate dev
npm run dev

リンク:

ローカル URL:

  • メインアプリ: http://localhost:3000/ja
  • テナントアプリ: http://team.localhost:3000/ja

この構成では Docker Compose は PostgreSQL(db サービス)のみ起動し、Next.js アプリ本体は npm run dev でローカル実行します。


課題と学び

TypeScript の型と整合性を守りながら、読みやすいコードを書くところから始めました。

最初の大きな課題は、誰でも使える形でマルチテナントを導入することでした。

より手早い選択肢(例: Clerk のマルチテナント機能)もありましたが、学習と制御性を重視して、サブドメイン + middleware を自前で実装しました。

middleware.ts のリクエストフローは次の通りです。

  1. ロケール処理: パスにロケールがなければ handleI18nRouting を通し、/en/ja/pt-BR のロケール付きルートに正規化。
  2. 認証ガード: req.auth?.user と公開パスを判定。
    • 未ログイン + 保護パス => /{locale}/login へリダイレクト
    • ログイン済みで /login にアクセス => /{locale}/ へリダイレクト
  3. サブドメインルーティング: team.localhostteam.example.com からテナントサブドメインを抽出。
  4. 内部 rewrite: サブドメインアクセス時にテナントプレフィックスがなければ /{locale}/s/{subdomain}{rest} に rewrite。

この rewrite によって、ユーザーには URL 構造を意識させずに、1つの App Router 構成でマルチテナントを維持できます。 サーバー境界では Zod で入力を検証し、予測可能なエラー状態を返しています。

middleware.ts Request Flow

今後も、URL ベースの OAuth フローi18n がマルチテナント設計を壊さないことを前提に、機能拡張を続ける予定です。


次の機能: AI 補助入力

現在「Essentials」や「Planned items」は手入力が必要です。オートコンプリートでも改善できますが、さらに入力負荷を下げるために、写真から商品情報を自動入力する機能を検討しています。例:

  • 商品名
  • カテゴリ
  • 推定相場価格
  • URL
  • 商品詳細