Help us understand the problem. What is going on with this article?

【はじめてのGO】gin + gormでシンプルなCRUDなREST APIを作成する

More than 1 year has passed since last update.

GO言語の勉強をはじめてまだ1ヶ月もないのですが、シンプルにCRUDするAPIを作りたいと思い、Go言語でのAPIの作り方(この記事ではginを使います)、DB接続、そしてcurlで叩いてCRUDするまでの過程をなぞっていきます。

使用するライブラリ

  • gin ・・・ Go言語でのWAF(Web Application Framework)。API作るのに使います。
  • gorm ・・・ORM。DBとのやりとりで使います。
  • json ・・・ 標準ライブラリですが一応。marshal/unmarshalメソッドを用いてjsonと構造体の変換を担います。

各種ライブラリを試す

CRUD APIの作成に向けて、上記3つのライブラリについて先に簡易な説明をしていきます。

gin

ginはGo言語のWAFです。他にもGO言語には様々なWAFがあるようなのですが、ginの記事が多くあったので今回はこれを使ってみました。
試しに、Hello Worldを返すだけの簡易なAPIを作ってみます。
ますは、インストール。

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

以下のようにポート8080で受け取るように作成します。

package main

import (
 "net/http"

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

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

 r.GET("/hello", func(c *gin.Context) {
  c.String(http.StatusOK, "Hello world")
})

 r.Run(":8080")
}

実行し、curlで叩いてみましょう。

$go run main.go
[GIN-debug] GET    /hello     --> main.main.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :8080

この状態でもう一つターミナルを立ち上げて、curlで叩きます。

$ curl localhost:8080/hello
Hello world

バッチリ返ってきました。こんな風にしてAPIを作っていきます。

gorm

次はORMであるgormです。
gormを使う前に、db接続の部分をみてみましょう。

package main

import (
    "fmt"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

func gormConnect() *gorm.DB {
    DBMS := "mysql"
    USER := <DB user>
    PASS := <DB Password>
    PROTOCOL := tcp(<DBのIPアドレス>:<PORT>)
    DBNAME := <DB NAME>
    CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME
    db, err := gorm.Open(DBMS, CONNECT)

    if err != nil {
        panic(err.Error())
    }
    fmt.Println("db connected: ", &db)
    return db
}

func main() {
 db := gormConnect()

 defer db.Close()
 db.LogMode(true)
}

接続ができたら、次はmigrationです。usersテーブルを作成します。
usersのモデルを作成します。

type User struct {
 gorm.Model
 Name     string    `json:"name"`
 Age      int       `json:age`
 Birthday time.Time `json:birthday`
}

gorm.Model はベースとなるモデル定義で、IDやCreatedAtなどを自身のモデルに埋め込めます。
http://doc.gorm.io/models.html#model-definition

main.goに下記を追記します。

db.Set("gorm:table_options", "ENGINE=InnoDB")
db.AutoMigrate(&User{})

実行すると、mysqlにusersテーブルが作成されます。

mysql> show columns from users;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| created_at | timestamp        | YES  |     | NULL    |                |
| updated_at | timestamp        | YES  |     | NULL    |                |
| deleted_at | timestamp        | YES  | MUL | NULL    |                |
| name       | varchar(255)     | YES  |     | NULL    |                |
| age        | int(11)          | YES  |     | NULL    |                |
| birthday   | timestamp        | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+

SELECTやUPDATE等については他にもたくさん記事があるので、そちらを参考にしました。
参考にした記事:
【GORM】Go言語でORM触ってみた
Go言語のGORMを使ってみた①

記事の下部に最終的なCRUD APIのソースを置くので、詳細はそちらをご参照ください。

json(Marshal/Unmarshal)

次は、jsonと構造体の変換についてです。
encoding/jsonパッケージのMarshal/Unmarshalというメソッドを用います。

Unmarshal

jsonから構造体に変換します。
まずは、jsonファイルを作成します。

test.json
[
  {
    "name": "Yamada",
    "age": 22,
    "birthday": "1996-08-06T00:00:00+09:00"
  }
]

次に、このjsonファイルを読み込み、Unmarshalします。Unmarshalは構造体に記載したjsonタグに対応したフィールドにマッピングされます。

//jsonファイルの読み込み
func main() {
 jsonBytes, err := ioutil.ReadFile("test.json")
 if err != nil {
    log.Fatal(err)
 }
 //json(bytes)→構造体 
 users := []User{}
 if err := json.Unmarshal(jsonBytes, &users); err != nil {
    fmt.Println("Unmarshal error:", err)
    return
 }
 fmt.Println("user name ->", users[0].Name) //YAMADA
 fmt.Println("user age->", users[0].Age) //22
 fmt.Println("user birthday->", users[0].Birthday) //1996-08-06 00:00:00 +0900 JST
}

Marshal

次は反対に構造体→jsonをやってみます。

func main() {
 //User型の構造体→json
 user := User{
  Name:     "Itou",
  Age:      10,
  Birthday: time.Date(2009, 2, 19, 12, 0, 0, 0, time.Local),
 }

 jsonBytes, err := json.Marshal(user)
 if err != nil {
  fmt.Println("Marshal error:", err)
  return
 }

 fmt.Println("user ->", string(jsonBytes))
}

結果は以下のようになります。

$ go run main.go 
user -> {"ID":0,"CreatedAt":"0001-01-01T00:00:00Z","UpdatedAt":"0001-01-01T00:00:00Z","DeletedAt":null,"name":"Itou","Age":10,"Birthday":"2009-02-19T12:00:00+09:00"}

必要なものは整ったので、早速APIを作っていきましょう!

CRUDのREST APIを作成する

CREATE

まずはCREATEから。
CreaetdAtやUpdatedAtはバック側でtimeを用いて追記するようにしました。

//CREATE
r.POST("/user", func(c *gin.Context) {
 user := User{}
 now := time.Now()
 user.CreatedAt = now
 user.UpdatedAt = now

 err := c.BindJSON(&user)
 if err != nil {
   c.String(http.StatusBadRequest, "Request is failed: "+err.Error())
 }
 db.NewRecord(user)
 db.Create(&user)
 if db.NewRecord(user) == false {
  c.JSON(http.StatusOK, user)
 }
})

無事に登録されれば、jsonで作成したレコードを返すようにしています。
curlで叩いみると正しく帰ってきているのがわかります。

$ curl localhost:8080/user curl -X POST -H "Content-Type: application/json" -d '{"name":"sensuikan1973", "age":15, "birthday": "2007-02-04T03:18:45.000Z"}'
{"ID":2,"CreatedAt":"2019-02-23T23:28:48.832877+09:00","UpdatedAt":"2019-02-23T23:28:48.832877+09:00","DeletedAt":null,"name":"sensuikan1973","age":15,"birthday":"2007-02-04T03:18:45Z"}

また、以下を記載しておくと、実際に発行されたクエリを確認できるので記載しておきましょう。

db.LogMode(true)

READ

次はデータの取得です。全データ取得と1レコードの取得でやってみます。

//READ
//全レコード
r.GET("/users", func(c *gin.Context) {
 users := []User{}
 db.Find(&users) // 全レコード
   c.JSON(http.StatusOK, users)
})
//1レコード
r.GET("/user/:id", func(c *gin.Context) {
 user := User{}
 id := c.Param("id")

 db.Where("ID = ?", id).First(&user)
 c.JSON(http.StatusOK, user)
})

全レコードはFindを用いて配列で取得、1レコードの例はidをparamから拾うようにし、Whereで指定した後、Firstで最初の1レコードだけを出すようにしています。

UPDATE

次はUpdateです。idをparamsから受け取り、更新するデータはjsonで取得しています。

//UPDATE
r.PUT("/user/:id", func(c *gin.Context) {
 user := NewUser()
 id := c.Param("id")

 data := NewUser()
 if err := c.BindJSON(&data); err != nil {
   c.String(http.StatusBadRequest, "Request is failed: "+err.Error())
 }

 db.Where("ID = ?", id).First(&user).Updates(&data)
})

DELETE

gormでは、削除フラグ deleted_at を立て、DELETE()を用いることで自動で論理削除として削除を実行してくれます。

//DELETE
r.DELETE("/user/:id", func(c *gin.Context) {
 user := NewUser()
 id := c.Param("id")

 db.Where("ID = ?", id).Delete(&user)
})

おわりに

今回、GoでCRUDなRESTful APIを作成するために、便利なライブラリとしてginとgormの説明、そして実際の作成を行いました。
gormの仕組みは非常にわかりやすかったのですが、クエリが複雑になってくると記載が難しそうな気がしました。

今回のソースは以下に置いています。
まだ初学者ゆえ、修正点等あればご連絡頂けると幸いです。
https://github.com/daitasu/go-crud-first/blob/master/main.go

参考記事

記事中にもいくつか記載しましたが、以下を参考にさせて頂きました。
【GORM】Go言語でORM触ってみた
Go言語のGORMを使ってみた①
Go言語製WAF GinでWebアプリを作ってみる
gorm Doc

daitasu
Frontend Engnieer in STORES.jp。vue/nuxt/react native/node/ts/webrtc。Code for JapanでNPO支援をしてます。
https://note.mu/daitasu
storesjp
インターネットビジネスの企画・開発・運営、マーケティング、プロモーション、コンテンツの企画・制作
https://about.stores.jp
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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした