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などのフレームワークを使用した方が効率よく開発出来ると思いますので、触ってみてはいかがでしょうか。
Discussion