Go + ginを使って簡単なAPIを作る
Goのフレームワークである、ginを使って簡易的なAPIを作成していきたいと思います。
ginとは
ginは、Goで書かれたWebアプリケーションフレームワークになります。 高速なパフォーマンス、ミドルウェアが充実、JSONのリクエストのバリデーション、ルーティングのグループ化、エラー管理、組み込みのレンダリング、拡張性があるなどの特徴を持ちます。
インストール
$ go get -u github.com/gin-gonic/gin
コード内でインポートする
import "github.com/gin-gonic/gin"
これで、ginを使用することができます、簡単ですね!
docker環境作成
Docker環境で、行いたいと思うので、以下ファイルを作成します。説明については割愛させていただきます。
ホットリロードするために、Air
を導入しています。
FROM golang:latest WORKDIR /go/src COPY ./ . RUN go install github.com/cosmtrek/air@latest CMD ["air", "-c", ".air.toml"]
version: '3' services: go: build: context: . volumes: - ./:/go/src ports: - "8080:8080"
# Config file for [Air](https://github.com/cosmtrek/air) in TOML format # Working directory # . or absolute path, please note that the directories following must be under root. root = "." tmp_dir = "tmp" [build] # Just plain old shell command. You could use `make` as well. cmd = "go build -o ./tmp/main ." # Binary file yields from `cmd`. bin = "tmp/main" # Customize binary, can setup environment variables when run your app. full_bin = "APP_ENV=dev APP_USER=air ./tmp/main" # Watch these filename extensions. include_ext = ["go", "tpl", "tmpl", "html"] # Ignore these filename extensions or directories. exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"] # Watch these directories if you specified. include_dir = [] # Exclude files. exclude_file = [] # Exclude specific regular expressions. exclude_regex = ["_test\\.go"] # Exclude unchanged files. exclude_unchanged = true # Follow symlink for directories follow_symlink = true # This log file places in your tmp_dir. log = "air.log" # It's not necessary to trigger build each time file changes if it's too frequent. delay = 1000 # ms # Stop running old binary when build errors occur. stop_on_error = true # Send Interrupt signal before killing process (windows does not support this feature) send_interrupt = false # Delay after sending Interrupt signal kill_delay = 500 # ms # Add additional arguments when running binary (bin/full_bin). Will run './tmp/main hello world'. args_bin = ["hello", "world"] [log] # Show log time time = false [color] # Customize each part's color. If no color found, use the raw app log. main = "magenta" watcher = "cyan" build = "yellow" runner = "green" [misc] # Delete tmp directory on exit clean_on_exit = true
API作成
それでは、APIの作成を行なっていきます。
package main import ( "errors" "net/http" "github.com/gin-gonic/gin" ) type book struct { ID string `json:"id"` Title string `json:"title"` Author string `json:"author"` Quantity int `json:"quantity"` } var books = []book{ {ID: "1", Title: "In Search of Lost Time", Author: "Marcel Proust", Quantity: 2}, {ID: "2", Title: "The Great Gatsby", Author: "F. Scott Fitzgerald", Quantity: 5}, {ID: "3", Title: "War and Peace", Author: "Leo Tolstoy", Quantity: 6}, } func getBooks(c *gin.Context) { c.IndentedJSON(http.StatusOK, books) } func main() { router := gin.Default() router.GET("/books", getBooks) router.Run() }
gin.Default
gin.Default
は、*gin.Engine
構造体を返します。ginでは、このEngine構造体を使って,エンドポイント、ミドルウェアなどを登録しておくことができます。
gin.Context
gin.Context
は、リクエストに関連する情報が格納されており、また応答を返すことができます。
クエリパラメータ、データ、ペイロード、ヘッダーなどが格納されていて、アクセスすることが可能です。
標準のGoで、HandleFunc
を作成する際に、引数にResponseWriter
や*http.Request
を渡すと思いますが、Context
構造体のフィールドにも存在します。
Run()
メソッドも、内部では、http.ListenAndServe()
を呼び出してます。
標準のGoで、サーバー立ち上げがわかる方なら、置き換わってると考えれば理解しやすいと思います。
コードは、localhost:8080/books
にアクセスすると、getBooks
が呼ばれます。処理としては、book構造体のスライスであるbooksをJSONとしてシリアライズして、返しています。
動かしてみます
$ docker-compose up go_1 | [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default go_1 | [GIN-debug] Listening and serving HTTP on :8080
立ち上がったので、curl
コマンドを使用し、作成したエンドポイントにカールします。
$ curl localhost:8080/books [ { "id": "1", "title": "In Search of Lost Time", "author": "Marcel Proust", "quantity": 2 }, { "id": "2", "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "quantity": 5 }, { "id": "3", "title": "War and Peace", "author": "Leo Tolstoy", "quantity": 6 } ]
インデントされたJSONが作成され返されてますね! 次は、bookに新しく、追加できるようにしていきたいと思います。
func createBook(c *gin.Context) { var newBook book if err := c.BindJSON(&newBook); err != nil { return } books = append(books, newBook) c.IndentedJSON(http.StatusCreated, newBook) } func main() { router := gin.Default() router.GET("/books", getBooks) router.POST("/books", createBook) router.Run() }
追加するための、JSONファイルを作成します。
{ "id": "4", "title": "Hamlet", "author": "William Shakespeare", "quantity": 2 }
コンテナを立ち上げます。
docker-compose up go_1 | [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default go_1 | [GIN-debug] Listening and serving HTTP on :8080
立ち上がったので、先ほど作成したJSONファイルを送信してみたと思います。
$ curl localhost:8080/books --include --header "Content-Type: application/json" -d @body.json --request "POST" HTTP/1.1 201 Created Content-Type: application/json; charset=utf-8 Date: Sat, 24 Sep 2022 06:16:13 GMT Content-Length: 96 { "id": "4", "title": "Hamlet", "author": "William Shakespeare", "quantity": 2 }
追加することができたと思います。 コードは、リクエストからきたJSONを、book構造体にバインドして、booksの中にappendして、インデントされたJSONを返します。 次は、特定の本を取得してみたいと思います。
func bookById(c *gin.Context) { id := c.Param("id") book, err := getBookById(id) if err != nil { c.IndentedJSON(http.StatusNotFound, gin.H{"message": "Book not found."}) return } c.IndentedJSON(http.StatusOK, book) } func getBookById(id string) (*book, error) { for i, b := range books { if b.ID == id { return &books[i], nil } } return nil, errors.New("book not found") } func main() { router := gin.Default() router.GET("/books", getBooks) router.GET("/books/:id", bookById) router.POST("/books", createBook) router.Run() }
コンテナを立ち上げます。
$ docker-compose up go_1 | [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default go_1 | [GIN-debug] Listening and serving HTTP on :8080
立ち上がったので、特定の本を取得してみたいと思います。
$ curl localhost:8080/books/2 { "id": "2", "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "quantity": 5 }
指定した本を取得することができました。本が存在しない場合も試してみます。存在しないidを指定してみます。
$ curl localhost:8080/books/5 { "message": "Book not found." }
"message": "Book not found."
存在しないid指定した時の動作も問題無さそうですね。
以上となります。 Goは、標準のライブラリが充実してるので、そちらを使用するのも良いと思いますが、規模が大きくなるとginなどのフレームワークを使用した方が効率よく開発出来ると思いますので、触ってみてはいかがでしょうか。