1. 件数はログインなしで見られる
  2. まだの人は付けられる
  3. すでに付けている人は409
  4. 未ログインで付けようとすると401
  5. 外すと204で本文なし
  6. 付いていなくても外すのは204
  7. 無い商品は404
  8. 件数取得でも無い商品は404
コース一覧
Express道場 fleama開発室のIssue10件
#110 新機能「いいね」を設計から

Express道場 fleama開発室のIssue10件

Node.js入門で作り上げた fleama API の開発室に、スプリントの助っ人として参加する演習専用コースです。解説はありません。1問が1件の Issue で、報告者は商店街の住人たち。依頼と完成条件だけを読み、白紙から自分で組み立てます。手が止まったときのために、方針・使う構文・部分解の3段階のヒントを各問に用意しています。ヘルスチェックから始まり、一覧API、404の扱い、並び替えとページング、入力検証、自作ミドルウェア、エラー形式の統一、所有者チェック、二重販売を止めるトランザクションを経て、最後はいいね機能をエンドポイント設計から任されるところまで、10件の Issue で Express の主要な道具を一巡します。1問15分から45分、全10問で約5時間です。Node.js入門を終えて「作れたけれど実務で通用するか不安」と感じている方に向いています。

1
今週のスプリント
01. #101 監視用ヘルスチェックが欲しい15分
02. #102 カテゴリ一覧APIが欲しい20分
03. #103 消えた商品を開くと落ちる20分
04. #104 検索に並び替えとページングを25分
05. #105 0円出品が通ってしまう25分
06. #106 調査用のアクセスログを25分
07. #107 エラーの形式がバラバラ30分
08. #108 他人の出品を値下げできてしまう30分
09. #109 同じ商品が2人に売れた35分
10. #110 新機能「いいね」を設計から45分

#110 新機能「いいね」を設計から

師範 / 目安 45分


Issue #110 新機能「いいね」を設計から

報告者: 凛(テックリード)

「スプリント最終日。新機能『いいね』を、エンドポイント設計からあなたに任せます。付ける・外す・件数取得。URL・メソッド・ステータス・重複時の挙動——設計表を書いてから実装を。レビューは私が」


ここまでの9件は仕様が決まっていました。最後は自分で決めます。

まず設計表を書く

実装より先です。第4章から繰り返してきた形になります。

したいことメソッドパス成功失敗
付けるPOST/items/:id/likes201401 / 404 / 409
外すDELETE/items/:id/likes204401 / 404
件数を見るGET/items/:id/likes200404

パスは3つとも同じで、メソッドで動作を分けています。リソースは「その商品のいいね」1つなので、これが REST らしい形です。

重複したときをどうするか

凛さんが名指しした論点です。すでにいいねを付けた人が、もう一度押したらどうしますか。

選べる道は2つあります。

選択考え方
409 で断る状態と矛盾する依頼なので拒む
何もせず成功を返す結果が同じなら成功でよい

どちらも正解です。ここでは 409 で断るほうを採ります。押した側に「すでに付いている」と伝わるほうが、画面の表示を合わせやすいからです。

外すほうは逆に、付いていないものを外そうとしても 204 にします。結果として「付いていない」状態は同じだからです。通信が途中で切れて再送されたときにも安全です。

この「同じ操作を2回しても結果が変わらない」性質は、変更系の API を設計するときに何度も出てきます。

完成条件

いいねの3エンドポイントを実装してください。

  • POST /items/:id/likes — 付ける。201 で {"itemId":1,"likes":件数}。すでに付けていれば 409 で すでにいいね済みです
  • DELETE /items/:id/likes — 外す。204 で本文なし。付いていなくても 204
  • GET /items/:id/likes — 件数を返す。200 で {"itemId":1,"likes":件数}。ログイン不要

付ける・外すはログインが要ります(401)。商品が無ければどれも 404 です。

要件

  1. 3つとも同じパスで、メソッドによって動作を分ける
  2. 付けるのは 201、重複は 409、外すのは付いていなくても 204 にする
  3. 付ける・外すは 401 → 404 の順に確かめ、件数取得はログイン不要にする

入出力例

request("GET", "/items/1/likes") → "200 {"itemId":1,"likes":1}" request("POST", "/items/1/likes", null, "matsuda") → "201 {"itemId":1,"likes":2}" request("POST", "/items/1/likes", null, "shiori") → "409 {"error":"すでにいいね済みです"}" request("POST", "/items/1/likes", null) → "401 {"error":"ログインしてください"}" request("DELETE", "/items/1/likes", null, "shiori") → "204 " request("DELETE", "/items/1/likes", null, "matsuda") → "204 " request("POST", "/items/999/likes", null, "matsuda") → "404 {"error":"商品が見つかりません"}" request("GET", "/items/999/likes") → "404 {"error":"商品が見つかりません"}"

ヒント

前のヒントを開くと次が開きます。開かずに解けると未開封クリアです

3つとも同じパスです。`app.post` `app.delete` `app.get` でメソッドごとに登録します

ヒント 2ヒント 1 を開くと読めます
ヒント 3ヒント 2 を開くと読めます
生田 陸人
監修生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア監修者プロフィールを見る →
編集 ゆめさく編集部·公開 2026/08/10

関連レッスン

  • #109 同じ商品が2人に売れた

    確認と更新の隙間をトランザクションで塞ぎ、二重販売を止めます。

  • #108 他人の出品を値下げできてしまう

    出品者だけが値下げできるよう、401と403を使い分けて守ります。

  • #107 エラーの形式がバラバラ

    バラバラだったエラー応答を、共通ハンドラで1つの形に統一します。

  • #106 調査用のアクセスログを

    全リクエストのメソッドとパスを記録するミドルウェアを自作します。

このレッスンに出てくる用語

意味があいまいなまま進んだ語は、ここから読み直せます。

  • 設計何をどう作るかを決める前工程
  • エンドポイント「この URL を叩くとこの機能が動く」入口の住所
  • URLページの住所全体
  • メソッドクラスに属する関数
  • レビューコードを読み合い品質を上げる工程
  • ビュークエリに名前をつけて再利用する仮想表
  • APIアプリ間でデータをやり取りする窓口
main.js
学習モード
エディタを読み込んでいます

メモ

#110 新機能「いいね」を設計から

⌘S で保存