Closed27

100日チャレンジ day3 (マッチングアプリ)

riddle_tecriddle_tec

昨日
https://zenn.dev/gin_nazo/scraps/c76827570c4980


https://blog.framinal.life/entry/2025/04/14/154104

100日チャレンジに感化されたので、アレンジして自分でもやってみます。

やりたいこと

  • 世の中のさまざまなドメインの簡易実装をつくり、バックエンドの実装に慣れる(dbスキーマ設計や、関数の分割、使いやすいインターフェイスの切り方に慣れる
  • 設計力(これはシステムのオーバービューを先に自分で作ってaiに依頼できるようにする
  • 生成aiをつかったバイブコーティングになれる
  • 実際にやったことはzennのスクラップにまとめ、成果はzennのブログにまとめる(アプリ自体の公開は必須ではないかコードはgithubにおく)

できたもの

https://github.com/lirlia/100day_challenge_backend/tree/main/day3_matching_app

riddle_tecriddle_tec

前日に気づいた改善点

  • ディレクトリレイアウト的にバックエンド / フロントエンドのコードが混ざっててみづらいので分けよう
  • 少し複雑な機能(価格の更新が発生した時の注文確定周り)については、コードを書く前に先にどのような設計にするのかの詳細を詰めてから依頼をした方が良い。そうしないと整合性が取れないコードを書いてくることがある
    • 特にうまくいかなくて方針転換するとゴミコードの上に別の処理追加することになるせいで整合性が余計にとりづらくなる
  • prisma の migrate が intaractive で辛いので deploy を使うことにする(よくないけど)
  • AI が急に止まってこっちの入力まちになることがある、なんで〜。
  • AIがシーケンシャルにしか動かないので時間がかかる まあ1から作るとしょうがないのかなあ
  • accept し忘れる
riddle_tecriddle_tec

今日はマッチングアプリを作ってみようかな
昔の Facebook のようにスワイプであり/なしが選べるといい。

仕様をAIに出してもらう

今日はマッチングアプリ(パートナー探し)のWebアプリを作りたいです。
タップルのようにスワイプ(webなので選択式)で自分の好みと近い人を選ぶ機能を搭載したいです。

まずは、仕様の洗い出しをしてください
riddle_tecriddle_tec

AI に作ってもらった仕様


マッチングアプリ(パートナー探し)仕様案 (改訂版)

1. コンセプト

  • タップルのような、異性(または同性)のプロフィールを見て「いいね」か「スキップ」を選択し、相互に「いいね」となった場合にマッチングが成立するWebアプリケーション。

2. 主要機能

  • ユーザープロファイル:
    • 表示: 他のユーザーのプロフィール情報(氏名、年齢、自己紹介文、プロフィール写真など)をカード形式で表示する。
    • 登録/編集: (今回は簡略化のため、初期データ投入 or 固定データで代替も可)
  • スワイプ機能(Web版):
    • 表示されたユーザープロフィールに対して「いいね (Like)」(例: ハートアイコンボタン)または「スキップ (Nope)」(例: Xアイコンボタン)のアクションを選択できるボタンを配置。
    • アクションボタンは視覚的に分かりやすく、押しやすいデザインにする。
    • 一度アクションしたユーザーは、基本的には再度表示されないようにする。
  • マッチング:
    • ユーザーAがユーザーBを「いいね」し、かつユーザーBがユーザーAを「いいね」した場合にマッチングが成立する。
    • マッチング成立時に画面上に分かりやすい表示(例: モーダルウィンドウやアニメーション)を行う。
    • 追加: マッチング成立時にデスクトップ通知を表示する(ブラウザの Notification API を使用)。ユーザーが許可している場合のみ。
  • マッチングリスト:
    • 自分がマッチングした相手の一覧を表示するページ。
    • 相手のプロフィール写真と名前をリスト形式で表示する。
  • ユーザー切り替え:
    • 認証機能を実装せず、簡易的な方法(例:画面上部のドロップダウンやシンプルなボタン)で操作するユーザーを切り替えられるようにする。

3. データモデル(想定されるDBテーブル)

  • User: ユーザー情報を管理
    • id: 一意な識別子 (Int, Auto Increment)
    • name: 氏名 (String)
    • age: 年齢 (Int)
    • gender: 性別 (String - 例: "male", "female", "other")
    • bio: 自己紹介文 (String, Nullable)
    • profileImageUrl: プロフィール画像のURL (String, Nullable) - 視覚的な魅力を高めるため、Unsplash等のフリー素材やSVGアイコンなど、見栄えの良いものを数パターン用意する。
    • createdAt: 登録日時 (DateTime, default now)
    • updatedAt: 更新日時 (DateTime, auto update)
  • Swipe: ユーザー間のスワイプアクションを記録
    • id: 一意な識別子 (Int, Auto Increment)
    • swiperUserId: アクションを起こしたユーザーID (Int, Userへのリレーション)
    • swipedUserId: アクション対象となったユーザーID (Int, Userへのリレーション)
    • action: アクション種別 (String - 例: "like", "skip")
    • createdAt: アクション日時 (DateTime, default now)
    • (複合ユニーク制約: swiperUserIdswipedUserId)
  • Match: マッチング成立情報を記録
    • id: 一意な識別子 (Int, Auto Increment)
    • user1Id: マッチしたユーザー1のID (Int, Userへのリレーション)
    • user2Id: マッチしたユーザー2のID (Int, Userへのリレーション)
    • createdAt: マッチング成立日時 (DateTime, default now)
    • (複合ユニーク制約 or チェック制約: user1Id < user2Id のようにして重複を防ぐ)

4. 画面構成案

  • / (ルート): スワイプ画面
    • ヘッダー: アプリロゴ、ユーザー切り替えUI、マッチングリストへのリンク
    • メイン: プロフィールカード(大きめの写真、名前、年齢、短い自己紹介)。カードはアニメーション効果(例:左右にスライド)を伴って切り替わると良い。
    • フッター or カード下部: 「スキップ」「いいね」の大きなアイコンボタン。
  • /matches: マッチングリスト画面
    • ヘッダー: アプリロゴ、ユーザー切り替えUI、スワイプ画面へのリンク
    • メイン: マッチングしたユーザーのグリッドリスト or シンプルなリスト(プロフィール写真、名前)。

5. その他考慮事項

  • 表示対象: 自分自身や既にアクション済みのユーザーはスワイプ対象から除外する。好み(例:表示したい性別など)によるフィルタリングは今回は実装しない。
  • 初期データ: 開発・テスト用に、見た目のバリエーションがある複数のダミーユーザーデータを用意する (seed.ts)。
  • スタイリング: マッチングアプリらしいデザインを採用。明るい色、丸みを帯びた要素、ハートやXのアイコンなどを効果的に使用し、魅力的で使いやすいUIを目指す。
  • API: Next.js Route Handlers で /api/users, /api/swipes, /api/matches のようなエンドポイントを作成する想定。
  • デスクトップ通知: フロントエンド側で Notification.requestPermission() による許可確認と new Notification() による通知表示を実装する。

この仕様案で進めてよろしいでしょうか? もしよろしければ、プロジェクトのセットアップを開始します。

riddle_tecriddle_tec

gemini2.5 が全然デザイン改善してくれないので sonnet 3.7 にする

riddle_tecriddle_tec

PostCSS ってなんだ


Tailwind CSS と PostCSS の関係性をざっくり言うと:

✅ Tailwind は PostCSS の「プラグイン」として動作する

Tailwind CSS は PostCSS のエコシステム上に構築された CSS フレームワーク です。
つまり、Tailwind = PostCSS プラグインの一つ という位置づけ。

PostCSS ってそもそも何?

PostCSS は、CSSを変換(トランスパイル)するツールで、JavaScriptベースのプラグインシステムを持っています。たとえば:
• autoprefixer(ベンダープレフィックス自動付与)
• cssnano(CSS圧縮)
• postcss-nested(Sass風のネスト構文を使えるようにする)
• tailwindcss ← これもプラグインの一つ!

Tailwind は何を PostCSS にやらせてる?

Tailwind は、HTML や JS/TS ファイル内のクラス名をスキャンし、必要なユーティリティクラスだけを CSS として出力する、という処理を PostCSS プラグインとして実行しています。

// postcss.config.js の一例
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

じゃあ PostCSS なしでは Tailwind は使えない?

昔(Tailwind v1〜v2あたり)

→ PostCSS がほぼ必須だった。

今(Tailwind v3 以降)

→ PostCSS 経由じゃなくても使えるようになった!
tailwindcss-cli や vite などの統合ツールが、内部的に Tailwind を直接呼び出している。

でも、PostCSS を使う構成は今でも 柔軟性と互換性が高いので、特に複雑な構成や他の CSS 処理と組み合わせたいときには便利。

まとめ

観点 内容
Tailwind CSS PostCSS のプラグインとして動く CSS フレームワーク
PostCSS CSS を処理するための「基盤」+「プラグインシステム」
関係性 Tailwind は PostCSS 上で動く、でも最近は PostCSS なしでも使える構成が増えてる

もし「PostCSS を使う理由がわからない」とか「vite + Tailwind で PostCSS 要る?」みたいな具体的な文脈があれば、そっちに絞って説明もできるよ!

riddle_tecriddle_tec

なんかいじってたらメチャクチャになった。しんどい

riddle_tecriddle_tec

Tailwind CSS v4 をつかってなくて変なことになってるぽい

Hidden comment
riddle_tecriddle_tec
Error: Route "/api/users/[userId]/recommendations" used `params.userId`. `params` should be awaited before using its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis
    at GET (app/api/users/[userId]/recommendations/route.ts:9:33)
   7 |   { params }: { params: { userId: string } }
   8 | ) {
>  9 |   const userId = parseInt(params.userId, 10);
     |                                 ^
  10 |
  11 |   if (isNaN(userId)) {
  12 |     return NextResponse.json({ error: 'Invalid user ID' }, { status: 400 });

これが一生解決しない

https://nextjs.org/docs/messages/sync-dynamic-apis

riddle_tecriddle_tec

そもそもリクエストしてるときに
http://localhost:3001/api/users/31/recommendations ってリクエストしてるので userId ってパラメータ取れなさそうなんですがどういう想定? むしろ path からとった方がいいんじゃ?


ご指摘ありがとうございます!おっしゃる通り、[userId]URL パスの一部 であり、Next.js App Router の Route Handler では、このパスパラメータを取得するための仕組みが用意されています。

まさにその仕組みが、GET 関数の 第二引数 ({ params }: { params: { userId: string } }) なのです。

  • URL: http://localhost:3001/api/users/31/recommendations
  • Route Handler: app/api/users/[userId]/recommendations/route.ts
  • GET 関数の第二引数: { params }
    • Next.js が自動的に URL パスの [userId] 部分 (31) を解析し、params オブジェクトに入れてくれます。
    • なので、コード内で params.userId と書くと、"31" という文字列が取得できます。

したがって、現在のコード:

export async function GET(
  request: Request,
  { params }: { params: { userId: string } } // ← ここで params を受け取る
) {
  const userId = parseInt(params.userId, 10); // ← ここで params.userId を使う
  // ...
}

は、まさに URL パス (/api/users/31/...) から userId (31) を取得するための、Next.js App Router での標準的な書き方 になっています。

以前出ていたエラーメッセージは、この第二引数の「型」の書き方に関するもので、パスからパラメータを取得する方法自体は正しかったはずです。

結論:
現在のコード (params.userId を使う方法) が、パスから userId を取得する想定通りの正しい実装です。

もしかすると、エラーの原因は他にあるか、あるいは修正が完全に反映されていない可能性も考えられます。

前回、matches/route.ts も修正しましたが、エラーはまだ出ていますでしょうか? エラーメッセージが以前と同じか、あるいは変わったか、再度教えていただけますでしょうか。

riddle_tecriddle_tec

よくわからんが params を await したら解消した

export async function GET(
  request: Request,
  { params }: { params: { userId: string } }
) {
  const resolvedParams = await params; // Await the destructured params object
  const userId = parseInt(resolvedParams.userId, 10); // Use the resolved params
riddle_tecriddle_tec

マッチングアプリの難しいところ

マッチングアプリのバックエンド開発において、一般的に難しいとされるポイントを、難易度が高いと思われる順にいくつか挙げます。今回の day3_matching_app のようなシンプルな構成から、より本格的なアプリへと発展させる際に顕著になる課題です。

  1. おすすめ・レコメンデーションロジックの実装:

    • 難しさ: 単純な条件(例: 年齢、性別)だけでなく、ユーザーの行動履歴(いいね/スキップの傾向)、興味、位置情報、さらには機械学習を用いた複雑なアルゴリズムを組み合わせて、適切なマッチング候補をリアルタイムに近い形で提示する必要があるため。計算コストも高くなりやすいです。
    • なぜ難しいか: 効果的なレコメンデーションはマッチング率やユーザー満足度に直結しますが、最適なアルゴリズムを見つけ、それを効率的に実装・運用するのは非常に困難です。データの質と量が重要になり、継続的な改善も必要です。
  2. スケーラビリティの確保:

    • 難しさ: ユーザー数や「いいね」「スキップ」などのアクション数が爆発的に増加する可能性があるため、それに耐えうるデータベース設計、インフラ構成、API設計が求められます。特に、おすすめ候補の抽出やマッチング判定などの処理は負荷が高くなりやすいです。
    • なぜ難しいか: 初期段階から将来的なスケールを見越した設計が必要であり、データベースのシャーディング、キャッシング戦略、非同期処理の導入など、高度な技術知識と経験が要求されます。負荷テストやパフォーマンスチューニングも継続的に行う必要があります。
  3. リアルタイム性の実現 (マッチング通知、チャットなど):

    • 難しさ: マッチングが成立した際の即時通知や、ユーザー間のリアルタイムチャット機能を実現するには、WebSocket などの技術を用いた効率的な双方向通信基盤が必要です。
    • なぜ難しいか: コネクション管理、状態同期、サーバー負荷分散、再接続処理など、通常のHTTPリクエスト/レスポンスモデルよりも複雑な課題が多く存在します。スケーラビリティとの両立も難しいポイントです。
  4. 不正行為・スパム対策:

    • 難しさ: 不正なアカウント作成、自動化された「いいね」連打、スパムメッセージ送信などの悪意のある行為を検知し、対策を講じる必要があります。
    • なぜ難しいか: 不正行為の手法は常に進化するため、ルールベースの対策だけでは不十分な場合が多く、機械学習を用いた異常検知や、ユーザー行動の分析、通報機能との連携など、多角的なアプローチが求められます。対策が厳しすぎると正規ユーザーの利便性を損なう可能性もあります。
  5. 大量のデータ管理と効率的なクエリ:

    • 難しさ: ユーザープロフィール、写真、いいね/スキップ/マッチングの履歴、メッセージ履歴など、膨大な量のデータを効率的に保存し、高速に検索・集計できるデータベーススキーマ設計とクエリ最適化が必要です。
    • なぜ難しいか: リレーションが複雑になりがちで、特に「まだアクションしていない相手を取得する」「共通のいいねを持つ相手を探す」といったクエリは、データ量が増えるとパフォーマンスが劣化しやすいため、適切なインデックス設計やクエリチューニングが不可欠です。

これらの点は、アプリの成長と共に重要度が増していく課題です。初期のシンプルな実装では表面化しにくいですが、本格的なサービスを目指す上では避けて通れない難関と言えるでしょう。

riddle_tecriddle_tec

おすすめ・レコメンデーションロジックの実装:
不正行為・スパム対策:

ここはまず難しい、MLの分野かな。
LLM でも不正かどうか?の検知はいけそうではあるが。

スケーラビリティの確保:

マッチングのところでリレーションは増えるだろうが、SNSと違って つながり を表現しなくていい点は楽か。リレーションをNewSQLで管理するか、水平分割で管理するかぐらいかなあ。

あとは今回実装しなかったけど、マッチングしてからできること(チャットとか?)の実装もあるし、そもそも課金しないと「いいね」もできないとかもあるか。

riddle_tecriddle_tec

気づき

  • Review changes が邪魔
  • https://picsum.photos ここから自由に画像取れる
    • ただしロードが遅いので表示にラグあることも
    • svg 作成と組み合わせると楽につくれるかな
  • accept 忘れや LLM の切り替えで修復が難しくなることがある、そんなときは1から作り直した方が早い。とはいえ、LLMのコンテキスト系を次に引き渡したいので、プロンプトを吐き出させたいな

次やること

  • Cursor Setting のうちよくわかってないものを一通り見る
  • フロントのデザインについてはある程度妥協し、裏側の機能にこだわること
  • フロントのデザインのところになったら Sonnet 3.7 に切り替える
  • npm run dev によるホットリロードが有効なときと一度killしないといけない時の違いがわからないので調べる
このスクラップは2025/04/17にクローズされました