なんかいろいろと書いてくブログ

関東のどこかで働く、一般人

【Go】Gin チュートリアルをやったので、まとめ(Windows環境)

現在お仕事を頂いているプロジェクトでは
Go Lang で API を製造している

しかし、私は Go Lang は素人あり、なにもわかっていないので、
「完全に理解した」状態に持っていくために
公式チュートリアルから、Restful API を行うためのチュートリアルをなぞったので、

自分なりの解釈を追加したメモ

公式ドキュメント

この記事は基本的に公式 Tutorial をなぞっていくだけ

go.dev

前提条件

  • Go 1.16 以降がインストールされていること 過去に入れた Go の version は 1.17.6
$ go version
go version go1.17.6 windows/amd64

Go は入ってなければインストール

  • editor が入っていること
    → VSCode を使用
  • A command terminalg が使えること
    → Widows 環境でのチュートリアルを行うので CMD と bushを使用
  • The curl tool
    → Curl を使用
>curl -V
curl 7.79.1 (Windows) libcurl/7.79.1 Schannel
Release-Date: 2021-09-22

API エンドポイントの仕様

チュートリアル記載の仕様

エンドポイント HTTP メソッド 概要
/albums Get アルバムリストを取得して、Json を返す
/albums Post Json リクエストを受けて、新規アルバムを追加する
/albums/:id Get id に紐づくアルバムリストを取得して、Json を返す

プロジェクトの作成

モジュールの作成

CMD を開いて、チュートリアル用フォルダを作成するディレクトリに移動

$ mkdir web-service-gin
$ cd web-service-gin

モジュールの作成

$ go mod init example/web-service-gin
go: creating new go.mod: module example/web-service-gin

ディレクトリ直下にgo.modが作成される

go.mod

https://go.dev/doc/modules/gomod-ref

データ作成

チュートリアルで使用するデータの作成 mian.goファイルを作成して編集

  1. package main記述
package main

※基本的には、go ファイルにはpackage mainが必要とのこと

  1. 構造体の宣言
type album struct {
    ID     string  `json:"id"`
    Title  string  `json:"title"`
    Artist string  `json:"artist"`
    Price  float64 `json:"price"`
}
  1. 取得データの中身を定義
var albums = []album{
    {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
    {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
    {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

ここまでが事前準備

API の作成

getAlbums

ここからは公式 Tutorial と順番を変えてやっていく
(順番通りにやっていくと linter に怒られるので)

1.import する package をpackage mainの下に追加

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

2.依存関係の追加
コマンドラインで下記実行

$ go get .

成功すればgo get: added github.com/gin-gonic/gin v1.7.2のような応答があるはず

3.データを取得する関数の実装

func getAlbums(c *gin.Context) {
    c.IndentedJSON(http.StatusOK, albums)
}

4.エンドポイント の実装

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)

    router.Run("localhost:8080")
}

http://localhost:8080/albumsをたたくと
Get メソッドにより先ほど実装したgetAlbumsを返すエンドポイントを実装
※ここでは、関数ではなく関数名をわたす必要があるとのこと

5.動作確認 go go ファイルがあるディレクトリで下記実行

$ go run .

さらにコマンドラインより curl コマンド API をたたく

curl http://localhost:8080/albums

成功すると下記のような json が返却される

$ curl http://localhost:8080/albums
[
    {
        "id": "1",
        "title": "Blue Train",
        "artist": "John Coltrane",
        "price": 56.99
    },
    {
        "id": "2",
        "title": "Jeru",
        "artist": "Gerry Mulligan",
        "price": 17.99
    },
    {
        "id": "3",
        "title": "Sarah Vaughan and Clifford Brown",
        "artist": "Sarah Vaughan",
        "price": 39.99
    }
]

go 側のレスポンス

[GIN] 2022/01/28 - 23:31:00 | 200 |            0s |       127.0.0.1 | GET      "/albums"

200 ともに、データすべてが json とともに取得できることを確認できた

実装メモ

c.IndentedJSON(http.StatusOK, albums>

部分で、albums すべてを json としてhttp.StatusOKを添えて返す関数を実装した

postAlbums

ここからは簡単に書いていく

1.post プロセスの実装

func postAlbums(c * gin.Context) {
        var newAlbum album

        if err := c.BindJSON(&newAlbum); err != nil {
            return
        }

        albums = append(albums,newAlbum)
        c.IndentedJSON((http.StatusCreated, newAlnewAlbum))
}

2.エンドポイントの追加

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)
+    router.POST("/albums", postAlbums)

    router.Run("localhost:8080")
}

3.API をたたく
公式記載の curl コマンドは CMD からだろ Bad Request が返ってくるので、
(おそらく書き方がおかしい) git bush から実行

curl http://localhost:8080/albums \
    --include \
    --header "Content-Type: application/json" \
    --request "POST" \
    --data '{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price": 49.99}'

レスポンス

HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Fri, 28 Jan 2022 22:30:03 GMT
Content-Length: 116

{
    "id": "4",
    "title": "The Modern Sound of Betty Carter",
    "artist": "Betty Carter",
    "price": 49.99
}

この状態で、Get リクエストを送ると、メモリ内にデータが追加されたのが確認できる

実装メモ

var newAlbum album

で、album 構造体をもつ newAlbum を実装

if err := c.BindJSON(&newAlbum); err != nil {
  return
}

で、リクエスト json を構造体にバインド 失敗するとerrに 400 が格納されるので、 err != nillに引っかかってそのまま return する

バインドに成功した場合は

albums = append(albums,newAlbum)

より、newAlbum に格納されている値を albums に追加する

getAlbumByID

最後にパラメータを持たせた API の実装を行う

1.実装

func getAlbumByID( c * gin.Context) {
    id := c.Param("id")

    for _, a := range albums {
        if a.ID == id {
            c.IndentedJSON(http.StatusOK, a)
            return
        }
    }

    c.IndentedJSON(http.StatusNotFound, gin.H{"message":"album not found"})
}

2.エンドポイントの追加

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)
+   router.GET("/albums/:id", getAlbumByID)
    router.POST("/albums", postAlbums)

    router.Run("localhost:8080")
}

3.API をたたく

http://localhost:8080/albums/2

レスポンス

{
        "id": "2",
        "title": "Jeru",
        "artist": "Gerry Mulligan",
        "price": 17.99
}

ID : 2のデータが取得できる

ちなみに、存在しない ID を指定すると

>curl http://localhost:8080/albums/5
{
    "message": "album not found"
}

message が返却される

実装メモ

id := c.Param("id")

でリクエストが持っているパラメータを取得

for _, a := range albums {
  if a.ID == id {
    c.IndentedJSON(http.StatusOK, a)
    return
  }
}

albums の要素を for ループで回して ID が一致するものがあれば、json として返す

また、ID がヒットしない場合は

 c.IndentedJSON(http.StatusNotFound, gin.H{"message":"album not found"})

より、hedder に message を付与して返す