😊
【開発ログ】学習カード登録にタグ機能を追加
画像アップロードの動作を確認しているうちに、タグ分けができていないことに気付きました。
そこで、タグだけ管理できるようにテーブルを分け、Word+画像+タグを同時に登録できるように実装しました。
前回
- 画像アップロードの動作確認
- 単語を DB / Firebase へ保存する処理の検証
- エラー発生時のログ確認とハンドリング方法の記録
今回
- タグ機能(検索・フィルタリング対応も検討)
- Word 登録時に既存タグを選択 or 新規タグを作成できるよう修正
- タグ一覧をフロントから取得して UI で選択可能に
① Prisma スキーマ修正
-
Tagモデルを追加 -
WordとTagを N:N 関係にする
model Tag {
id String @id @default(cuid())
name String @unique
createdAt DateTime @default(now())
words Word[] @relation("WordTags")
}
model Word {
id String @id @default(cuid())
jaSurface String
koSurface String
createdAt DateTime @default(now())
imageId String?
tags Tag[] @relation("WordTags")
}
② API 修正
単語登録の際に タグを新規登録または既存タグに接続できるように connectOrCreate を利用。
リクエスト例
{
"jaSurface": "犬",
"koSurface": "개",
"tags": ["動物", "ペット"],
"imageUrl": "...",
"storagePath": "...",
"contentType": "image/png"
}
処理抜粋
tags: {
connectOrCreate: (tags ?? []).map((t: string) => ({
where: { name: t },
create: { name: t },
})),
}
- 既存タグ → connect(再利用)
- 新規タグ → create(新規作成)
更新時は一度 set: [] で既存タグをリセットしてから再設定。
③ コンポーネント修正
- タグ一覧を API から取得
- タグボタンのトグル選択 + 新規タグ入力 (Enter)
- 選択済みタグは強調表示
- 保存後はフォームと選択状態をリセット
// タグ一覧を取得
useEffect(() => {
const fetchTags = async () => {
const res = await fetch("/api/tags");
const data = await res.json();
setAllTags(data);
};
fetchTags();
}, []);
// タグ選択トグル
const toggleTag = (tagName: string) => {
setForm((prev) => {
const already = prev.tags.includes(tagName);
return {
...prev,
tags: already
? prev.tags.filter((t) => t !== tagName)
: [...prev.tags, tagName],
};
});
};
// 新しいタグを追加
const addNewTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
e.preventDefault();
const value = e.currentTarget.value.trim();
if (value && !form.tags.includes(value)) {
setForm((prev) => ({ ...prev, tags: [...prev.tags, value] }));
}
e.currentTarget.value = "";
}
};
UI部品(抜粋)
{/* タグ選択 */}
<div className="flex flex-wrap gap-2 mb-3">
{allTags.map((tag) => (
<button
key={tag.id}
type="button"
onClick={() => toggleTag(tag.name)}
className={`px-3 py-1 rounded-full border ${
form.tags.includes(tag.name)
? "bg-gray-100"
: "bg-black text-white"
}`}
>
{tag.name}
</button>
))}
</div>
{/* 新規タグ入力 */}
<input
type="text"
placeholder="新しいタグを入力して Enter"
onKeyDown={addNewTag}
className="w-full border rounded px-3 py-1"
/>
結果
タグ選択UIのスクリーンショット

悩んだこと / 学んだこと
- Prisma の N:N 関係では connectOrCreate が便利
→ 既存なら接続、なければ新規作成して接続できる - UI/UX の観点からは、毎回タグ入力より「既存タグ選択 + 新規追加」が自然
- 保存後のタグリストを再フェッチする処理を入れて、即座に反映できるようにした
次回やること
- タグ別検索・フィルタリングの実装
- レッスンカードを API から取得
- UI デザインの再調整(ボタンの色・一覧性改善)
Discussion