Golang+Echo+dbrでMySQLのCRUDをする/JSONでDBの値を返却する話

More than 3 years have passed since last update.


環境

Go :go version go1.6.2 darwin/amd64

IDE :VSCode1.1.0

echo:Echo v2.(beta)

MySQL: Ver 14.14 Distrib 5.7.12, for osx10.11 (x86_64)

OS:Max OSX El Capitan

俺 :Go初めて2日目

MySQLはHomebrewで入れた。


何ができるか

http://localhost:1323/users/

にアクセス(GET)したらMySQLに予め登録してあるユーザ情報を取得してJSONが返される。

とか

http://localhost:1323/

にユーザ情報をPOSTしたら情報が登録される。

など


dbrについて

本家

https://github.com/gocraft/dbr

わかりやすい日本語記事(まとまっています)

https://eurie.co.jp/blog/engineering/2015/12/go-lang-ormapper-dbr


コード


server.go

package main

import (
"net/http"
"strconv"
//"fmt"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/labstack/echo/middleware"

_ "github.com/go-sql-driver/mysql"
"github.com/gocraft/dbr"
//"github.com/gocraft/dbr/dialect"
)

type (
userinfo struct {
ID int `db:"id"`
Email string `db:"email"`
First_name string `db:"first_name"`
Last_name string `db:"last_name"`
}

userinfoJSON struct {
ID int `json:"id"`
Email string `json:"email"`
Firstname string `json:"firstName"`
Lastname string `json:"lastName"`
}

responseData struct {
Users []userinfo `json:"users"`
}
)

var (
tablename = "userinfo"
seq = 1
conn, _ = dbr.Open("mysql", "root:@tcp(127.0.0.1:3306)/ws2", nil)
sess = conn.NewSession(nil)
)

//----------
// Handlers
//----------

func insertUser(c echo.Context) error {
u := new(userinfoJSON)
if err := c.Bind(u); err != nil {
return err
}

sess.InsertInto(tablename).Columns("id","email","first_name","last_name").Values(u.ID,u.Email,u.Firstname,u.Lastname).Exec()

return c.NoContent(http.StatusOK)
}

func selectUsers(c echo.Context) error {
var u []userinfo

sess.Select("*").From(tablename).Load(&u)
response := new(responseData)
response.Users = u
return c.JSON(http.StatusOK,response)
}
func selectUser(c echo.Context) error {
var m userinfo
id := c.Param("id")
sess.Select("*").From(tablename).Where("id = ?", id).Load(&m)
//idはPrimary Key属性のため重複はありえない。そのため一件のみ取得できる。そのため配列である必要はない
return c.JSON(http.StatusOK,m)

}

func updateUser(c echo.Context) error {
u := new(userinfoJSON)
if err := c.Bind(u); err != nil {
return err
}

attrsMap := map[string]interface{}{"id": u.ID, "email": u.Email , "first_name" : u.Firstname , "last_name" : u.Lastname}
sess.Update(tablename).SetMap(attrsMap).Where("id = ?", u.ID).Exec()
return c.NoContent(http.StatusOK)
}

func deleteUser(c echo.Context) error {
id,_ := strconv.Atoi(c.Param("id"))

sess.DeleteFrom(tablename).
Where("id = ?", id).
Exec()

return c.NoContent(http.StatusNoContent)
}

func main() {
e := echo.New()

// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())

// Routes

e.POST("/users/", insertUser)
e.GET("/user/:id", selectUser)
e.GET("/users/",selectUsers)
e.PUT("/users/", updateUser)
e.DELETE("/users/:id", deleteUser)

// Start server
e.Run(standard.New(":1323"))
}



DB

userinfoというテーブル名です。

スクリーンショット 2016-05-10 19.38.21.png


サーバーを立てる前に

注意点など

1.import文の

_ "github.com/go-sql-driver/mysql"

に関しては、_がないと”imported and not used”とコンパイラが怒ってくるのですが、じゃあ削除しよう、とするとMySQLにアクセスするためのセッションが起動しなくなるのでこれは必須です。謎。

2.typeにおいて

db:"hoge"のようにタグがついている構造体とjson:"fuga"のようにタグがついている構造体が存在する。構造体に含まれる変数や型はほぼおなじであるがどうして2つあるのか説明しておく。

例えばJSONがPOSTされてそれを受け取る時contextからBindされて構造体のインスタンスの各変数に値が代入される。この時jsonタグがついているものでなければならない。

次にDBからSELECTなどで値を受け取る時、Load(&u)によって構造体のインスタンスの各変数に値が代入される。この時dbタグがついているものでなければならない。

以上の理由により2つ存在してます。バッドノウハウかもしれないのでもし何かあれば教えて下さい。


insertUser


sample.go

func insertUser(c echo.Context) error {

u := new(userinfoJson)
if err := c.Bind(u); err != nil {
return err
}
sess.InsertInto(tablename).Columns("id","email","first_name","last_name").Values(u.ID,u.Email,u.First_name,u.Last_name).Exec()

return c.NoContent(http.StatusOK)
}


まずJSONを受け取る構造体のインスタンスを作成する。

その後、c.Bind(u)で構造体のインスタンスに対して値を入れていく。

(もしBindでうまく値が入らないとかあったらfmt.Println(err)でPrintデバッグ…)

あとは、InsertIntoのラッパーから、Valuesをいれていく。

終わり!


selectUser


sample.go

func selectUser(c echo.Context) error {

var m userinfo
id := c.Param("id")
sess.Select("*").From(tablename).Where("id = ?", id).Load(&m)
//idはPrimary Key属性のため重複はありえない。そのため一件のみ取得できる。そのため配列である必要はない
return c.JSON(http.StatusOK,m)
}

SQL文によって複数の構造体が帰ってくる可能性があるので格納するための配列を用意する。

"/:id"によってURLから数字を取得することができるのでそれをidに代入する。

構造体として値が保持できているため、それをJSON化してreturnする。

コメントにあるとおり、今回はm[0]を指定している。


selectUsers


sample.go

func selectUsers(c echo.Context) error {

var u []userinfo

sess.Select("*").From(tablename).Load(&u)
response := new(responseData)
response.Users = u
return c.JSON(http.StatusOK,response)
}


全ユーザを検索する。

今回は複数のユーザ情報の構造体がかえってくるので配列を用いる必要がある。

最後にこれをわざわざ1つのJSONと変換しているが、

これは、そのまま配列を返すよりは{"user":[{json},{json},{json}]}となっている方が後々扱いやすいことが理由である。


updateUser


sample.go



func updateUser(c echo.Context) error {
u := new(userinfoJson)
if err := c.Bind(u); err != nil {
return err
}

attrsMap := map[string]interface{}{"id": u.ID, "email": u.Email , "first_name" : u.Firstname , "last_name" : u.Lastname}
sess.Update(tablename).SetMap(attrsMap).Where("id = ?", u.ID).Exec()
return c.NoContent(http.StatusOK)
}


insertと同様にuserinfoJsonインスタンスに値を入れる。

次に変更内容をmap形式で記述する。これは次のUpdate文のラッパーで、SetMapという関数を使えるようにするためである。だいぶ便利。

最後にwhereからどのユーザに対して変更を適用するかを決めてSQL文を発行する。


updateUser


sample.go

func deleteUser(c echo.Context) error {

id,_ := strconv.Atoi(c.Param("id"))

sess.DeleteFrom(tablename).
Where("id = ?", id).
Exec()

return c.NoContent(http.StatusNoContent)
}


これはselectと同様にURLから数字を指定するタイプ

その数字のIDを持つユーザをテーブルから削除するだけ。そんなに大したことはしてない。


main


sample.go

func main() {

e := echo.New()

// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())

// Routes
e.POST("/users/", insertUser)
e.GET("/user/:id", selectUser)
e.GET("/users/",selectUsers)
e.PUT("/users/", updateUser)
e.DELETE("/users/:id", deleteUser)

// Start server
e.Run(standard.New(":1323"))
}


いつもどおり、echoの新しいインスタンスを作ってLoggerやRecoverなどのミドルウェアの使用を宣言する。

e.POST("/users/", insertUser)

curl -X POST \

-H 'Content-Type: application/json' \

-d '{"id" : 10 , "email" : "test@gmail.com" , "firstName" : "poyoc" , "lastName" : "negi" }' \

localhost:1323/users/

というコマンドをターミナルで打つと、localhost:1323/users/に対してJSONをPOSTすることになり、その結果としてDBにJSONのそれぞれの要素を書き込む。

e.GET("/user/:id", selectUser)

ブラウザから http://localhost:1323/user/1 にアクセスすると

{"ID":1,"Email":"negipoyoc@gmail.com","First_name":"poyoc","Last_name":"negi"}

のような結果が得られる。今回はDBとSQLの構成上1件のみしかヒットしないためJSON単体を返却するようにしている。

e.GET("/users/",selectUsers)

上と似ているけどこっちは全ユーザを取得するタイプ。

同様にブラウザから http://localhost:1323/users/ にアクセスすると

{"users":[{"ID":1,"Email":"negipoyoc@gmail.com","First_name":"poyoc","Last_name":"negi"}]}

のうな結果が得られる。

e.PUT("/users/", updateUser)

curl -X PUT \

-H 'Content-Type: application/json' \

-d '{"id" : 28 , "email" : "test@gmail.com" , "first_name" : "miku" , "last_name" : "hatsune" }' \

localhost:1323/users/

Insertと同様、Curl文を発行するとDBのそのIDの人にたいしてUPDATEが起きる。

e.DELETE("/users/:id", deleteUser)

curl -X DELETE localhost:1323/users/25

のようにCurlでDELETEを発行するとDBのURLの末尾の番号のIDを持つ人が削除される。


※当然ながらINSERT,PUT,DELETE,GETは大文字の必要がある。

※JSONの記法が間違っている場合がおおいので、errをPrintDebugするのは良いし、解決が早くなりそう。