Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
329
Help us understand the problem. What is going on with this article?
@Syoitu

ginを最速でマスターしよう

初めに:sunny:

gin.png

ginとは何でしょうか

ginはGo(Golang)で書かれたWebフレームワークです。 httprouterのおかげで、最大40倍高速なパフォーマンスを備えたmartiniのようなAPIを備えています。パフォーマンスと優れた生産性が必要な場合は、Ginを好きになるでしょう。--公式ドキュメント

パフォーマンスが良いのが売りらしいですが、他のGo言語のwebフレームワークと比較してみます。

人気ランキング

githubのスターの多い順

Name Stars Forks Issues Open Issues Closed Birth Year Latest Update Author
Gin 34,231 3,900 183 1,115 2014 2019-12-27 @manucorporat
Beego 22,890 4,600 737 1,900 2012 2019-12-27 @astaxie
Iris 17,133 1,906 4 467 2016 2019-12-27 @kataras
Echo 16,058 1,541 26 902 2015 2019-12-27 @vishr

その他の比較詳細

また、国内に案件が存在するのは、現在GinとEchoだけです:point_up:

準備

作業PCにGo言語が入ってることをまず確認してください。

go version
go version go1.13

ginをgetしましょう、新規プロジェクトを作る際にgomodを使用することをお勧めします。
gomodについてよくわからない方はこちらの記事を参考にしてください。
GOMODULE--Goのパッケージ管理

新規フォルダを作ってください、フォルダ名は任意で結構です。

mkdir gin_test && cd gin_test

go modを初期化します。

go mod init gin_test

初期化完了したら、ginのパッケージを取得します

go get -u github.com/gin-gonic/gin

ginの基礎

ginはlaravelやDjangoのようなフルスタックフレームワークと違って非常にライトなフレームワークになります、
最初の実例を書いてみよう。

main.go
package main

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

import "net/http"

func main() {
    engine:= gin.Default()
    engine.GET("/", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "hello world",
        })
    })
    engine.Run(":3000")
}

go run main.goでサーバー立ち上げて localhost:3000にアクセスしてみると
Jsonメッセージの {"message":"hello world"}が確認できます。

もしginを使わずに素のGoで同じサーバーを作る場合、以下のようになります。

main.go

package main

import (
    "encoding/json"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, q *http.Request) {
        message := map[string]string{
            "message": "hello world",
        }
        jsonMessage, err := json.Marshal(message)
        if err != nil {
            panic(err.Error())
        }
        w.Write(jsonMessage)
    })
    http.ListenAndServe("127.0.0.1:3000", mux)
}

コードの量はほぼ倍になりました。
では、ginは実際何をしたのでしょうか、ginのソースコード見てみます。

context.go
type Context struct {
    writermem responseWriter
    Request   *http.Request
    Writer    ResponseWriter

    Params   Params
    handlers HandlersChain
    index    int8
    fullPath string

    engine *Engine

    // Keys is a key/value pair exclusively for the context of each request.
    Keys map[string]interface{}

    // Errors is a list of errors attached to all the handlers/middlewares who used this context.
    Errors errorMsgs

    // Accepted defines a list of manually accepted formats for content negotiation.
    Accepted []string

    // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()
    queryCache url.Values

    // formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
    // or PUT body parameters.
    formCache url.Values
}

素のGoで HandleFuncを書く際に、パラメタとして存在する writermem responseWriterRequest *http.Request、ginの Contextstructの構成要素になってます。

gin.go
func (engine *Engine) Run(addr ...string) (err error) {
    defer func() { debugPrintError(err) }()

    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine)
    return
}

engin.Run(":3000")も実際 http.ListenAndServe(address, engine)を中身で呼んでます。
Goのhttpサーバーのコードを活かして、便利な処理を追加するのがginです。
素のGoでhttpサーバー書いたことがあれば、ginのソースコードは非常にわかやすいはずです

ミドルウェアを使ってみよう

ginはリクエストに対する処理は基本下記のようになります

Request -> Route Parser -> Middleware -> Route Handler -> Middleware -> Response

実際処理する関数に到達する前に、必ずミドルウェアを通る必要があります、
簡単な例として、アクセスするユーザーのUser-Agent を取得するミドルウェアを作ってみましょう。

main.go
package main

import (
    "net/http"

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

func main() {
    engine:= gin.Default()
    ua := ""
     // ミドルウェアを使用
    engine.Use(func(c *gin.Context) {
        ua = c.GetHeader("User-Agent")
        c.Next()
    })
    engine.GET("/", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message":    "hello world",
            "User-Agent": ua,
        })
    })
    engine.Run(":3000")
}

http://localhost:3000/にアクセスすれば、以下のメッセージが確認できるはずです。

{
User-Agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36",
message: "hello world"
}

HTMLや静的ファイルを扱う

HTMLの扱い

mkdir templates && cd templates && touch index.html

index.htmlの中身は以下のように

index.html
<h1 style="color: rebeccapurple;">{{.message}}</h1>

main.goを修正

main.go
package main

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

import "net/http"

func main() {
    engine:= gin.Default()
    // htmlのディレクトリを指定
    engine.LoadHTMLGlob("templates/*")
    engine.GET("/", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.html", gin.H{
             // htmlに渡す変数を定義
            "message": "hello gin",
        })
    })
    engine.Run(":3000")
}

サーバー立ち上げてlocalhost:3000にアクセルすると、以下の内容が確認できるはずですUNADJUSTEDNONRAW_mini_3e.jpg

静的ファイルを扱う

mkdir static 

任意の画像をstaticフォルダに入れます。
2019-12-27 17.35のイメージ.jpg
main.goを以下のように修正します。

main.go
package main

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

func main() {
  engine:= gin.Default()
  engine.Static("/static", "./static")
  engine.Run(":3000")
}

サーバーを立ち上げて、http://localhost:3000/static/gin.pngをアクセスすると、画像が表示されるはずです。

ファイルのアップロード

まず画像保存用のフォルダを作っておきます。

mkdir images
main.go
package main

import (
    "github.com/gin-gonic/gin"
    "io"
    "log"
    "net/http"
    "os"
)

func main(){
    engine := gin.Default()
    engine.POST("/upload", func(c *gin.Context) {
        file,header, err :=  c.Request.FormFile("image")
        if err != nil {
            c.String(http.StatusBadRequest, "Bad request")
            return
        }
        fileName := header.Filename
        dir, _ := os.Getwd()
        out, err := os.Create(dir+"\\images\\"+fileName)
        if err != nil {
            log.Fatal(err)
        }
        defer out.Close()
        _, err = io.Copy(out, file)
        if err != nil {
            log.Fatal(err)
        }
        c.JSON(http.StatusOK, gin.H{
            "status": "ok",
        })
    })
    engine.Run(":3000")

}

フロントエンド、vue.jsを使ってます。 urlが /api/uploadになってるのはproxyの設定に/apiで始まるurlのtargetをginサーバーにリダイレクトしています。設定の仕方に疑問のある方はコメントください

App.vue
<template>
  <div id="app">
          <input type="file" id="people-export" ref="input">
          <button type="submit" @click="fileUpload">提出</button>
  </div>
</template>
<script>
import axios from 'axios'

export default {
  name: 'app',
  components: {
    HelloWorld
  },
  methods: {
    fileUpload(){
      let file = this.$refs.input;
      let formData = new FormData();
      formData.append('image', file.files[0]);
      axios({
        method: 'post',
        url :'/api/upload',
        data : formData,
        header :{
           'Content-Type': 'multipart/form-data',
        }
      }).then(( res )=>{
        console.log(res.data)
      })
    }
  }
}
</script>
<style>
</style>

ginで簡単なREST風のAPIサーバーを作ってみよう

実装機能、書籍についての

  •  新規追加
  •  データ一覧
  •  データ修正
  •  削除

ディレクトリ構成

qiita
|- controller
|- |- book.go
|- middleware
|- |- bookMiddleware.go
|- model
|- |- book.go
|- serice
|- |- book.go
|- |- init.go
|- go.mod
|- main.go

必要なパッケージをgetします。

go get github.com/gin-gonic/gin
go get github.com/go-sql-driver/mysql
go get github.com/go-xorm/xorm
go get go.uber.org/zap

main.go

役割はREST風のAPIを実装とmysql用のパッケージの初期化します。

main.go
package main

import (
    "github.com/gin-gonic/gin"
    _ "github.com/go-sql-driver/mysql"
    "qiita/controller"
    "qiita/middleware"
)

func main(){
    engine := gin.Default()
    // ミドルウェア
    engine.Use(middleware.RecordUaAndTime)
    // CRUD 書籍
    bookEngine := engine.Group("/book")
    {
        v1 := bookEngine.Group("/v1")
        {
            v1.POST("/add", controller.BookAdd)
            v1.GET("/list", controller.BookList)
            v1.PUT("/update", controller.BookUpdate)
            v1.DELETE("/delete", controller.BookDelete)
        }
    }
    engine.Run(":3000")
}

controller/book.go

機能としてはmain.goから振られたリクエストをserviceにハンドルし、レスポンスを返します。

controller/book.go
package controller

import (
    "github.com/gin-gonic/gin"
    "net/http"
    "qiita/model"
    "qiita/service"
    "strconv"
)

func BookAdd(c *gin.Context) {
     book := model.Book{}
     err := c.Bind(&book)
     if err != nil{
         c.String(http.StatusBadRequest, "Bad request")
         return
     }
    bookService :=service.BookService{}
    err = bookService.SetBook(&book)
    if err != nil{
        c.String(http.StatusInternalServerError, "Server Error")
        return
    }
    c.JSON(http.StatusCreated, gin.H{
        "status": "ok",
    })
}

func BookList(c *gin.Context){
    bookService :=service.BookService{}
    BookLists := bookService.GetBookList()
    c.JSONP(http.StatusOK, gin.H{
        "message": "ok",
        "data": BookLists,
    })
}

func BookUpdate(c *gin.Context){
    book := model.Book{}
    err := c.Bind(&book)
    if err != nil{
        c.String(http.StatusBadRequest, "Bad request")
        return
    }
    bookService :=service.BookService{}
    err = bookService.UpdateBook(&book)
    if err != nil{
        c.String(http.StatusInternalServerError, "Server Error")
        return
    }
    c.JSON(http.StatusCreated, gin.H{
        "status": "ok",
    })
}

func BookDelete(c *gin.Context){
    id := c.PostForm("id")
    intId, err := strconv.ParseInt(id, 10, 0)
    if err != nil{
        c.String(http.StatusBadRequest, "Bad request")
        return
    }
    bookService :=service.BookService{}
    err = bookService.DeleteBook(int(intId))
    if err != nil{
        c.String(http.StatusInternalServerError, "Server Error")
        return
    }
    c.JSON(http.StatusCreated, gin.H{
        "status": "ok",
    })
}

middleware/bookMiddleware.go

リクエストのlogを記録します。
go.uber.org/zapパッケージを使用しています、出前で有名なUberさんのオープンソースらしいです。

middleware/bookMiddleware.go
package middleware

import (
    "github.com/gin-gonic/gin"
    "go.uber.org/zap"
    "log"
    "time"
)

func RecordUaAndTime(c *gin.Context){
   logger, err := zap.NewProduction()
   if err != nil{
      log.Fatal(err.Error())
   }
   oldTime := time.Now()
   ua := c.GetHeader("User-Agent")
   c.Next()
    logger.Info("incoming request",
        zap.String("path", c.Request.URL.Path),
        zap.String("Ua", ua),
        zap.Int("status", c.Writer.Status()),
        zap.Duration("elapsed", time.Now().Sub(oldTime)),
    )
}

model/book.go

bookの構造体を定義してます。
xormパッケージ使用して、テーブルの初期化でも使用します。

model/book.go
package model

type Book struct {
    Id    int64  `xorm:"pk autoincr int(64)" form:"id" json:"id"`
    Title string `xorm:"varchar(40)" json:"title" form:"title"`
    Content string `xorm:"varchar(40)" json:"content" form:"content"`
}

service/init.go

データベースへの接続とテーブルの初期化を実装します。

service/init.go
package service

import (
    "errors"
    "fmt"
    "github.com/go-xorm/xorm"
    "qiita/model"
    "log"
)

var DbEngine *xorm.Engine

func init()  {
    driverName := "mysql"
    DsName := "root:root@(192.168.99.100:3306)/gin?charset=utf8"
    err := errors.New("")
    DbEngine, err = xorm.NewEngine(driverName,DsName)
    if err != nil && err.Error() != ""{
        log.Fatal(err.Error())
    }
    DbEngine.ShowSQL(true)
    DbEngine.SetMaxOpenConns(2)
    DbEngine.Sync2(new(model.Book))
    fmt.Println("init data base ok")
}

service/book.go

機能としてはコントローラから振られたdb操作を引き受け、結果を返します。

service/book.go
package service

import (
    "qiita/model"
)

type BookService struct {}

func (BookService) SetBook(book *model.Book) error {
    _, err := DbEngine.Insert(book)
    if err!= nil{
         return  err
    }
    return nil
}


func (BookService) GetBookList() []model.Book {
    tests := make([]model.Book, 0)
    err := DbEngine.Distinct("id", "title", "content").Limit(10, 0).Find(&tests)
    if err != nil {
        panic(err)
    }
    return tests
}

func (BookService) UpdateBook(newBook *model.Book) error {
    _, err := DbEngine.Id(newBook.Id).Update(newBook)
    if err != nil {
        return err
    }
    return nil
}

func (BookService) DeleteBook(id int) error {
    book := new(model.Book)
    _, err := DbEngine.Id(id).Delete(book)
    if err != nil{
        return err
    }
    return nil
}

サーバを立ち上げて、APIの動作を見てみます。

go run main.go

起動後、以下のlogも確認できるはずです。
内容はdbの初期化完了と実装されたAPI情報です。

[xorm] [info]  2020/01/01 00:40:53.621508 [SQL] SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT`, `TABLE_COMMENT` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE
_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB') [gin]
[xorm] [info]  2020/01/01 00:40:53.655668 [SQL] SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`, `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION
_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? [gin book]
[xorm] [info]  2020/01/01 00:40:53.656645 [SQL] SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NA
ME` = ? [gin book]
init data base ok
[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] POST   /book/v1/add              --> qiita/controller.BookAdd (4 handlers)
[GIN-debug] GET    /book/v1/list             --> qiita/controller.BookList (4 handlers)
[GIN-debug] PUT    /book/v1/update           --> qiita/controller.BookUpdate (4 handlers)
[GIN-debug] DELETE /book/v1/delete           --> qiita/controller.BookDelete (4 handlers)
[GIN-debug] Listening and serving HTTP on :3000

APIのテストはpostmanを使います。

新規追加 POST /book/v1/add

1.PNG
データベースにもちゃんとデータが入りました。
2.PNG

データ一覧 GET /book/v1/list

3.PNG

データ修正 PUT /book/v1/update

内容を一部修正します。
4.PNG

データベースにもちゃんと反映されてます。
キャプチャ.PNG

データの削除 DELETE /book/v1/delete

キャプチャ.PNG

データベース内のデータも削除されました。
キャプチャ.PNG

長くなりましたが、以上となります。
ORMに関しては xorm パッケージを使用してます、ドキュメントは公式リポジトリを参考にしてください -- リンク

最後に

最後まで読んで下さり、ありがとうございます。

実際ginを使ってみて、ライトな感覚に結構惹かれています。
フレームワークを使用してるけれど、過依存することもなく、素のGoの感覚でコーディングできます。

2020/09/11の追記

Ginに関する内容を追記します。
【簡単なREST風のAPIサーバー】と被る内容もありますが本記事をクックブックとして読みやすくするために敢えて抜粋しています。

Getリクエストからクエリを取得

main.go
package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func main()  {
    serve := gin.Default()
    serve.GET("/test", func(c *gin.Context) {
        firstName := c.Query("first_name")
        lastName := c.DefaultQuery("last_name", "default_name")
        c.JSON(http.StatusOK, gin.H{"firstName":firstName, "lastName":lastName})
    })
    serve.Run("127.0.0.1:8080")
}

サーバーを立ち上げ、http://localhost:8080/test?first_name=山田にアクセスすると下記のような結果になります。
DefaultQuery関数を使用すれば、クエリ欠損する場合は設定したデフォルト値を使用します。

キャプチャ.PNG

PostリクエストからFormデータを取得

main.go
package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func main()  {
    serve := gin.Default()
    serve.POST("/test", func(c *gin.Context) {
        firstName := c.PostForm("first_name")
        lastName := c.DefaultPostForm("last_name", "default_last_name")

        c.JSON(http.StatusOK, gin.H{"firstName": firstName, "lastName": lastName})
    })
    serve.Run("127.0.0.1:8080")
}

サーバーを立ち上げ、127.0.0.1:8080/testにPOSTリクエスト送信すれば、以下のレスポンスが戻ってきます。
:sunny:テストツールはPOSTMAN使用をしています。
キャプチャ.PNG

データのバリデーション

構造体を使用したバリデーション

main.go
package main

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

type Person struct {
    Age int `form:"age" validate:"required,gt=10,max=100"`
    Name string `form:"name" validate:"required"`
    Address string `form:"address" validate:"required"`
}

func main()  {
    serve := gin.Default()
    serve.GET("/testing", func(c *gin.Context) {
         var person Person
         if err := c.ShouldBind(&person);err!=nil{
            //c.String(500, "%v",err)
            c.JSON(500, gin.H{"msg": err.Error()})
            return
         }
         //c.String(200, "%v", person)
         c.JSON(200, gin.H{"person": person})
    })
    serve.Run("127.0.0.1:8080")
}

構造体を使用してバリデーション行う際に、検証条件をタグのvalidateに追加すれば、リクエスト本文をバインドする際に自動的にバリデーション行えます。
また、複数の検証条件がある際に,を使て分けることができます。

サーバーを立ち上げてhttp://127.0.0.1:8080/testing?age=11&name=山田&address=どこかにGetリクエストを送信。
ageは11になってるため、以下の結果が帰ってきました。
キャプチャ.PNG
ageを9に修正して、再度リクエスト送信しますと、500エラーが戻ってきました。
認証ルールちゃんと機能してます。
キャプチャ.PNG

:point_down_tone1:下記のように、子構造体の検証条件を使用する際に、親構造体の検証条件にdiveを追加する必要があります。

...
type user struct {
    Name  string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email"`
}

type data struct {
    User []user `json:"user" binding:"required,dive"` // use dive tag
}
...

:point_right_tone1:検証条件一覧は 公式ドキュメントを参考にしてください。

検証条件のカスタマイズ

検証条件のカスタマイズをするにはvalidatorパッケージが必要です、本記事はv10系使用してます。
go get gopkg.in/go-playground/validator.v10でインストールすることができます。ドキュメント公式サイト

mian.go
package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
    "net/http"
    "time"
)
// bookabledateというカスタマイズ検証条件を追加
type Booking struct {
   CheckIn time.Time `form:"check_in" validate:"required,bookabledate" time_format:"2006-01-02"`
   CheckOut time.Time `form:"check_out" validate:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}

//カスタマイズ検証条件の関数
//チェックインする時間は現在時間より先である必要があります
func bookableDate(fl validator.FieldLevel) bool {
    today:=time.Now()
    if  date,ok:=fl.Field().Interface().(time.Time);ok{
        if date.Unix()>today.Unix(){
            fmt.Println("date unix :",date.Unix())
            return true
        }
    }
    return false
}

func main()  {
    serve := gin.Default()

    v := validator.New()
    v.RegisterValidation("bookabledate", bookableDate)

    serve.GET("/bookable", func(c *gin.Context) {
        var b Booking
        if err := c.ShouldBind(&b);err!=nil{
            c.JSON(500,gin.H{"error":err.Error()})
            c.Abort()
            return
        }
        if err := v.Struct(b); err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{
                "error": err.Error(),
            })
            c.Abort()
            return
        }
        c.JSON(http.StatusOK, gin.H{"message": "ok!", "booking":b})
    })

    serve.Run("127.0.0.1:8080")
}

bookableDateというカスタマイズ検証条件関数では現在時間のタイムスタンプとチェックイン時間のを比較し、チェックイン時間の方が大きければfalseを返します。

サーバーを立ち上げ、http://localhost:8080/bookable?check_in=2020-09-13&check_out=2020-09-15へリクエスト送信。
check_inの値が2020-09-13のタイムスタンプが現在時間(記事の更新時間)より大きいため、trueが戻ります。
キャプチャ.PNG
check_inを十年前の2010-09-13に修正して、再度リクエスト送信すると検証に引っかかりました。
キャプチャ.PNG

バリデーションの多言語対応

検証条件のカスタマイズと同じようにvalidatorパッケージが必要です。
インストール: go get gopkg.in/go-playground/validator.v10

main.go
package main

import (
    "github.com/gin-gonic/gin"
    en2 "github.com/go-playground/locales/en"
    ja2 "github.com/go-playground/locales/ja"
    ut "github.com/go-playground/universal-translator"
    "github.com/go-playground/validator/v10"
    en_translations "github.com/go-playground/validator/v10/translations/en"
    ja_translations "github.com/go-playground/validator/v10/translations/ja"
    "net/http"
)

type Person struct {
    Age int `form:"age" validate:"required,gt=10"`
    Name string `form:"name" validate:"required"`
    Address string `form:"address" validate:"required"`
}

var (
    Uni *ut.UniversalTranslator
    Validate *validator.Validate
)

func main()  {
    Validate = validator.New()
    ja := ja2.New()
    en := en2.New()
    Uni = ut.New(ja, en)
    serve := gin.Default()
    serve.GET("/testing", func(c *gin.Context) {
        locale := c.DefaultQuery("locale", "ja")
        trans, _ := Uni.GetTranslator(locale)
        switch locale {
        case "ja":
            ja_translations.RegisterDefaultTranslations(Validate,trans)
        case "en":
            en_translations.RegisterDefaultTranslations(Validate,trans)
        default:
            ja_translations.RegisterDefaultTranslations(Validate,trans)
        }
        var person Person
        if err := c.ShouldBind(&person);err!=nil{
            c.JSON(http.StatusInternalServerError, gin.H{"err": err.Error()})
            c.Abort()
            return
        }
        if err := Validate.Struct(person);err!=nil{
            errs := err.(validator.ValidationErrors)
            sliceErrs :=[]string{}
            for _, e := range errs{
                sliceErrs = append(sliceErrs, e.Translate(trans))
            }
            c.JSON(http.StatusInternalServerError, gin.H{"err": sliceErrs})
            c.Abort()
            return
        }
        c.JSON(http.StatusOK, gin.H{"msg": person})
    })
    serve.Run("127.0.0.1:8080")
}

サーバーを起動し、http://localhost:8080/testing?locale=enにアクセスすると、エラーメッセージが英語になっています。
キャプチャ.PNG

localeをjaにすれば、エラーメッセージが日本語になります。
キャプチャ.PNG
また、何も渡さない場合でも、エラーメッセージが日本語になります。
キャプチャ.PNG

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
329
Help us understand the problem. What is going on with this article?