Gin(Go)でAPIをRailsやLaravelライクに作る

APIを作るときにどのようなディレクトリ構成にするのかということも悩むものの一つでしょう。特にフレームワークを使わない場合、どんな構成で作っていくのかは結構考えるのではないでしょうか。

でもフレームワークを使う場合はどうでしょう。ある程度フレームワークの流れは決まっているので、それに沿って作る場合が多いんじゃないかと思います。

今回は、GOのGinというフレームワークを使って簡単なAPIを作ってみようかなと思います。その際、ビューのないMVCモデルのような感じでやってみようかなーと思う、イメージはRailsやLaravelのような感じです。GOをわからない人が見てもあーRailsの流れと同じね、みたいな感じになればいいと思う。

作成するAPI

作るのはユーザーを登録したり削除したりすることのできるものにする。

  • ユーザー一覧表示
  • ユーザーの個別表示
  • ユーザー登録
  • ユーザー削除

上記のことを行えるようにしようかな。

ディレクトリ構成

ディレクトリ構成はこんな感じでいこうと思う。

.
|-- controllers
|   `-- users.go
|-- main.go
`-- models
    `-- user.go

main.goがエントリーポイントなので、そこにルーティングの処理を書いてコントローラー→モデルのようなデータのやり取りをすることにする。

ルーティング

main.goにルーティングの処理を書く。

// main.go

package main

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

    "gin_api/controllers"
)

func main() {
    router := gin.Default()
    router.GET("/users", controllers.Index)
    router.GET("/users/:id", controllers.Show)
    router.POST("/users", controllers.Create)
    router.DELETE("/users/:id", controllers.Delete)
    router.Run(":8080")
}

指定したパスにアクセスが来たら、コントローラーのアクションに渡す。フレームワークを使わないとハンドラ関数なんかを作ってやると思うけど、フレームワーク使えばこれだけでできるから便利だね。

  • Index・・・全てのユーザーを返却
  • Show・・・指定したIDのユーザーを返却
  • Create・・・ユーザーを新規作成
  • Delete・・・指定したIDのユーザーを削除

上記のアクションを作り、controllerに実装する。

modelの定義をする

modelsディレクトリを作り、その中にuser.goを作成する。

// models/user.go

package models
import (
    "log"
    "github.com/go-xorm/xorm"
    _ "github.com/go-sql-driver/mysql"
)
var engine *xorm.Engine

func init() {
    var err error
    engine, err = xorm.NewEngine("mysql", "DB情報")
    if err != nil {
        log.Fatal(err)
    }
}

type User struct {
    ID       int    `json:"id" xorm:"id pk autoincr"`
    NickName string `json:"nickname" xorm:"'nickname'"`
}

init関数はmysqlへの接続処理ですね。モデルはtype User structの部分。構造体のフィールドにxormとかつけるとそこで指定したものがDBテーブルのカラムとマッピングしてくれるみたい。

ここら辺のオプションは以下のgithubを見てみるといい。

github.com

コントローラー

Indexアクション

Indexでは全ユーザーの取得と返却を行う。

func Index(c *gin.Context) {
    engine, err := xorm.NewEngine("mysql", "データベース情報")
    if err != nil {
        log.Fatal(err)
    }

    users := []models.User{}
    engine.Find(&users)
    c.JSON(200, users)
}

サーバーを起動してブラウザから確かめてみるとこんな感じで返却される。

f:id:utr066:20180923145218p:plain

Showアクション

Showアクションでは、指定したIDのユーザーをDBから検索して返却する

func Show(c *gin.Context) {
    n := c.Param("id")
    id, err := strconv.Atoi(n)

    if err != nil {
        log.Fatal(err)
    }

    if id <= 0 {
        c.JSON(400, gin.H{"message": "idは1以上にしてね"})
        return
    }

    user := models.User{
        ID: id,
    }

    engine, err := xorm.NewEngine("mysql", "DB情報")
    if err != nil {
        log.Fatal(err)
    }
    engine.Get(&user)

    c.JSON(200, user)
}

これはこんな感じ。idを1と想定してときの返却値。 f:id:utr066:20180923145937p:plain

でも、これ存在しないユーザーIDを指定しても返ってきちゃうからなんとかした方がいいですね。

Create

ここでは、ユーザーの新規作成を行う。

func Create(c *gin.Context) {
    newUser := models.User{}
    id, err := strconv.Atoi(c.PostForm("id"))
  if err != nil {
    log.Fatal(err)
  }

  newUser.ID = id
  newUser.NickName = c.PostForm("nickname")

    engine, err := xorm.NewEngine("mysql", "DB情報")
  if err != nil {
    log.Fatal(err)
  }
    engine.Insert(newUser)

    c.JSON(200, newUser)
}
$ curl -F "id=4" -F "nickname=new_user" http://localhost:8080/users

curlで叩いてデータが入っているか確かめるとちゃんと入っていますね。

f:id:utr066:20180923150223p:plain

Delete

ここでは、指定したIDのユーザーを削除する。

func Delete(c *gin.Context) {
    n := c.Param("id")
    id, err := strconv.Atoi(n)
    if err != nil {
        log.Fatal(err)
    }

    engine, err := xorm.NewEngine("mysql", "DB情報")
    if err != nil {
        log.Fatal(err)
    }
    user := models.User{ID: id}
    _, err = engine.Id(id).Delete(&user)
    if err != nil {
        log.Fatal(err)
    }

    c.JSON(200, "")
}
$ curl -X DELETE 'http://localhost:8080/users/4'

これで全てのユーザーをブラウザから見てみると、確かに削除されていますね。 f:id:utr066:20180923150401p:plain

全コード

//  controllers/users.go

package controllers

import (
    "log"
    "strconv"

    "github.com/gin-gonic/gin"
    "github.com/go-xorm/xorm"

    "gin_api/models"
)

func Index(c *gin.Context) {
    engine, err := xorm.NewEngine("mysql", "DB情報")
    if err != nil {
        log.Fatal(err)
    }

    users := []models.User{}
    engine.Find(&users)
    c.JSON(200, users)
}

func Show(c *gin.Context) {
    n := c.Param("id")
    id, err := strconv.Atoi(n)

    if err != nil {
        log.Fatal(err)
    }

    if id <= 0 {
        c.JSON(400, gin.H{"message": "idは1以上にしてね"})
        return
    }

    user := models.User{
        ID: id,
    }

    engine, err := xorm.NewEngine("mysql", "DB情報")
    if err != nil {
        log.Fatal(err)
    }
    engine.Get(&user)

    c.JSON(200, user)
}

func Create(c *gin.Context) {
    newUser := models.User{}
    id, err := strconv.Atoi(c.PostForm("id"))
  if err != nil {
    log.Fatal(err)
  }

  newUser.ID = id
  newUser.NickName = c.PostForm("nickname")

    engine, err := xorm.NewEngine("mysql", "DB情報")
  if err != nil {
    log.Fatal(err)
  }
    engine.Insert(newUser)

    c.JSON(200, newUser)
}

func Delete(c *gin.Context) {
    n := c.Param("id")
    id, err := strconv.Atoi(n)
    if err != nil {
        log.Fatal(err)
    }

    engine, err := xorm.NewEngine("mysql", "DB情報")
    if err != nil {
        log.Fatal(err)
    }
    user := models.User{ID: id}
    _, err = engine.Id(id).Delete(&user)
    if err != nil {
        log.Fatal(err)
    }

    c.JSON(200, "")
}
// models/user.go

package models
import (
    "log"
    "github.com/go-xorm/xorm"
    _ "github.com/go-sql-driver/mysql"
)
var engine *xorm.Engine

func init() {
    var err error
    engine, err = xorm.NewEngine("mysql", "DB情報")
    if err != nil {
        log.Fatal(err)
    }
}

type User struct {
    ID       int    `json:"id" xorm:"id pk autoincr"`
    NickName string `json:"nickname" xorm:"'nickname'"`
}
// main.go

package main

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

    "gin_api/controllers"
)

func main() {
    router := gin.Default()
    router.GET("/users", controllers.Index)
    router.GET("/users/:id", controllers.Show)
    router.POST("/users", controllers.Create)
    router.DELETE("/users/:id", controllers.Delete)
    router.Run(":8080")
}

まとめ

フレームワークを使うと、割と楽にできてしまうのが良いですね。今回はxormを使ってみたけど、以前ちらっと本で読んだ感じgormが結構良さげだったからそっちの方が使いやすいかもしれんね。