Ginへ入門

公開日:2024-10-02


はじめに

  • Go言語を学習中。今後Go言語を利用してバックエンド開発を行う予定なので、WebフレームワークのGinについて学習しました。
  • 具体的にはGinを用いてRESTAPIを作成しました。

Ginとは

Quick Start

mkdir bbs-project
go mod init bbs-project # モジュールの初期化(公開予定なし)
# go.modファイルが作成される

go get -u github.com/gin-gonic/gin # Ginのインストール
go get -u github.com/stretchr/testify/assert@latest # テストモジュールのインストール
# go.modファイルにginの依存関係が追加される
# go.sumファイルが作成される

vim main.go # 下記のコードを記述
package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // 0.0.0.0:8080 でサーバーを立てます。
}
  • Postmanよりhttp://localhost:8080/pingにGETリクエストでアクセスすると、{"message":"pong"}が返ってくることを確認。

BBSを元にしたAPIの作成

  • Github このリポジトリにまとめています。
  • BBSプロジェクト で作成したAPIをGinを用いて作成しました。
  • プロジェクトはMVCモデルで作成。
    • Model層 .. models/内に記載。データ構造体を定義してデータベースとのやり取りを行う。
    • Controller層 .. controllers/内に記載。リクエストを受け取り、適切な処理を行う。
    • View層 .. views/内に記載。REST APIのためViewは不要だが、返却するJSONをViewとして扱う。
  • ルーティングはroutes/内に記載。
  • main.goがエントリーポイント。
  • テストコードはtest/内に記載。
  • ディレクトリ構造は以下の通り。
    ├── go.mod
    ├── go.sum
    ├── main.go
    ├── models
    │   └── post.go
    ├── views
        └── post_view.go
    ├── controllers
    │   └── post_controller.go
    ├── routers
    │   └── router.go
    ├── test
    │   └── post_controller_test.go
    ├── openapi.yaml
    └── README.md
    
  • 呼び出しについて
    1. main.goを起動することで、ルーティングを起動。
    2. ルーティングはcontrollers/内のコントローラーを呼び出し、処理を行う。
    3. コントローラーはmodels/内のモデルを呼び出し、データベースとのやり取りを行う。
    4. コントローラーではviews/内のビューを呼び出し、JSONを返却する。
      ├── main.go
          └── routers/router.go
              └── controllers/post_controller.go
                  ├── models/post.go
                  └── views/post_view.go
    

各ファイルの説明

  • main.go

    • ルーティングを設定し、サーバーを起動する。
    package main
    
    import (
      "bbs-project/routers"
    )
    
    func main() {
      r := routers.SetupRouter()
      r.Run(":8080")
    }
    
  • routers/router.go

    • GET, POST, DELETE, PUTのエンドポイントを設定する。
    package routers
    
    import (
      "bbs-project/controllers"
    
      "github.com/gin-gonic/gin"
    )
    
    // ルーティングの設定
    func SetupRouter() *gin.Engine {
    r := gin.Default()
    
        // /v1/api に関連するエンドポイントをグループ化
        api := r.Group("/v1/api")
        {
          api.GET("/list", controllers.GetPosts)
          api.GET("/detail/:id", controllers.GetPostDetail)
          api.POST("/create", controllers.CreatePost)
          api.DELETE("/delete/:id", controllers.DeletePost)
          api.PUT("/update/:id", controllers.UpdatePost)
        }
    
        return r
    }
    
  • controllers/post_controller.go

    • リクエストを受け取り、適切な処理を行う。
    • 投稿一覧取得の例
      • c.Queryでクエリパラメータを取得している。
      • strconv.Atoiで文字列を数値に変換し、数値でない場合はエラーを返す。
    package controllers
    
    import (
      "bbs-project/models"
      "bbs-project/views"
      "net/http"
      "strconv"
    
      "github.com/gin-gonic/gin"
    )
    
    // 投稿一覧取得のコントローラ
    func GetPosts(c *gin.Context) {
      // クエリパラメータの取得
      pageStr := c.Query("page")
      perPageStr := c.Query("per_page")
    
      // ページ数の確認
      page, err := strconv.Atoi(pageStr)
      if err != nil || page < 1 {
        views.RenderError(c, http.StatusBadRequest, "Invalid page parameter", "The 'page' parameter must be a positive integer.")
        return
      }
    
      // 1ページあたりの表示数の確認
      perPage, err := strconv.Atoi(perPageStr)
      if err != nil || perPage < 1 {
        views.RenderError(c, http.StatusBadRequest, "Invalid per_page parameter", "The 'per_page' parameter must be a positive integer.")
        return
      }
    
      start := (page - 1) * perPage
      end := start + perPage
    
      if start >= len(models.Posts) {
        views.RenderPosts(c, []models.Post{})
        return
      }
    
      if end > len(models.Posts) {
        end = len(models.Posts)
      }
    
      views.RenderPosts(c, models.Posts[start:end])
    }
    

実行と確認方法

# 実行
go run main.go

テスト

  • test/post_controller_test.goにテストコードを記述し、関数をTest~で始め、go testコマンドでテストを実行することが可能

  • 今回はhttpcodeが200であることを確認するテストを記述。

    # テスト
    cd ~/bbs-project/test
    go test
    
    [GIN] 2024/10/02 - 19:06:00 | 200 |      52.748µs |                 | POST     "/v1/api/create"
    [GIN] 2024/10/02 - 19:06:00 | 200 |      19.998µs |                 | GET      "/v1/api/list?page=1&per_page=2"
    [GIN] 2024/10/02 - 19:06:00 | 200 |       2.344µs |                 | GET      "/v1/api/detail/2"
    [GIN] 2024/10/02 - 19:06:00 | 200 |       9.628µs |                 | DELETE   "/v1/api/delete/2"
    [GIN] 2024/10/02 - 19:06:00 | 200 |      16.591µs |                 | PUT      "/v1/api/update/2"
    PASS
    ok      bbs-project/test        0.004s
    

Golang/Ginの学習メモ

  • gin.Default() でロガーとリカバリーのミドルウェアがすでにアタッチされたEngineインスタンスを返す。
  • gin.New() では空のEngineインスタンスを返す。
  • gin.Context ではミドルウェア間で変数を渡したり、フローを管理したり、リクエストのJSONを検証したり、JSONレスポンスをレンダリングしたりすることができる。
  • gin.Hmap[string]interface{} へのショートカット。
  • import "log"log.Println() などのログ出力が可能。
  • Groupでエンドポイントをグループ化することができる。
  • ShouldBindJSON でリクエストボディを構造体にバインドし、受け取ったデータを構造体に格納できる。
  • c.JSON で構造体をJSON形式に変換して返却することができる。

まとめ

  • Ginを用いてREST APIを作成しました。プロジェクトはMVCモデルで作成し、メンテナンス性を高めました。
  • 今後としては、データベースの連携、Ginの機能を活用したAPIの拡張を行っていきたいです。

😄 END 😀