Step4:出品APIを作る - ch0cobanana/mercari-build-training GitHub Wiki
$ curl -X POST 'http://127.0.0.1:9000/items'
とすると、期待しているコール成功時の{"message": "item received: "}とは異なる、name is requiredというレスポンスが返ってくる。以下のようにコマンドを修正すると期待通りに。
$ curl \
-X POST \
--url 'http://localhost:9000/items' \
-d 'name=jacket'
なぜか
func parseAddItemRequest(r *http.Request) (*AddItemRequest, error) {
req := &AddItemRequest{
Name: r.FormValue("name"),
// STEP 4-2: add a category field
}
// STEP 4-4: add an image field
// validate the request
if req.Name == "" {
return nil, errors.New("name is required")
}
// STEP 4-2: validate the category field
// STEP 4-4: validate the image field
return req, nil
}
func parseAddItemRequest内に、if req.Name == "" { return nil, errors.New("name is required") }という、nameが空であったときに"name is required"とエラーメッセージを返すように設定されている。
- GETとPOSTの違い
- GET:データの取得のみ。リクエストのコンテンツを入れてはいけない
- POST:リソースの作成。サーバーにデータを送信し、変化をもたらす。PUTメソッドと異なり、同じPOSTに成功すると、複数回の注文を行うような追加の効果が出る。
- ブラウザでhttp://127.0.0.1:9000/items にアクセスしても {"message": "item received: "} が返ってこないのはなぜ?
- アクセスしたときに返ってくるHTTPステータスコード:200
- 考えられる理由:/items の GET リクエストが / の GET にリダイレクトされ、200 OK が返った?
package app #このコードはappというパッケージ内に属することを示す。Goでは必ずpackageを指定する必要がある
import (
"encoding/json" #データをjson形式に変換したり、JSONデータをGOの構造体に変換するために使用
"errors" #エラーハンドリングのためにGo標準のerror型を作成するために使用
"fmt" #フォーマットされた文字列を出力するために使用
"log/slog" #ログを記録するためのライブラリ
"net/http" #HTTPサーバーやクライアント機能を提供する標準ライブラリ
"os" #環境変数の取得やファイル操作のためのライブラリ
"path/filepath" #ファイルやディレクトリのパスをああ使うためのライブラリ
"strings" #文字列操作のためのライブラリ
)
#Server構造体の定義
type Server struct {
// Port is the port number to listen on.
Port string
// ImageDirPath is the path to the directory storing images.
ImageDirPath string
}
// Run is a method to start the server.
// This method returns 0 if the server started successfully, and 1 otherwise.
func (s Server) Run() int {
// set up logger
logger := slog.New(slog.NewJSONHandler(os.Stderr, nil)) #loggerを作成し、標準エラー出力(os.Stderr)にJSON形式でログを出力するよう設定
slog.SetDefault(logger) #デフォルトのロガーを設定
// STEP 4-6: set the log level to DEBUG
slog.SetLogLoggerLevel(slog.LevelInfo) #ログレベルをINFOに設定
// set up CORS settings
frontURL, found := os.LookupEnv("FRONT_URL") #環境変数FRONT_URLを取得。
if !found {
frontURL = "http://localhost:3000"
} #foundがfalseの場合、つまり環境変数が設定されていない場合、デフォルトでhttp://localhost:3000を使用
// STEP 5-1: set up the database connection
// set up handlers
itemRepo := NewItemRepository() #NewItemRepositoryで新しいリポジトリインスタンスを作成
h := &Handlers{imgDirPath: s.ImageDirPath, itemRepo: itemRepo} #Handlers構造体を作成。imgDirPathにs.ImageDirPathをセット(画像ディレクトリのパス)。itemRepoにitemRepoをセット(データ管理用リポジトリ)。
// set up routes ルーティングの設定
mux := http.NewServeMux() #新しいルーティング用のServeMuxインスタンスを作成
mux.HandleFunc("GET /", h.Hello) #GET /にアクセスされたらHelloハンドラーを実行
mux.HandleFunc("POST /items", h.AddItem) #POST /itemsにアクセスされたらAddItemハンドラーを実行
mux.HandleFunc("GET /images/{filename}", h.GetImage) #GET /images/{filename}で画像を取得
// start the server サーバーの起動
slog.Info("http server started on", "port", s.Port) #サーバーが起動したことをログに記録。
err := http.ListenAndServe(":"+s.Port, simpleCORSMiddleware(simpleLoggerMiddleware(mux), frontURL, []string{"GET", "HEAD", "POST", "OPTIONS"})) #s.Portで指定されたポートでHTTPサーバーを起動。simpleCORSMiddleware(mux), frontURL, ...)でCORSとロギングのミドルウェアを起動
if err != nil {
slog.Error("failed to start server: ", "error", err)
return 1
} #サーバーの起動に失敗した場合、エラーメッセージをログに出力し、1を返す
return 0
}
type Handlers struct { #Handlersはリクエストを処理するハンドラーをまとめた構造体。
// imgDirPath is the path to the directory storing images.
imgDirPath string #画像が保存されているディレクトリのpzす
itemRepo ItemRepository #データ管理用のリポジトリ
}
type HelloResponse struct { #Helloハンドラーが返すレスポンスの構造体
Message string `json:"message"` #Massage string は”Hello World"というメッセージを格納
}
// Hello is a handler to return a Hello, world! message for GET / .
func (s *Handlers) Hello(w http.ResponseWriter, r *http.Request) {
resp := HelloResponse{Message: "Hello, world!"} #レスポンスデータを作成
err := json.NewEncoder(w).Encode(resp) #レスポンスをjsonに変換して出力
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} #エラーが発生したらhttp.Errorで500エラーを返す
}
前提として、準備されているPOST /itemsのエンドポイントは name という情報を受け取ることが出来ます。 ここで、category という情報も受け取れるように変更を加えましょう。
- name:商品の名前(string型)
- category:商品のカテゴリ(string型)
type AddItemRequest struct { #AddItemRequestはPOST /itemsリクエストのデータを格納する構造体
Name string `form:"name"` #フォームから送信された"name"フィールドの値を格納
Category string `form:"category"` #同じようにcategoryも追加
Image []byte `form:"image"` // STEP 4-4: add an image field
}
type AddItemResponse struct { #AddItemResponseはAddItemハンドラーのレスポンスデータを格納する構造体
Message string `json:"message"` #json形式でmessageフィールドとして出力される
}