Taka blog

プログラミングとか

【Go言語】ginを用いたWebアプリケーション入門

htmlやjsonを返すGo言語のWebアプリの作り方をご紹介したいと思います。

WEBアプリのフレームワークは複数ありますが、代表的なものはginなどです。

フレームワーク スター数 リンク
gin 55.9k https://github.com/gin-gonic/gin
beego 27.8k https://github.com/beego/beego
gokit 22.4k https://github.com/go-kit/kit
eco 21.7k https://github.com/labstack/echo

今回はstarが多いginを利用します。

目次

  1. 最小の構成
  2. エンドポイントの増やし方
  3. 各種パラメータの利用
  4. レスポンスの設定
  5. 処理で共通の値を使う
  6. パッケージ構成を変更する

最小の構成

main.goとgo.modだけでサーバーを起動することができます。

内容 コマンド
go.mod作成 go mod init 任意のプロジェクト名
ginインストール go get github.com/gin-gonic/gin

起動コマンドはいつも通りのgo run main.goもしくはgo run .です。

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func main() {
    // Engineインスタンスを生成する
    r := gin.Default()

    // エンドポイントと処理を結び付ける
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"message": "pong"})
    })

    // 起動時のポート番号を指定する
    r.Run(":8080")
}

動作確認

このファイルを追加し起動させている間にhttp://localhost:8080/pingにアクセスすると、{"message": "pong"}が返ってきます。
ブラウザのバーに上記のURLをコピペすれば、動作を確認できます。

エンドポイントの増やし方

先程はGETメソッドのpingパスの処理のみを定義しました。
この章では、エンドポイントの増やし方をご紹介します。

メソッドの種類

r.POST("/ping", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"message": "pong"})
})

どのメソッドであってもr.メソッド名で指定をします。

  • GET
  • POST
  • PUT
  • DELETE

パスの指定

/pingのようにスラッシュ始まりで記述します。

パスパラメータを利用する場合は、/ping/:id/:nameのように:パラメータ名形式で指定します。
この例の様に複数のパスパラメータを利用することもできます。
/ping/30/taroにアクセスしたとすると、idに"30"、nameに"taro"が入ります。

処理で各種パラメータを利用する

以下の3つのパラメータを取得する方法を説明いたします。

クエリパラメータ パスパラメータ フォームパラメータ

加えて、リクエスト情報全体の取得方法も書いています。

クエリパラメータ

r.GET("/ping", func(c *gin.Context) {
    id := c.Query("id")
    c.JSON(http.StatusOK, gin.H{"message": id})
})

パスが/ping?id=30&name=taroだとすると「?」以降がクエリパラメータです。
このパスにリクエストが来た場合、r.GETの第2引数に渡した関数が実行されます。
クエリパラメータの名前指定はパスパラメータとは違い、/pingのパス部分に含める必要はありません。

取得はQueryメソッドで行います。
このコード例で/ping?id=30にリクエストが来ると、idにstring型の"30"が入ります。

パスパラメータ

r.GET("/ping/:id", func(c *gin.Context) {
    id := c.Param("id")
    c.JSON(http.StatusOK, gin.H{"message": id})
})

取得はParamメソッドで行います。
パスパラメータを利用する場合は、/:パラメータ名をエンドポイントのパスに含める必要があります。

フォームパラメータ

r.POST("/ping", func(c *gin.Context) {
    id := c.PostForm("id")
    c.JSON(http.StatusOK, gin.H{"message": id})
})

取得はPostFormメソッドで行います。
これはあまり使わないと思います。

リクエスト情報を取得する

r.POST("/ping", func(c *gin.Context) {
    request := c.Request()
    c.JSON(http.StatusOK, gin.H{"message": request.URL.String()})
})

gin.Context型の変数から、 http.Request型の変数を取得できます。
RequestからボディやURLなど全ての情報を持ってくることができます。
フォームパラメータを複数取得する場合などは、こちらを使いましょう。

request.Bodyとすればボディを取得できますが、これはstring型ではなくio.ReadCloser型なので注意が必要です。
string型にするには以下のように書きます。

// body は[]byte型
body, _ := io.Readall(c.Request().Body)
s := string(body)

レスポンスの設定

レスポンスを定義しているのは以下の部分です。

c.JSON(http.StatusOK, gin.H{"message": request.URL.String()})
})

c.メソッド名で記述していて、他にもメソッドがいくつかあります。
主に使いそうなのは下の3つです。

JSON String HTML

Stringはc.String(http.StatusOK, "hoge")のように書きます。

HTMLを利用する場合には、htmlファイルを作成しておき、事前にHTMLを読み込む関数を呼ぶ必要があります。

// templatesディレクトリ配下のhtmlを読み込む
r.LoadHTMLGlob("templates/*")
 
r.GET("/ping", func(c *gin.Context) {
    c.HTML(http.StatusOK, "ping.html", gin.H{
        "message": "pong",
    })
})

c.HTMLメソッドの第2引数では、htmlファイル名を渡します。読み込んだディレクトリパスに指定したファイルが存在しなければ、レスポンスでHTMLを返せません。

第3引数gin.H{~}では、htmlに埋め込む値を記述しています。なので埋め込む値が無ければ、gin.H{}内の中身は無くても大丈夫です。

各処理に共通の値を渡す

type Config struct{
    Message string
    // 色々入れる
}

func main() {
    r := gin.Default()

    // 実際はconst定数や環境変数に設定した値を入れる
    config := Config{}

    // 関数の外の変数configを、変数内で利用できる
    r.GET("/ping", func(c *gin.Context) {
        m := config.Message

        c.JSON(http.StatusOK, gin.H{"message": m})
    })

    router.Run(":8080")
}

パッケージ構成を変更する

業務で扱う場合は、MVCやクリーンアーキテクチャを用いることになると思います。