TAKA Blog

【Go言語】入門 - Webアプリ・gin

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

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

  • gin : 55.6k stars
  • eco : 21.6k stars


今回はstarが多いginを利用します。
基本的なコーディングはどちらのフレームワークでも変わらないので、echoを用いる方も参考になるかもしれません。

最小の構成

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

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

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

main.go

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.GET("/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("/ping"の後に記述した処理が実行されます。
つまり、クエリパラメータの情報は"/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メソッドで行います。
これはあまり使わないと思います。

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

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

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


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

// body[]byte型
body, err := 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を読み込む関数を呼ぶ必要があります。

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

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


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やクリーンアーキテクチャを用いることになると思います。