過去の記事
下記の内容を実施している前提となりますのでご了承ください
今回の内容
チュートリアルには、次のセクションが含まれています。
- API エンドポイントを設計します。
- コード用のフォルダーを作成します。
- データを作成します。
- すべてのアイテムを返すハンドラーを作成します。
- 新しいアイテムを追加するハンドラーを作成します。
- 特定のアイテムを返すハンドラーを作成します。
前提条件
- Go 1.16 以降のインストール。インストール手順については、 Go のインストールを参照してください。
- コードを編集するためのツール。お使いのテキスト エディタはどれでも問題なく動作します。
- コマンド端末です。Go は、Linux と Mac の任意のターミナル、および Windows の PowerShell または cmd で適切に動作します。
- カールツール。Linux と Mac では、これは既にインストールされているはずです。Windows では、Windows 10 Insider ビルド 17063 以降に含まれています。以前の Windows バージョンでは、インストールが必要になる場合があります。詳細については、「 Tar と Curl が Windows に登場」を参照してください。
API エンドポイントの設計
/albums
GET
– 全アルバムの一覧を取得し、JSONで返す。POST
– JSONで送られたリクエストデータから、新しいアルバムを追加する。
/albums/:id
GET
– IDでアルバムを取得し、アルバムデータをJSONで返す。
コード用のフォルダーを作成する
$ mkdir web-service-gin
$ cd web-service-gin
$ go mod init example/web-service-gin
go: creating new go.mod: module example/web-service-gin
データを作成する
チュートリアルを簡単にするために、データをメモリに保存します。より典型的な API は、データベースと対話します。
データをメモリに保存すると、サーバーを停止するたびにアルバムのセットが失われ、サーバーを起動すると再作成されることに注意してください。
package main
// album はレコードのアルバムに関するデータを表す。
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
// アルバムスライスで、レコードアルバムデータのシードとする。
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}
すべてのアイテムを返すハンドラーを作成する
クライアントが でリクエストを行ったときに、GET /albums
すべてのアルバムを JSON として返す必要があります。
これを行うには、次のように記述します。
- 応答を準備するロジック
- リクエスト パスをロジックにマップするコード
これは実行時に実行される方法の逆ですが、最初に依存関係を追加し、次にそれらに依存するコードを追加していることに注意してください。
package main
// album はレコードのアルバムに関するデータを表す。
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
// アルバムスライスで、レコードアルバムデータのシードとする。
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}
// getAlbumsは、全アルバムのリストをJSONで応答します
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
このコードでは、次のことを行います。
gin.Context
パラメータを受け取るgetAlbums関数を作成します。GinもGoも特定の関数名形式を要求していませんので、この関数には任意の名前を付けることができます。
gin.Contextは、Ginの最も重要な部分です。リクエストの詳細、JSONの検証やシリアライズなどを行います。(似たような名前ですが、これはGoの組み込みコンテキストパッケージとは異なります)。
Context.IndentedJSON
を呼び出して、構造体を JSON にシリアライズし、レスポンスに追加します。
この関数の最初の引数は、クライアントに送信したいHTTPステータスコードです。ここでは、200 OK を示すために、net/http パッケージの StatusOK 定数を渡しています。
なお、Context.IndentedJSON
をContext.JSON
の呼び出しに置き換えることで、よりコンパクトな JSON を送信することができます。実際には、インデントされた形の方がデバッグ時に作業しやすく、サイズの差も通常は小さくなります。
package main
// album はレコードのアルバムに関するデータを表す。
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
// アルバムスライスで、レコードアルバムデータのシードとする。
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.Run("localhost:8080")
}
// getAlbumsは、全アルバムのリストをJSONで応答します
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
このコードでは次のことをしています
- DefaultでGinルータを初期化する。
- GET HTTPメソッドと/albumsパスをハンドラ関数に関連付けるには、GET関数を使用します。
getAlbums 関数の名前を渡していることに注意してください。これは、getAlbums() を使って関数の結果を渡すのとは違います(括弧に注目してください)。 - Run機能を使って、ルーターをhttp.Serverにアタッチし、サーバーを起動します。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
// album はレコードのアルバムに関するデータを表す。
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
// アルバムスライスで、レコードアルバムデータのシードとする。
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}
// ルーティング
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.Run("localhost:8080")
}
// getAlbumsは、全アルバムのリストをJSONで応答します
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
main.goを保存します
そして、モジュールを取り込んで、go.modを更新します。
$ go mod tidy
go: finding module for package github.com/gin-gonic/gin
go: downloading github.com/gin-gonic/gin v1.8.1
go: found github.com/gin-gonic/gin in github.com/gin-gonic/gin v1.8.1
go: downloading github.com/gin-contrib/sse v0.1.0
go: downloading github.com/mattn/go-isatty v0.0.14
すると、go.modが次のように更新されます
module example/web-service-gin
go 1.19
require github.com/gin-gonic/gin v1.8.1
require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
コードを実行する
下記のコードの箇所でルーティングを記述しました。
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.Run("localhost:8080")
}
こちらの動作確認をしたいと思います。
まずは起動前に実行します。下記の表示されたら問題ありません。
$curl http://localhost:8080/albums
curl: (7) Failed to connect to localhost port 8080: Connection refused
localhost:8080は未使用なので、当然繋がりません。
逆に下記が表示されたら注意が必要です
$curl http://localhost:8080/albums
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 404 Not Found</title>
</head>
<body><h2>HTTP ERROR 404 Not Found</h2>
<table>
<tr><th>URI:</th><td>/albums</td></tr>
<tr><th>STATUS:</th><td>404</td></tr>
<tr><th>MESSAGE:</th><td>Not Found</td></tr>
<tr><th>SERVLET:</th><td>default</td></tr>
</table>
<hr/><a href="https://eclipse.org/jetty">Powered by Jetty:// 11.0.7</a><hr/>
</body>
</html>
この場合、ポート番号8080は使用中です。Dockerを閉じたり、8080ポートをkillしてください。
問題がないことを確認したところで、起動します。go run .
でmain()を実行します
$ go run .
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /albums --> main.getAlbums (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on localhost:8080
無事に起動できたら、下記を実行します
$curl http://localhost:8080/albums
すると、先ほど定義した配列がreturnされます。
$curl http://localhost:8080/albums
[
{
"id": "1",
"title": "Blue Train",
"artist": "John Coltrane",
"price": 56.99
},
{
"id": "2",
"title": "Jeru",
"artist": "Gerry Mulligan",
"price": 17.99
},
{
"id": "3",
"title": "Sarah Vaughan and Clifford Brown",
"artist": "Sarah Vaughan",
"price": 39.99
}
]%
これで一覧取得処理は完了です
Create処理を実装する
クライアントから にリクエストがあった場合POST
、/albums
リクエストボディに記載されたアルバムを既存のアルバムのデータに追加したい。
これを行うには、次のように記述します。
- 新しいアルバムを既存のリストに追加するロジック。
POST
リクエストをロジックにルーティングするための少しのコード。
コードを書く
次のコードを追加します
func postAlbums(c *gin.Context) {
var newAlbum album
// Call BindJSON to bind the received JSON to
// newAlbum.
if err := c.BindJSON(&newAlbum); err != nil {
return
}
// Add the new album to the slice.
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
このコードでは、次のことを行います。
Context.BindJSON
リクエスト本文を にバインドするために使用しnewAlbum
ます。album
JSON から初期化された構造体をalbums
スライスに追加します。- 追加し
201
たアルバムを表す JSON と共に、ステータス コードを応答に追加します。
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.POST("/albums", postAlbums) // 追加
router.Run("localhost:8080")
}
$ go run .
上記実施しないと、Not Foundになります
curl http://localhost:8080/albums \
--include \
--header "Content-Type: application/json" \
--request "POST" \
--data '{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price": 49.99}'
HTTP/1.1 404 Not Found
Content-Type: text/plain
Date: Tue, 04 Oct 2022 07:23:40 GMT
Content-Length: 18
$curl http://localhost:8080/albums \
--include \
--header "Content-Type: application/json" \
--request "POST" \
--data '{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price": 49.99}'
すると、下記のレスポンスが返ってきます
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Tue, 04 Oct 2022 07:24:02 GMT
Content-Length: 116
{
"id": "4",
"title": "The Modern Sound of Betty Carter",
"artist": "Betty Carter",
"price": 49.99
}%
追加されたらか確認します
$ curl http://localhost:8080/albums \
--header "Content-Type: application/json" \
--request "GET"
すると無事に表示されました
[
{
"id": "1",
"title": "Blue Train",
"artist": "John Coltrane",
"price": 56.99
},
{
"id": "2",
"title": "Jeru",
"artist": "Gerry Mulligan",
"price": 17.99
},
{
"id": "3",
"title": "Sarah Vaughan and Clifford Brown",
"artist": "Sarah Vaughan",
"price": 39.99
},
{
"id": "4",
"title": "The Modern Sound of Betty Carter",
"artist": "Betty Carter",
"price": 49.99
}
]
特定のアイテムを返すハンドラーを作成する
クライアントが にリクエストを送信したときに、パス パラメータGET /albums/[id]
に一致する ID を持つアルバムを返したいとします。
これを行うには、次のことを行います。
- 要求されたアルバムを取得するロジックを追加します。
- パスをロジックにマップします。
コードを書く
このgetAlbumByID
関数は、リクエスト パスの ID を抽出し、一致するアルバムを見つけます。
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
// Loop over the list of albums, looking for
// an album whose ID value matches the parameter.
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}
このコードでは、次のことを行います。
- URL からパス パラメーター
Context.Param
を取得するために使用します。id
このハンドラーをパスにマップするときは、パラメーターのプレースホルダーをパスに含めます。 album
スライス内の構造体をループして、フィールド値がパラメーター値とID
一致する構造体を探します。id
見つかった場合は、その構造体を JSON にシリアル化し、 HTTP コードalbum
を含む応答として返します。200 OK
前述のように、実際のサービスでは、データベース クエリを使用してこのルックアップを実行する可能性があります。- アルバムが見つからない場合はHTTP
404
エラーを返します。http.StatusNotFound
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.GET("/albums/:id", getAlbumByID) // 追加
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
このコードでは、次のことを行います。
/albums/:id
パスをgetAlbumByID
関数に関連付けます。Gin では、パス内の項目の前にあるコロンは、その項目がパス パラメーターであることを示します。
まずはアプリを停止します。
その後に再度起動します
$ go run .
起動ができたら、下記を実行します。
$ curl http://localhost:8080/albums/2
すると、値を取得できました
{
"id": "2",
"title": "Jeru",
"artist": "Gerry Mulligan",
"price": 17.99
}%
これで実装は以上です
次に取り組む
おめでとう!Go と Gin を使用して、単純な RESTful Web サービスを作成しました。
次のチュートリアルに進んでいきましょう!
コメント