前提条件
- Go 1.16 以降のインストール。 インストール手順については、Goのインストールを参照してください。
- コードを編集するためのツール。 お使いのテキストエディタで問題ありません。
- コマンドターミナル。 GoはLinuxやMacの任意のターミナル、WindowsのPowerShellやcmdでうまく動作します。
- curlツール。 LinuxやMacでは、これがすでにインストールされているはずです。Windowsでは、Windows 10 Insiderビルド17063以降に含まれています。以前のWindowsバージョンでは、インストールが必要な場合があります。詳細については、WindowsにTarとCurlが登場を参照してください。
APIエンドポイントの設計
ビンテージレコーディングを販売するストアへのアクセスを提供するAPIを構築します。したがって、クライアントがユーザーのアルバムを取得および追加できるエンドポイントを提供する必要があります。
APIを開発する際は、通常、エンドポイントの設計から始めます。エンドポイントが理解しやすい場合、APIのユーザーはより成功します。
このチュートリアルで作成するエンドポイントは次のとおりです。
/albums
GET
– すべてのアルバムのリストを取得し、JSONとして返します。POST
– JSONとして送信されたリクエストデータから新しいアルバムを追加します。
/albums/:id
GET
– IDによってアルバムを取得し、アルバムデータをJSONとして返します。
コード用のフォルダーを作成
まず、作成するコードのプロジェクトを作成します。
- 1. コマンドプロンプトを開き、ホームディレクトリに移動します。
LinuxまたはMacの場合:
Windowsの場合:$ cd
C:\> cd %HOMEPATH%
- 2. コマンドプロンプトを使用して、web-service-ginという名前のコード用のディレクトリを作成します。
$ mkdir web-service-gin
$ cd web-service-gin
- 3. 依存関係を管理するためのモジュールを作成します。
go mod init
コマンドを実行し、コードが含まれるモジュールのパスを指定します。
このコマンドは、追加した依存関係が追跡されるgo.modファイルを作成します。モジュールパスでモジュールに名前を付ける方法については、依存関係の管理を参照してください。$ go mod init example/web-service-gin
go: creating new go.mod: module example/web-service-gin
データを作成
チュートリアルを簡単にするために、データをメモリに保存します。より典型的なAPIはデータベースと対話します。
メモリにデータを保存することは、サーバーを停止するたびにアルバムのセットが失われ、再起動時に再作成されることを意味します。
コードを書く
- 1. テキストエディタを使用して、web-serviceディレクトリにmain.goという名前のファイルを作成します。このファイルにGoコードを書きます。
- 2. main.goのファイルの先頭に、次のパッケージ宣言を貼り付けます。
スタンドアロンプログラム(ライブラリとは対照的に)は常にパッケージpackage main
main
にあります。 - 3. パッケージ宣言の下に、
album
構造体の次の宣言を貼り付けます。これを使用してメモリにアルバムデータを保存します。``````bash
// album represents data about a record album.
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
`
- 4. 先ほど追加した構造体宣言の下に、開始時に使用するデータを含む
album
構造体のスライスを貼り付けます。// albums slice to seed record album data.
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として返したいと思います。
これを行うために、次のように書きます:
- レスポンスを準備するためのロジック
- リクエストパスをロジックにマッピングするコード
これは、実行時に実行される順序とは逆ですが、最初に依存関係を追加し、その後にそれに依存するコードを追加しています。
コードを書く
- 1. 前のセクションで追加した構造体コードの下に、アルバムリストを取得するための次のコードを貼り付けます。
このgetAlbums
関数は、album
構造体のスライスからJSONを作成し、JSONをレスポンスに書き込みます。
このコードでは、次のことを行います:// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
getAlbums
関数を作成し、gin.Context
パラメータを受け取ります。この関数には任意の名前を付けることができます。GinやGoは特定の関数名形式を要求しません。gin.Context
はGinの最も重要な部分です。リクエストの詳細を運び、JSONを検証およびシリアライズします。(似た名前ですが、これはGoの組み込みcontext
パッケージとは異なります。)Context.IndentedJSON
を呼び出して、構造体をJSONにシリアライズし、レスポンスに追加します。
関数の最初の引数は、クライアントに送信したいHTTPステータスコードです。ここでは、StatusOK
定数をnet/http
パッケージから渡して200 OK
を示しています。Context.IndentedJSON
をContext.JSON
への呼び出しに置き換えて、よりコンパクトなJSONを送信することもできます。実際には、インデントされた形式はデバッグ時に扱いやすく、サイズの違いは通常小さいです。
2. main.goの上部近く、
albums
スライス宣言のすぐ下に、ハンドラ関数をエンドポイントパスに割り当てるためのコードを貼り付けます。
これにより、getAlbums
が/albums
エンドポイントパスへのリクエストを処理する関連付けが設定されます。func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.Run("localhost:8080")
}
このコードでは、次のことを行います:
3. main.goの上部近く、パッケージ宣言のすぐ下に、先ほど書いたコードをサポートするために必要なパッケージをインポートします。
最初のコード行は次のようになります:package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
- 4. main.goを保存します。
コードを実行
- 1. Ginモジュールを依存関係として追跡し始めます。
コマンドラインで、go get
を使用して、github.com/gin-gonic/ginモジュールをモジュールの依存関係として追加します。ドット引数を使用して「現在のディレクトリのコードの依存関係を取得する」ことを意味します。
Goは、前のステップで追加した$ go get .
go get: added github.com/gin-gonic/gin v1.7.2
import
宣言を満たすためにこの依存関係を解決し、ダウンロードしました。 - 2. main.goを含むディレクトリのコマンドラインから、コードを実行します。ドット引数を使用して「現在のディレクトリのコードを実行する」ことを意味します。
コードが実行されると、リクエストを送信できるHTTPサーバーが起動します。$ go run .
- 3. 新しいコマンドラインウィンドウから、
curl
を使用して、実行中のWebサービスにリクエストを行います。
コマンドは、サービスにシードしたデータを表示するはずです。$ 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
}
]
APIを開始しました!次のセクションでは、アイテムを追加するためのPOST
リクエストを処理するコードを持つ別のエンドポイントを作成します。
新しいアイテムを追加するハンドラーを書く
クライアントがPOST
リクエストを/albums
で行ったとき、リクエストボディに記載されたアルバムを既存のアルバムデータに追加したいと思います。
これを行うために、次のように書きます:
コードを書く
1. アルバムデータをアルバムのリストに追加するコードを追加します。
``````bash
// postAlbums adds an album from JSON received in the request body.
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
にバインドします。- JSONから初期化された
album
構造体をalbums
スライスに追加します。 - 追加したアルバムを表すJSONとともに、
201
ステータスコードをレスポンスに追加します。
2.
main
関数を変更して、次のようにrouter.POST
関数を含めます。func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
このコードでは、次のことを行います:
コードを実行
- 1. サーバーが前のセクションからまだ実行中の場合は、停止します。
- 2. main.goを含むディレクトリのコマンドラインから、コードを実行します。
$ go run .
3. 別のコマンドラインウィンドウから、
curl
を使用して、実行中のWebサービスにリクエストを行います。$ 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}'
コマンドは、追加されたアルバムのヘッダーとJSONを表示するはずです。
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Wed, 02 Jun 2021 00:34:12 GMT
Content-Length: 116
{
"id": "4",
"title": "The Modern Sound of Betty Carter",
"artist": "Betty Carter",
"price": 49.99
}
- 4. 前のセクションと同様に、
curl
を使用してアルバムの完全なリストを取得し、新しいアルバムが追加されたことを確認します。
コマンドは、アルバムリストを表示するはずです。$ 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
を処理するコードを追加します。
特定のアイテムを返すハンドラーを書く
クライアントがGET /albums/[id]
にリクエストを行ったとき、id
パスパラメータに一致するIDのアルバムを返したいと思います。
これを行うために、次のことを行います:
コードを書く
1. 前のセクションで追加した
postAlbums
関数の下に、特定のアルバムを取得するための次のコードを貼り付けます。
このgetAlbumByID
関数は、リクエストパスのIDを抽出し、一致するアルバムを見つけます。// getAlbumByID locates the album whose ID value matches the id
// parameter sent by the client, then returns that album as a response.
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"})
}
このコードでは、次のことを行います:
Context.Param
を使用して、URLからid
パスパラメータを取得します。このハンドラをパスにマッピングするときは、パスにパラメータのプレースホルダーを含めます。- スライス内の
album
構造体をループし、ID
フィールド値がid
パラメータ値に一致するものを探します。見つかった場合、そのalbum
構造体をJSONにシリアライズし、200 OK
HTTPコードでレスポンスとして返します。
前述のように、実際のサービスでは、データベースクエリを使用してこの検索を行うことが一般的です。 - アルバムが見つからない場合は、
http.StatusNotFound
でHTTP404
エラーを返します。
2. 最後に、
main
を変更して、パスが/albums/:id
になるようにrouter.GET
への新しい呼び出しを含めます。次の例のように。func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.GET("/albums/:id", getAlbumByID)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
このコードでは、次のことを行います:
コードを実行
- 1. サーバーが前のセクションからまだ実行中の場合は、停止します。
- 2. main.goを含むディレクトリのコマンドラインから、サーバーを起動するためにコードを実行します。
$ go run .
3. 別のコマンドラインウィンドウから、
curl
を使用して、実行中のWebサービスにリクエストを行います。$ curl http://localhost:8080/albums/2
コマンドは、使用したIDのアルバムのJSONを表示するはずです。アルバムが見つからなかった場合は、エラーメッセージを含むJSONが返されます。
{
"id": "2",
"title": "Jeru",
"artist": "Gerry Mulligan",
"price": 17.99
}
結論
おめでとうございます!GoとGinを使用して、シンプルなRESTful Webサービスを作成しました。
次に推奨されるトピック:
- Goが初めての場合は、Effective GoやGoコードの書き方に記載されている有用なベストプラクティスを見つけることができます。
- Go Tourは、Goの基本を段階的に紹介する素晴らしい入門書です。
- Ginについての詳細は、Gin Web FrameworkパッケージのドキュメントやGin Web Frameworkのドキュメントを参照してください。
完成したコード
このセクションには、このチュートリアルで構築したアプリケーションのコードが含まれています。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
// album represents data about a record album.
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
// albums slice to seed record album data.
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.GET("/albums/:id", getAlbumByID)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
// postAlbums adds an album from JSON received in the request body.
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)
}
// getAlbumByID locates the album whose ID value matches the id
// parameter sent by the client, then returns that album as a response.
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
// Loop through 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"})
}