前提条件

  • 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の場合:
    1. $ cd
    Windowsの場合:
    1. C:\> cd %HOMEPATH%
  • 2. コマンドプロンプトを使用して、web-service-ginという名前のコード用のディレクトリを作成します。
    1. $ mkdir web-service-gin
    2. $ cd web-service-gin
  • 3. 依存関係を管理するためのモジュールを作成します。
    go mod initコマンドを実行し、コードが含まれるモジュールのパスを指定します。
    1. $ go mod init example/web-service-gin
    2. go: creating new go.mod: module example/web-service-gin
    このコマンドは、追加した依存関係が追跡されるgo.modファイルを作成します。モジュールパスでモジュールに名前を付ける方法については、依存関係の管理を参照してください。

次に、データを処理するためのデータ構造を設計します。

データを作成

チュートリアルを簡単にするために、データをメモリに保存します。より典型的なAPIはデータベースと対話します。

メモリにデータを保存することは、サーバーを停止するたびにアルバムのセットが失われ、再起動時に再作成されることを意味します。

コードを書く

  • 1. テキストエディタを使用して、web-serviceディレクトリにmain.goという名前のファイルを作成します。このファイルにGoコードを書きます。
  • 2. main.goのファイルの先頭に、次のパッケージ宣言を貼り付けます。
    1. package main
    スタンドアロンプログラム(ライブラリとは対照的に)は常にパッケージmainにあります。
  • 3. パッケージ宣言の下に、album構造体の次の宣言を貼り付けます。これを使用してメモリにアルバムデータを保存します。
    1. ``````bash
    2. // album represents data about a record album.
    3. type album struct {
    4. ID string `json:"id"`
    5. Title string `json:"title"`
    6. Artist string `json:"artist"`
    7. Price float64 `json:"price"`
    8. }
    9. `
  • 4. 先ほど追加した構造体宣言の下に、開始時に使用するデータを含むalbum構造体のスライスを貼り付けます。
    1. // albums slice to seed record album data.
    2. var albums = []album{
    3. {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
    4. {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
    5. {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
    6. }

次に、最初のエンドポイントを実装するためのコードを書きます。

すべてのアイテムを返すハンドラーを書く

クライアントがGET /albumsでリクエストを行ったとき、すべてのアルバムをJSONとして返したいと思います。

これを行うために、次のように書きます:

  • レスポンスを準備するためのロジック
  • リクエストパスをロジックにマッピングするコード

これは、実行時に実行される順序とは逆ですが、最初に依存関係を追加し、その後にそれに依存するコードを追加しています。

コードを書く

  • 1. 前のセクションで追加した構造体コードの下に、アルバムリストを取得するための次のコードを貼り付けます。
    このgetAlbums関数は、album構造体のスライスからJSONを作成し、JSONをレスポンスに書き込みます。
    1. // getAlbums responds with the list of all albums as JSON.
    2. func getAlbums(c *gin.Context) {
    3. c.IndentedJSON(http.StatusOK, albums)
    4. }
    このコードでは、次のことを行います:
    • getAlbums関数を作成し、gin.Contextパラメータを受け取ります。この関数には任意の名前を付けることができます。GinやGoは特定の関数名形式を要求しません。
      gin.ContextはGinの最も重要な部分です。リクエストの詳細を運び、JSONを検証およびシリアライズします。(似た名前ですが、これはGoの組み込みcontextパッケージとは異なります。)
    • Context.IndentedJSONを呼び出して、構造体をJSONにシリアライズし、レスポンスに追加します。
      関数の最初の引数は、クライアントに送信したいHTTPステータスコードです。ここでは、StatusOK定数をnet/httpパッケージから渡して200 OKを示しています。
      Context.IndentedJSONContext.JSONへの呼び出しに置き換えて、よりコンパクトなJSONを送信することもできます。実際には、インデントされた形式はデバッグ時に扱いやすく、サイズの違いは通常小さいです。
  • 2. main.goの上部近く、albumsスライス宣言のすぐ下に、ハンドラ関数をエンドポイントパスに割り当てるためのコードを貼り付けます。
    これにより、getAlbums/albumsエンドポイントパスへのリクエストを処理する関連付けが設定されます。

    1. func main() {
    2. router := gin.Default()
    3. router.GET("/albums", getAlbums)
    4. router.Run("localhost:8080")
    5. }

    このコードでは、次のことを行います:

    • Defaultを使用してGinルーターを初期化します。
    • GET関数を使用して、GET HTTPメソッドと/albumsパスをハンドラ関数に関連付けます。
      getAlbums関数の名前を渡しています。これは、関数の結果を渡すのとは異なります。結果を渡す場合は、getAlbums()を渡します(括弧に注意)。
    • Run関数を使用して、ルーターをhttp.Serverに接続し、サーバーを起動します。
  • 3. main.goの上部近く、パッケージ宣言のすぐ下に、先ほど書いたコードをサポートするために必要なパッケージをインポートします。
    最初のコード行は次のようになります:

    1. package main
    2. import (
    3. "net/http"
    4. "github.com/gin-gonic/gin"
    5. )
  • 4. main.goを保存します。

コードを実行

  • 1. Ginモジュールを依存関係として追跡し始めます。
    コマンドラインで、go getを使用して、github.com/gin-gonic/ginモジュールをモジュールの依存関係として追加します。ドット引数を使用して「現在のディレクトリのコードの依存関係を取得する」ことを意味します。
    1. $ go get .
    2. go get: added github.com/gin-gonic/gin v1.7.2
    Goは、前のステップで追加したimport宣言を満たすためにこの依存関係を解決し、ダウンロードしました。
  • 2. main.goを含むディレクトリのコマンドラインから、コードを実行します。ドット引数を使用して「現在のディレクトリのコードを実行する」ことを意味します。
    1. $ go run .
    コードが実行されると、リクエストを送信できるHTTPサーバーが起動します。
  • 3. 新しいコマンドラインウィンドウから、curlを使用して、実行中のWebサービスにリクエストを行います。
    1. $ curl http://localhost:8080/albums
    コマンドは、サービスにシードしたデータを表示するはずです。
    1. [
    2. {
    3. "id": "1",
    4. "title": "Blue Train",
    5. "artist": "John Coltrane",
    6. "price": 56.99
    7. },
    8. {
    9. "id": "2",
    10. "title": "Jeru",
    11. "artist": "Gerry Mulligan",
    12. "price": 17.99
    13. },
    14. {
    15. "id": "3",
    16. "title": "Sarah Vaughan and Clifford Brown",
    17. "artist": "Sarah Vaughan",
    18. "price": 39.99
    19. }
    20. ]

APIを開始しました!次のセクションでは、アイテムを追加するためのPOSTリクエストを処理するコードを持つ別のエンドポイントを作成します。

新しいアイテムを追加するハンドラーを書く

クライアントがPOSTリクエストを/albumsで行ったとき、リクエストボディに記載されたアルバムを既存のアルバムデータに追加したいと思います。

これを行うために、次のように書きます:

  • 既存のリストに新しいアルバムを追加するためのロジック。
  • POSTリクエストをロジックにルーティングするための少しのコード。

コードを書く

  • 1. アルバムデータをアルバムのリストに追加するコードを追加します。

    1. ``````bash
    2. // postAlbums adds an album from JSON received in the request body.
    3. func postAlbums(c *gin.Context) {
    4. var newAlbum album
    5. // Call BindJSON to bind the received JSON to
    6. // newAlbum.
    7. if err := c.BindJSON(&newAlbum); err != nil {
    8. return
    9. }
    10. // Add the new album to the slice.
    11. albums = append(albums, newAlbum)
    12. c.IndentedJSON(http.StatusCreated, newAlbum)
    13. }
    14. `

    このコードでは、次のことを行います:

    • Context.BindJSONを使用して、リクエストボディをnewAlbumにバインドします。
    • JSONから初期化されたalbum構造体をalbumsスライスに追加します。
    • 追加したアルバムを表すJSONとともに、201ステータスコードをレスポンスに追加します。
  • 2. main関数を変更して、次のようにrouter.POST関数を含めます。

    1. func main() {
    2. router := gin.Default()
    3. router.GET("/albums", getAlbums)
    4. router.POST("/albums", postAlbums)
    5. router.Run("localhost:8080")
    6. }

    このコードでは、次のことを行います:

    • POSTメソッドを/albumsパスにpostAlbums関数に関連付けます。
      Ginを使用すると、HTTPメソッドとパスの組み合わせにハンドラを関連付けることができます。このようにして、クライアントが使用しているメソッドに基づいて、単一のパスに送信されたリクエストを個別にルーティングできます。

コードを実行

  • 1. サーバーが前のセクションからまだ実行中の場合は、停止します。
  • 2. main.goを含むディレクトリのコマンドラインから、コードを実行します。
    1. $ go run .
  • 3. 別のコマンドラインウィンドウから、curlを使用して、実行中のWebサービスにリクエストを行います。

    1. $ curl http://localhost:8080/albums \
    2. --include \
    3. --header "Content-Type: application/json" \
    4. --request "POST" \
    5. --data '{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price": 49.99}'

    コマンドは、追加されたアルバムのヘッダーとJSONを表示するはずです。

    1. HTTP/1.1 201 Created
    2. Content-Type: application/json; charset=utf-8
    3. Date: Wed, 02 Jun 2021 00:34:12 GMT
    4. Content-Length: 116
    5. {
    6. "id": "4",
    7. "title": "The Modern Sound of Betty Carter",
    8. "artist": "Betty Carter",
    9. "price": 49.99
    10. }
  • 4. 前のセクションと同様に、curlを使用してアルバムの完全なリストを取得し、新しいアルバムが追加されたことを確認します。
    1. $ curl http://localhost:8080/albums \
    2. --header "Content-Type: application/json" \
    3. --request "GET"
    コマンドは、アルバムリストを表示するはずです。
    1. [
    2. {
    3. "id": "1",
    4. "title": "Blue Train",
    5. "artist": "John Coltrane",
    6. "price": 56.99
    7. },
    8. {
    9. "id": "2",
    10. "title": "Jeru",
    11. "artist": "Gerry Mulligan",
    12. "price": 17.99
    13. },
    14. {
    15. "id": "3",
    16. "title": "Sarah Vaughan and Clifford Brown",
    17. "artist": "Sarah Vaughan",
    18. "price": 39.99
    19. },
    20. {
    21. "id": "4",
    22. "title": "The Modern Sound of Betty Carter",
    23. "artist": "Betty Carter",
    24. "price": 49.99
    25. }
    26. ]

次のセクションでは、特定のアイテムのGETを処理するコードを追加します。

特定のアイテムを返すハンドラーを書く

クライアントがGET /albums/[id]にリクエストを行ったとき、idパスパラメータに一致するIDのアルバムを返したいと思います。

これを行うために、次のことを行います:

  • リクエストされたアルバムを取得するためのロジックを追加します。
  • パスをロジックにマッピングします。

コードを書く

  • 1. 前のセクションで追加したpostAlbums関数の下に、特定のアルバムを取得するための次のコードを貼り付けます。
    このgetAlbumByID関数は、リクエストパスのIDを抽出し、一致するアルバムを見つけます。

    1. // getAlbumByID locates the album whose ID value matches the id
    2. // parameter sent by the client, then returns that album as a response.
    3. func getAlbumByID(c *gin.Context) {
    4. id := c.Param("id")
    5. // Loop over the list of albums, looking for
    6. // an album whose ID value matches the parameter.
    7. for _, a := range albums {
    8. if a.ID == id {
    9. c.IndentedJSON(http.StatusOK, a)
    10. return
    11. }
    12. }
    13. c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
    14. }

    このコードでは、次のことを行います:

    • Context.Paramを使用して、URLからidパスパラメータを取得します。このハンドラをパスにマッピングするときは、パスにパラメータのプレースホルダーを含めます。
    • スライス内のalbum構造体をループし、IDフィールド値がidパラメータ値に一致するものを探します。見つかった場合、そのalbum構造体をJSONにシリアライズし、200 OK HTTPコードでレスポンスとして返します。
      前述のように、実際のサービスでは、データベースクエリを使用してこの検索を行うことが一般的です。
    • アルバムが見つからない場合は、http.StatusNotFoundでHTTP 404エラーを返します。
  • 2. 最後に、mainを変更して、パスが/albums/:idになるようにrouter.GETへの新しい呼び出しを含めます。次の例のように。

    1. func main() {
    2. router := gin.Default()
    3. router.GET("/albums", getAlbums)
    4. router.GET("/albums/:id", getAlbumByID)
    5. router.POST("/albums", postAlbums)
    6. router.Run("localhost:8080")
    7. }

    このコードでは、次のことを行います:

    • /albums/:idパスをgetAlbumByID関数に関連付けます。Ginでは、パス内の項目の前にコロンが付いていると、その項目がパスパラメータであることを示します。

コードを実行

  • 1. サーバーが前のセクションからまだ実行中の場合は、停止します。
  • 2. main.goを含むディレクトリのコマンドラインから、サーバーを起動するためにコードを実行します。
    1. $ go run .
  • 3. 別のコマンドラインウィンドウから、curlを使用して、実行中のWebサービスにリクエストを行います。

    1. $ curl http://localhost:8080/albums/2

    コマンドは、使用したIDのアルバムのJSONを表示するはずです。アルバムが見つからなかった場合は、エラーメッセージを含むJSONが返されます。

    1. {
    2. "id": "2",
    3. "title": "Jeru",
    4. "artist": "Gerry Mulligan",
    5. "price": 17.99
    6. }

結論

おめでとうございます!GoとGinを使用して、シンプルなRESTful Webサービスを作成しました。

次に推奨されるトピック:

完成したコード

このセクションには、このチュートリアルで構築したアプリケーションのコードが含まれています。

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/gin-gonic/gin"
  5. )
  6. // album represents data about a record album.
  7. type album struct {
  8. ID string `json:"id"`
  9. Title string `json:"title"`
  10. Artist string `json:"artist"`
  11. Price float64 `json:"price"`
  12. }
  13. // albums slice to seed record album data.
  14. var albums = []album{
  15. {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
  16. {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
  17. {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
  18. }
  19. func main() {
  20. router := gin.Default()
  21. router.GET("/albums", getAlbums)
  22. router.GET("/albums/:id", getAlbumByID)
  23. router.POST("/albums", postAlbums)
  24. router.Run("localhost:8080")
  25. }
  26. // getAlbums responds with the list of all albums as JSON.
  27. func getAlbums(c *gin.Context) {
  28. c.IndentedJSON(http.StatusOK, albums)
  29. }
  30. // postAlbums adds an album from JSON received in the request body.
  31. func postAlbums(c *gin.Context) {
  32. var newAlbum album
  33. // Call BindJSON to bind the received JSON to
  34. // newAlbum.
  35. if err := c.BindJSON(&newAlbum); err != nil {
  36. return
  37. }
  38. // Add the new album to the slice.
  39. albums = append(albums, newAlbum)
  40. c.IndentedJSON(http.StatusCreated, newAlbum)
  41. }
  42. // getAlbumByID locates the album whose ID value matches the id
  43. // parameter sent by the client, then returns that album as a response.
  44. func getAlbumByID(c *gin.Context) {
  45. id := c.Param("id")
  46. // Loop through the list of albums, looking for
  47. // an album whose ID value matches the parameter.
  48. for _, a := range albums {
  49. if a.ID == id {
  50. c.IndentedJSON(http.StatusOK, a)
  51. return
  52. }
  53. }
  54. c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
  55. }