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ファイルを作成します。
[
{
"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