つくる: API主要部完成
エンドポイント一式を1つの mux に
第8章の内容を全部使います。新しい知識はありません。
プレーンテキスト
ServeMux パスで振り分ける
メソッド判定 GET と POST を分ける
JSON 構造体タグで返す
クエリ 検索で絞り込む
パス ID を取り出す
ステータス 200 / 201 / 400 / 404ここまで1本ずつ作ってきたものを、1つのサーバーにまとめます。
プレーンテキスト
GET /items 一覧(keyword で絞り込み)
GET /items/{id} 詳細(無ければ404)
POST /items 出品(検証して201)応答の形を揃える
1つの API の中で、成功もエラーも同じ形にします。
プレーンテキスト
成功 データそのもの
エラー {"error":"理由"}エラーのときだけ形が変わると、使う側が毎回確かめることになります。第8章で何度か触れた話です。
エラーを返す処理が3か所以上出てくるので、関数に切り出すと読みやすくなります。
Go
func writeError(w http.ResponseWriter, code int, message string) {
w.WriteHeader(code)
w.Write([]byte(fmt.Sprintf(`{"error":%q}`, message)))
}第4章でやった「同じ処理が2回以上出てきたら切り出す」の判断です。
ステータスコードの割り当て
プレーンテキスト
GET /items 200
GET /items/1 200
GET /items/99 404 存在しない
GET /items/abc 400 IDの形が違う
POST /items 正常 201 作った
POST /items 検証NG 400 送られた内容がおかしい404 と 400 の使い分けが要点です。「そのIDのものが無い」のか「そもそもリクエストの書き方が違う」のかで分けます。
メソッドで分ける
/items は GET と POST の両方を受けます。1つのハンドラの中で分けるか、Go 1.22 の記法で別々に登録するか、どちらでも構いません。
Go
mux.HandleFunc("GET /items", listHandler)
mux.HandleFunc("POST /items", createHandler)別々に登録するほうが、1つ1つのハンドラが短くなります。
順番に作る
プレーンテキスト
1. GET /items だけを通す
2. GET /items/{id} を足す
3. 404 と 400 を分ける
4. POST /items を足す
5. エラーの形を揃える1段ごとにテストを流してください。全部書いてから直すと、どこで崩れたのか分からなくなります。
要件
GET /itemsはクエリkeywordで絞り込む。0件でも[]を返すGET /items/{id}は数値でなければ 400、無ければ 404POST /itemsは検証を通れば 201、通らなければ 400- エラーの本文は
{"error":"理由"}の形に揃える
入出力例
callAPI("GET", "/items", "") → "200 [{"id":1,"name":"きのこ図鑑","price":1200},{"id":2,"name":"中古スマホ","price":24800}]"
callAPI("GET", "/items?keyword=中古スマホ", "") → "200 [{"id":2,"name":"中古スマホ","price":24800}]"
callAPI("GET", "/items?keyword=謎の箱", "") → "200 []"
callAPI("GET", "/items/2", "") → "200 {"id":2,"name":"中古スマホ","price":24800}"
callAPI("GET", "/items/99", "") → "404 {"error":"not found"}"
callAPI("GET", "/items/abc", "") → "400 {"error":"invalid id"}"
callAPI("POST", "/items", "{"name":"新商品","price":500}") → "201 {"id":0,"name":"新商品","price":500}"
callAPI("POST", "/items", "{"name":"","price":500}") → "400 {"error":"商品名が空です"}"ヒント
編集 ゆめさく編集部