[Golang] GinでRESTのサーバを作成する

Go言語を使ってRESTサーバを作るときの備忘録

今回使うライブラリはGin Web Framework。例にもれずGo言語にWebフレームワークには様々なものがあるが、今回は最も使われているGinを使います。

本記事は下記のような人向けです。

  • Ginを使ってHTTPサーバーを始めたい
  • Go言語を使ってHTTPサーバーをサクッと作りたい
  • Go言語を使ってREST APIをサポートするバックエンドサーバーを作りたい

作業用ディレクトリの作成

今回作成するソースコード用のフォルダを作成します。

# 作業用ディレクトリの作成
mkdir restapi-srv

# Gitレポジトリのセットアップ
git init
touch README.md
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin git@github.com:eng-investor-admin/restapi-srv
git push -u origin main

# モジュールの作成とgo.modの初期化
go mod init github.com/eng-investor-admin

Ginのインストール

Githubのページに書いてあるとおりモジュールをインストールします。

参考 Gin Web Framework

作業用ディレクトリ内でモジュールをインストール。

go get -u github.com/gin-gonic/gin

これにより、go.modファイルにginが追加されます。

Ginを使ってみる

さっそく、サンプルを使用して動かしてみます。

main.go
package main

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

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

	router.GET("/ping", func(ctx *gin.Context) {
		ctx.JSON(200, gin.H{
			"message": "pong",
		})
	})

	router.Run()
}

このサンプルは大きく3つのパートに別れています。

ルータの作成

        router := gin.Default()

これは、デフォルトの設定(loggerとrecovery)のミドルウェアを使用してルータを作成しています。

サーバの処理

	router.GET("/ping", func(ctx *gin.Context) {
		ctx.JSON(200, gin.H{
			"message": "pong",
		})
	})

ルータに処理を追加するパートです。今回は/pingに対してGETリクエストが来た場合にHTTPのステータス200OKを{"message": "pong"}とともに返信するようにしています。

  • gin.Contextはginを扱う上で重要な部分で、受け取ったリクエストの内容や返答するためのコンテクストになります。
  • gin.Hmap[string]interface{}への簡単な表現
  • ctx.JSONでJSONを返しています。

サーバーの起動

	router.Run()

最後にこのHTTPサーバを起動しているパートです。

GETサンプルのテスト

実際に起動しましょう。

go run main.go

デフォルトだと8080番ポートを使用して起動します。

curlでアクセスして確認します。

$ curl -vvv http://localhost:8080/ping
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /ping HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Date: Sun, 26 Dec 2021 07:30:56 GMT
< Content-Length: 17
<
* Connection #0 to host localhost left intact
{"message":"pong"}

期待通りhttp://localhost:8080/pingにGETリクエストを送ると{"message":"pong"}が返ってきています。

POSTのデータを受け取る

先程のコードを拡張して、JSONのデータを受け取るようにします。

/userというパスに対して{"user": "testuser1"}のデータが来たとき、{"status": "testuser1"}と返すようにします。

まず、送られてくるデータの構造体を作成します。

type userRequest struct {
	User string `json:"user" binding:"required"`
}

JSONでどのようなキーを使っているか指示をするためにjson:"user"、userを必須のパラメータとするため、binding:"required"が指定されています。このように指定することで、ginのShouldBindJSONで用意にデータの検証と取り出しを簡単に実施できます。

	router.POST("/user", func(ctx *gin.Context) {
		var userRequest userRequest
		if err := ctx.ShouldBindJSON(&userRequest); err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		ctx.JSON(http.StatusOK, gin.H{"status": userRequest.User})
	})

続いて、POSTの処理を行う部分を上記のように追加します。

まず、受け取ったデータの検証と取り出しを行っています。ここでエラーが出た場合には、400 Bad Requestを返すようにしています。

リクエストが正当性が確認できた場合には、200 OK{"status": "ユーザ名"}をデータして返答しています。

最後にmain.goは以下のとおりです。

main.go
package main

import (
	"net/http"

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

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

	router.GET("/ping", func(ctx *gin.Context) {
		ctx.JSON(200, gin.H{
			"message": "pong",
		})
	})

	router.POST("/user", func(ctx *gin.Context) {
		var userRequest userRequest
		if err := ctx.ShouldBindJSON(&userRequest); err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		ctx.JSON(http.StatusOK, gin.H{"status": userRequest.User})
	})

	router.Run()
}

type userRequest struct {
	User string `json:"user" binding:"required"`
}

POSTのテスト

実際に起動してテストしてみましょう。

$ curl -vvv -X POST http://localhost:8080/user -d '{"user": "testuser1"}'
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /user HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
> Content-Length: 21
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 21 out of 21 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Date: Sun, 26 Dec 2021 07:50:42 GMT
< Content-Length: 22
<
* Connection #0 to host localhost left intact
{"status":"testuser1"}

正しいリクエストを送ると200 OKが返ってきています。

$ curl -vvv -X POST http://localhost:8080/user -d '{"use": "testuser1"}'
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /user HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
> Content-Length: 20
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 20 out of 20 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 400 Bad Request
< Content-Type: application/json; charset=utf-8
< Date: Sun, 26 Dec 2021 07:51:14 GMT
< Content-Length: 98
<
* Connection #0 to host localhost left intact
{"error":"Key: 'userRequest.User' Error:Field validation for 'User' failed on the 'required' tag"}

キーをuseなどの間違ったものにすると、400 Bad Requestとエラー内容が帰ってきています。

これによりJSONをデータとして扱う簡易的なRESTのサーバが作成できました。

さいごに

Ginを使うと簡単にGo言語でHTTPサーバを作成できます。データの検証や応答処理も簡単に記述できるので、リクエストを受け取った際のビジネスロジックに集中することが可能です。次回は、Ginを使ってOIDCのアクセストークンの検証を行います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA