こんにちは、KOUKIです。
Golang/ReactでMovie Appの開発をしています。
今回は、Go言語で実装したWebサーバーのルーティング処理を実装します。
尚、Udemyの「Working with React and Go (Golang)」を参考にしているので、よかったら受講してみてください。
<目次>
前回
事前準備
フォルダ/ファイル
1 2 3 4 5 6 |
touch backend-app/cmd/api/routes.go touch backend-app/cmd/api/movie-handlers.go touch backend-app/cmd/api/statusHandler.go touch backend-app/cmd/api/utilities.go mkdir backend-app/models touch backend-app/models/models.go |
モジュール
1 2 |
cd backend-app/ go get -u github.com/julienschmidt/httprouter |
ルーティング処理
前回、動作確認のために簡単なルーティング処理を実装しましたが、本格的なアプリケーション用に「julienschmidt/httprouter」パッケージを使って、ルーティング処理を実装しましょう。
設定構造体
アプリケーション全体で使用する設定の構造体を定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// cmd/api/main.go package main import ( "flag" "fmt" "log" "net/http" "os" "time" ) ... type application struct { config config logger *log.Logger } |
ルートの設定
次に、ルートを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// cmd/api/routes.go package main import ( "net/http" "github.com/julienschmidt/httprouter" ) func (app *application) routes() *httprouter.Router { router := httprouter.New() router.HandlerFunc(http.MethodGet, "/status", app.statusHandler) return router } |
ハンドラーの設定
次に、ハンドラーを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// cmd/api/statusHandler.go package main import ( "encoding/json" "net/http" ) func (app *application) statusHandler(w http.ResponseWriter, r *http.Request) { currentStatus := AppStatus{ Status: "Available", Environment: app.config.env, Version: version, } js, err := json.MarshalIndent(currentStatus, "", "\t") if err != nil { app.logger.Println(err) } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(js) } |
main処理の修正
最後にmain処理を修正しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// cmd/api/main.go ... func main() { var cfg config flag.IntVar(&cfg.port, "port", 4000, "Server port to listen on") flag.StringVar(&cfg.env, "env", "development", "Application environment (development|production)") flag.Parse() // application全体で使う設定 logger := log.New(os.Stdout, "", log.Ldate|log.Ltime) app := &application{ config: cfg, logger: logger, } srv := &http.Server{ Addr: fmt.Sprintf(":%d", cfg.port), Handler: app.routes(), IdleTimeout: time.Minute, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, } logger.Println("Starting server on port", cfg.port) err := srv.ListenAndServe() if err != nil { log.Println(err) } } |
fiberやginなどのWebフレームワークを使うのもありですが、httprouterで手組みするのもありですね。
動作確認
1 2 |
# docker コンテナを立ち上げる docker-compose up backend |
以下のパラメータで、リクエストを送ってください。
- URL: http://localhost:4000/status
- 形式: GET

Movieデータの取得
ルーティング処理で学んだことを活かして、Movieデータを取得する処理を実装しましょう。
Movie App構造体
Movie App構造体を定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// models/models.go package models import "time" type Movie struct { ID int `json:"id"` Title string `json:"title"` Description string `json:"description"` Year int `json:"year"` ReleaseDate time.Time `json:"release_date"` Runtime int `json:"runtimme"` Rating int `json:"rating"` MPAARating string `json:"mpaa_rating"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` MovieGenre []MovieGenre `json:"-"` } type Genre struct { ID int `json:"id"` GenreName string `json:"genre_name"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type MovieGenre struct { ID int `json:"id"` MovieID string `json:"movie_id"` GenreID string `json:"genre_id"` Genre Genre `json:"genre"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } |
Movieデータ(個別)の取得
Movieデータを取得する処理を実装します。
まずは、ルーティングです。
1 2 3 4 5 6 7 8 9 10 11 |
// cmd/api/routes.go ... func (app *application) routes() *httprouter.Router { ... router.HandlerFunc(http.MethodGet, "/v1/movie/:id", app.getOneMovie) return router } |
「http://localhost:4000/v1/movie/<id>」を指定してリクエストすると、特定のMovieデータが取得できるようにしました。「:id」の部分はGoで取り出すことができます。
次にハンドラーの作成です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// cmd/api/movie-handlers.go package main import ( "backend/models" "errors" "net/http" "strconv" "time" "github.com/julienschmidt/httprouter" ) func (app *application) getOneMovie(w http.ResponseWriter, r *http.Request) { params := httprouter.ParamsFromContext(r.Context()) // urlからidを抜きだす id, err := strconv.Atoi(params.ByName("id")) if err != nil { app.logger.Print(errors.New("invalid id parameter")) } app.logger.Println("id is", id) // 仮のデータ movie := models.Movie{ ID: id, Title: "Some movie", Description: "Some description", Year: 2021, ReleaseDate: time.Date(2021, 01, 01, 01, 0, 0, 0, time.Local), Runtime: 100, Rating: 5, MPAARating: "PG-13", CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = app.writeJSON(w, http.StatusOK, movie, "movie") if err != nil { app.errorJSON(w, err) return } } |
URLからIDを取り出して、仮データ(Movie)を作成し、呼び出し元に戻しています。
writeJSONの実装は、以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// cmd/api/utilities.go package main import ( "encoding/json" "net/http" ) func (app *application) writeJSON( w http.ResponseWriter, status int, data interface{}, wrap string) error { wrapper := make(map[string]interface{}) wrapper[wrap] = data js, err := json.Marshal(wrapper) if err != nil { return err } w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) w.Write(js) return nil } |
動作確認をしてみましょう。
以下のパラメータで、リクエストを送ってください。
- URL: http://localhost:4000/v1/movie/1
- 形式: GET

OKですね。
エラーハンドリング
先ほど実装したコードにエラーハンドリングを追加します。
まずは、エラーレスポンスを返却する用のメソッドを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// cmd/api/utilities.go package main import ( "encoding/json" "net/http" ) ... func (app *application) errorJSON(w http.ResponseWriter, err error) { type jsonError struct { Message string `json:"message"` } theErr := jsonError{ Message: err.Error(), } app.writeJSON(w, http.StatusBadRequest, theErr, "error") } |
これを、getOneMovieメソッドのエラーハンドリング内で呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// cmd/api/movie-handlers.go ... func (app *application) getOneMovie(w http.ResponseWriter, r *http.Request) { ... // urlからidを抜きだす id, err := strconv.Atoi(params.ByName("id")) if err != nil { app.logger.Print(errors.New("invalid id parameter")) app.errorJSON(w, err) return } app.logger.Println("id is", id) ... } |
動作確認をしましょう。
以下のパラメータで、リクエストを送ってください。
- URL: http://localhost:4000/v1/movie/nonumber
- 形式: GET

Movieデータ(全件)の取得
全てのMovieデータを取得するハンドラーも作成しておきましょう。
1 2 3 4 5 6 7 8 |
// cmd/api/routes.go ... func (app *application) routes() *httprouter.Router { ... router.HandlerFunc(http.MethodGet, "/v1/movies", app.getAllMovie) return router } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// cmd/api/movie-handlers.go ... func (app *application) getAllMovie(w http.ResponseWriter, r *http.Request) { movies := []models.Movie{ {1, "movie1", "movie1", 2021, time.Date(2021, 01, 01, 01, 0, 0, 0, time.Local), 100, 5, "PG-13", time.Now(), time.Now(), []models.MovieGenre{}}, {2, "movie2", "movie2", 2021, time.Date(2021, 01, 01, 01, 0, 0, 0, time.Local), 100, 5, "PG-13", time.Now(), time.Now(), []models.MovieGenre{}}, {3, "movie3", "movie3", 2021, time.Date(2021, 01, 01, 01, 0, 0, 0, time.Local), 100, 5, "PG-13", time.Now(), time.Now(), []models.MovieGenre{}}, } app.writeJSON(w, http.StatusOK, movies, "movie") } |
以下のパラメータで、リクエストを送ってください。
- URL: http://localhost:4000/v1/movies
- 形式: GET

次回
記事まとめ
参考書籍
ソースコード
ここまで実装したソースコードを下記に記載します。
main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
// cmd/api/main.go package main import ( "flag" "fmt" "log" "net/http" "os" "time" ) const version = "1.0.0" type config struct { port int env string } type AppStatus struct { Status string `json:"status"` Environment string `json:"environment"` Version string `json:"version"` } type application struct { config config logger *log.Logger } func main() { var cfg config flag.IntVar(&cfg.port, "port", 4000, "Server port to listen on") flag.StringVar(&cfg.env, "env", "development", "Application environment (development|production)") flag.Parse() // application全体で使う設定 logger := log.New(os.Stdout, "", log.Ldate|log.Ltime) app := &application{ config: cfg, logger: logger, } srv := &http.Server{ Addr: fmt.Sprintf(":%d", cfg.port), Handler: app.routes(), IdleTimeout: time.Minute, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, } logger.Println("Starting server on port", cfg.port) err := srv.ListenAndServe() if err != nil { log.Println(err) } } |
routes.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// cmd/api/routes.go package main import ( "net/http" "github.com/julienschmidt/httprouter" ) func (app *application) routes() *httprouter.Router { router := httprouter.New() router.HandlerFunc(http.MethodGet, "/status", app.statusHandler) router.HandlerFunc(http.MethodGet, "/v1/movie/:id", app.getOneMovie) router.HandlerFunc(http.MethodGet, "/v1/movies", app.getAllMovie) return router } |
movie-handlers.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
// cmd/api/movie-handlers.go package main import ( "backend/models" "errors" "net/http" "strconv" "time" "github.com/julienschmidt/httprouter" ) func (app *application) getOneMovie(w http.ResponseWriter, r *http.Request) { params := httprouter.ParamsFromContext(r.Context()) // urlからidを抜きだす id, err := strconv.Atoi(params.ByName("id")) if err != nil { app.logger.Print(errors.New("invalid id parameter")) app.errorJSON(w, err) return } app.logger.Println("id is", id) // 仮のデータ movie := models.Movie{ ID: id, Title: "Some movie", Description: "Some description", Year: 2021, ReleaseDate: time.Date(2021, 01, 01, 01, 0, 0, 0, time.Local), Runtime: 100, Rating: 5, MPAARating: "PG-13", CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = app.writeJSON(w, http.StatusOK, movie, "movie") if err != nil { app.errorJSON(w, err) return } } func (app *application) getAllMovie(w http.ResponseWriter, r *http.Request) { movies := []models.Movie{ {1, "movie1", "movie1", 2021, time.Date(2021, 01, 01, 01, 0, 0, 0, time.Local), 100, 5, "PG-13", time.Now(), time.Now(), []models.MovieGenre{}}, {2, "movie2", "movie2", 2021, time.Date(2021, 01, 01, 01, 0, 0, 0, time.Local), 100, 5, "PG-13", time.Now(), time.Now(), []models.MovieGenre{}}, {3, "movie3", "movie3", 2021, time.Date(2021, 01, 01, 01, 0, 0, 0, time.Local), 100, 5, "PG-13", time.Now(), time.Now(), []models.MovieGenre{}}, } app.writeJSON(w, http.StatusOK, movies, "movie") } |
statusHandler.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// cmd/api/statusHandler.go package main import ( "encoding/json" "net/http" ) func (app *application) statusHandler(w http.ResponseWriter, r *http.Request) { currentStatus := AppStatus{ Status: "Available", Environment: app.config.env, Version: version, } js, err := json.MarshalIndent(currentStatus, "", "\t") if err != nil { app.logger.Println(err) } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(js) } |
utilities.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// cmd/api/utilities.go package main import ( "encoding/json" "net/http" ) func (app *application) writeJSON( w http.ResponseWriter, status int, data interface{}, wrap string) error { wrapper := make(map[string]interface{}) wrapper[wrap] = data js, err := json.Marshal(wrapper) if err != nil { return err } w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) w.Write(js) return nil } func (app *application) errorJSON(w http.ResponseWriter, err error) { type jsonError struct { Message string `json:"message"` } theErr := jsonError{ Message: err.Error(), } app.writeJSON(w, http.StatusBadRequest, theErr, "error") } |
models.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// models/models.go package models import "time" type Movie struct { ID int `json:"id"` Title string `json:"title"` Description string `json:"description"` Year int `json:"year"` ReleaseDate time.Time `json:"release_date"` Runtime int `json:"runtimme"` Rating int `json:"rating"` MPAARating string `json:"mpaa_rating"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` MovieGenre []MovieGenre `json:"-"` } type Genre struct { ID int `json:"id"` GenreName string `json:"genre_name"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type MovieGenre struct { ID int `json:"id"` MovieID string `json:"movie_id"` GenreID string `json:"genre_id"` Genre Genre `json:"genre"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } |
コメントを残す
コメントを投稿するにはログインしてください。