golangのフレームワークrevelを使用して掲示板っぽいものを作ってみる
はじめに
今回は golang の revel framework を使用して掲示板っぽいやつを作ってみる。
掲示板っぽいと言っても基本的には API で Json を返すことにする。
しかし html を返す場合もやることはほぼ変わらない。
今回作成する API は一般的な掲示板でいうスレッドは存在せず
レスのみを扱う。(今後スレッド対応やログイン対応等を行っていくかもしれない)
endpoint は下記のようにする
http method | path | 説明 |
GET | /comments | 一覧 |
GET | /comments/:id | 詳細 |
POST | /comments | 登録 |
DELETE | /comments/:id | 削除 |
環境
$ go version go version go1.4.2 $ mysql --version mysql Ver 14.14 Distrib 5.6.13
revel を install する
下記で install できる
$ go get github.com/revel/revel # revel framework を取得 $ go get github.com/revel/cmd/revel # revel command を取得
プロジェクトを作成してみる
とりあえずハロワが表示されるとこまで進める。
$ revel new github.com/shiro16/golang-bbs # rails new みたいなやつ ~ ~ revel! http://revel.github.io ~ Your application is ready: ${GOPATH}/src/github.com/shiro16/golang-bbs You can run it with: revel run github.com/shiro16/golang-bbs $ cd ${GOPATH}/src/github.com/shiro16/golang-bbs $ revel run # applicaton の起動
これで「http://localhost:9000/」にアクセスすると
「It works!」が表示される。
API の endpoint を定義する
routing を追加する
routing は「config/routes」で管理されている
下記のように編集する。
今回の Api の path は「/api/v1」をベースにする
GET / App.Index + GET /api/v1/comments ApiV1Comments.Index + GET /api/v1/comments/:id ApiV1Comments.Show + POST /api/v1/comments ApiV1Comments.Create + DELETE /api/v1/comments/:id ApiV1Comments.Delete
controller を作成する
今回は api で共通して使うであろう Error 処理等をまとめて記述する
ベースとなる controller も作成しておく
// app/controllers/api/v1/v1.go package controllers import ( "github.com/revel/revel" "github.com/shiro16/golang-bbs/app/utils" "net/http" ) // 埋め込みによって revel.Controller をラップした ApiV1Controller を定義する type ApiV1Controller struct { *revel.Controller } // エラーの際に返す Json 用の構造体 type ErrorResponse struct { Code int `json:"code"` Message string `json:"message"` } // 正常な際に返す Json 用の構造体(今回は1種類で統一する) type Response struct { Results interface{} `json:"results"` } // 引数として渡されて interface にリクエストの Json の値を格納する func (c *ApiV1Controller) BindParams(s interface{}) error { return utils.JsonDecode(c.Request.Body, s) } // Bad Request Error を返すやつ func (c *ApiV1Controller) HandleBadRequestError(s string) revel.Result { c.Response.Status = http.StatusBadRequest r := ErrorResponse{c.Response.Status, s} return c.RenderJson(r) } // Not Found Error を返すやつ func (c *ApiV1Controller) HandleNotFoundError(s string) revel.Result { c.Response.Status = http.StatusNotFound r := ErrorResponse{c.Response.Status, s} return c.RenderJson(r) } // Internal Server Error を返すやつ func (c *ApiV1Controller) HandleInternalServerError(s string) revel.Result { c.Response.Status = http.StatusInternalServerError r := ErrorResponse{c.Response.Status, s} return c.RenderJson(r) }
これでベースとなる処理が完了したので comments controller を作成していく
// app/controllers/api/v1/comments.go package controllers import ( "github.com/revel/revel" "github.com/shiro16/golang-bbs/app/controllers" ) type ApiV1Comments struct { ApiV1Controller } func (c ApiV1Comments) Index() revel.Result { r := Response{"index"} return c.RenderJson(r) } func (c ApiV1Comments) Show(id int) revel.Result { r := Response{"show"} return c.RenderJson(r) } func (c ApiV1Comments) Create() revel.Result { r := Response{"create"} return c.RenderJson(r) } func (c ApiV1Comments) Delete(id int) revel.Result { r := Response{"delete"} return c.RenderJson(r) }
これで controller の作成は完了。
DB へ接続してデータを取得する等は次で行います。
実際に起動してみると下記のようになるかと。
$ revel run github.com/shiro16/golang-bbs # 別窓等で $ curl http://localhost:9000/api/v1/comments { "results": "index" } $ curl http://localhost:9000/api/v1/comments/1 { "results": "show" } $ curl -X POST http://localhost:9000/api/v1/comments { "results": "create" } $ curl -X DELETE http://localhost:9000/api/v1/comments/1 { "results": "delete" }
DB 周りの処理を作成する
現状 ORM が revel には無い為自分で好きなものを使う。
revel の samples では gorp が使われているが
今回は gorm を使用する。
validation には validator を使用する。
DB の接続情報を追加
「golang_bbs_development」という名前の DB 名にしています。
// conf/app.conf [dev] mode.dev = true results.pretty = true watch = true watcher.mode = "normal" log.trace.output = off log.info.output = stderr log.warn.output = stderr log.error.output = stderr db.info = "root@/golang_bbs_development?charset=utf8&parseTime=True
model を作成する
gorm を使用するので取得する
validator も取得しておく
$ go get github.com/jinzhu/gorm $ go get gopkg.in/validator.v2
comment model を作成
// app/models/comment.go package models import ( "time" ) type Comment struct { ID uint64 `gorm:"primary_key" json:"id"` Nickname string `sql:"size:64" json:"nickname" validate:"max=64"` Body string `sql:"size:255" json:"body" validate:"min=1,max=255"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt *time.Time `json:"deleted_at"` }
DB への接続などの初期化処理を作成
// app/controllers/gorm.go package controllers import ( _ "github.com/go-sql-driver/mysql" "github.com/jinzhu/gorm" "github.com/revel/revel" "github.com/shiro16/golang-bbs/app/models" "log" ) var DB *gorm.DB func InitDB() { db, err := gorm.Open("mysql", dbInfoString()) if err != nil { log.Panicf("Failed to connect to database: %v\n", err) } db.DB() db.AutoMigrate(&models.Comment{}) # ここで table の作成を行っている DB = &db } func dbInfoString() string { s, b := revel.Config.String("db.info") if !b { log.Panicf("database info not found") } return s }
上記を呼び出す処理を追記
// app/init.go - import "github.com/revel/revel" + import( + "github.com/revel/revel" + "github.com/shiro16/golang-bbs/app/controllers" + ) func init() { .... + revel.OnAppStart(controllers.InitDB) // 28行目くらいに }
DB 周りの処理の作成が完了し、
残すは作成した model を controller で実際に使用する処理を残すのみ
controller で model を使用する
comments controller を編集していく
package controllers import ( "github.com/revel/revel" + "github.com/shiro16/golang-bbs/app/controllers" + "github.com/shiro16/golang-bbs/app/models" + "gopkg.in/validator.v2" ) type ApiV1Comments struct { ApiV1Controller } func (c ApiV1Comments) Index() revel.Result { + comments := []models.Comment{} + if err := controllers.DB.Order("id desc").Find(&comments).Error; err != nil { + return c.HandleInternalServerError("Record Find Failure") + } + r := Response{comments} - r := Response{"index"} return c.RenderJson(r) } func (c ApiV1Comments) Show(id int) revel.Result { + comment := &models.Comment{} + if err := controllers.DB.First(&comment, id).Error; err != nil { + return c.HandleNotFoundError(err.Error()) + } + r := Response{comment} - r := Response{"show"} return c.RenderJson(r) } func (c ApiV1Comments) Create() revel.Result { + comment := &models.Comment{} + if err := c.BindParams(comment); err != nil { + return c.HandleBadRequestError(err.Error()) + } + if err := validator.Validate(comment); err != nil { + return c.HandleBadRequestError(err.Error()) + } + if err := controllers.DB.Create(comment).Error; err != nil { + return c.HandleInternalServerError("Record Create Failure") + } + r := Response{comment} - r := Response{"create"} return c.RenderJson(r) } func (c ApiV1Comments) Delete(id int) revel.Result { + comment := models.Comment{} + if err := controllers.DB.First(&comment, id).Error; err != nil { + return c.HandleNotFoundError(err.Error()) + } + if err := controllers.DB.Delete(&comment).Error; err != nil { + return c.HandleInternalServerError("Record Delete Failure") + } + r := Response{"success"} - r := Response{"delete"} return c.RenderJson(r) }
これで各 endpoint にアクセスしてみる
$ revel run github.com/shiro16/golang-bbs # 別窓等で $ curl -H "Content-type: application/json" -X POST -d '{"nickname":"shiro16", "body":"test comment"}' http://localhost:9000/api/v1/comments { "results": { "id": 1, "nickname": "shiro16", "body": "test comment", "created_at": "2015-08-13T21:20:46.681910871+09:00", "updated_at": "2015-08-13T21:20:46.681910871+09:00", "deleted_at": null } } # validation がちゃんと機能しているかチェック $ curl -H "Content-type: application/json" -X POST -d '{"nickname":"shiro16", "body":""}' http://localhost:9000/api/v1/comments { "code": 400, "message": "Body: less than min" } $ curl http://localhost:9000/api/v1/comments { "results": [ { "id": 1, "nickname": "shiro16", "body": "test comment", "created_at": "2015-08-13T12:20:47Z", "updated_at": "2015-08-13T12:20:47Z", "deleted_at": null } ] } $ curl http://localhost:9000/api/v1/comments/1 { "results": { "id": 1, "nickname": "shiro16", "body": "test comment", "created_at": "2015-08-13T12:20:47Z", "updated_at": "2015-08-13T12:20:47Z", "deleted_at": null } } $ curl http://localhost:9000/api/v1/comments/2 { "code": 404, "message": "record not found" } $ curl -X DELETE http://localhost:9000/api/v1/comments/1 { "results": "success" }
まとめ
こんな感じで雑な部分もありますが revel を使って基本的な処理の作成が完了しました。
validation に関しては model にてチェックを行った方がいいかと思いますが、
今回は時間の関係で controller にて行っています。
今回作成したものはこちらで公開しています。
API 以外の処理も追加していますので参考にしてください。
今回の説明以外の詳しい内容は下記を参考にするといいと思います。
とくに sample を配布しているので「go get github.com/revel/samples」で取得して
見てみるといいかと思います。
Welcome to Revel, the Web Framework for Go!