昨日は naoina さんの genmai を使って wiki を書きましたが、今日は同じく naoina さんが開発している kocha を使って簡単な SPA アプリを書いてみたいと思います。
Kocha web application framework for Go
A convenient web application framework for Go
http://naoina.github.io/kocha/
巷の golang の WAF の殆どは、どちらかというと Sinatra 風の、自前でルーティングハンドラ兼コントローラを書いてくタイプの物ですが、kocha はどちらかというと rails 寄りの WAF に位置します。
rails g でコントローラを生成したりモデルを作ったりといった、コマンドラインベースのジェネレータを使ってガシガシと開発していくフローを実現出来ます。以下手順を追って説明します。
アプリケーションひな形を作る
$ kocha new github.com/mattn/kocho
    create directory /home/mattn/dev/godev/github.com/mattn/kocho/app/controller
              create /home/mattn/dev/godev/github.com/mattn/kocho/app/controller/root.go
    create directory /home/mattn/dev/godev/github.com/mattn/kocho/app/view/error
              create /home/mattn/dev/godev/github.com/mattn/kocho/app/view/error/404.html
              create /home/mattn/dev/godev/github.com/mattn/kocho/app/view/error/500.html
    create directory /home/mattn/dev/godev/github.com/mattn/kocho/app/view/layout
              create /home/mattn/dev/godev/github.com/mattn/kocho/app/view/layout/app.html
              create /home/mattn/dev/godev/github.com/mattn/kocho/app/view/root.html
    create directory /home/mattn/dev/godev/github.com/mattn/kocho/config
              create /home/mattn/dev/godev/github.com/mattn/kocho/config/app.go
              create /home/mattn/dev/godev/github.com/mattn/kocho/config/routes.go
              create /home/mattn/dev/godev/github.com/mattn/kocho/main.go
    create directory /home/mattn/dev/godev/github.com/mattn/kocho/public
              create /home/mattn/dev/godev/github.com/mattn/kocho/public/robots.txt
この状態でも kocha run とすれば動作します。この辺は rails に似た動作になっていますね。golang の場合はシングルバイナリがウリですがもちろん kocha でも kocha build とすればシングルバイナリが生成されます。
モデルを作る
このサンプルではユーザ登録画面を作ろうと思います。とは言っても属性は名前(name)だけとなります。
$ kocha g model user
              create app/model/users.go
生成されるコードは以下の通り。
package model
import (
    "github.com/naoina/genmai"
)
type User struct {
    Id int64 `db:"pk" json:"id"`
    genmai.TimeStamp
}
func (m *User) BeforeInsert() error {
    // FIXME: This method is auto-generated by Kocha.
    //        You can remove this method if unneeded.
    return m.TimeStamp.BeforeInsert()
}
func (m *User) AfterInsert() error {
    // FIXME: This method is auto-generated by Kocha.
    //        You can remove this method if unneeded.
    return nil
}
func (m *User) BeforeUpdate() error {
    // FIXME: This method is auto-generated by Kocha.
    //        You can remove this method if unneeded.
    return m.TimeStamp.BeforeUpdate()
}
func (m *User) AfterUpdate() error {
    // FIXME: This method is auto-generated by Kocha.
    //        You can remove this method if unneeded.
    return nil
}
func (m *User) BeforeDelete() error {
    // FIXME: This method is auto-generated by Kocha.
    //        You can remove this method if unneeded.
    return nil
}
func (m *User) AfterDelete() error {
    // FIXME: This method is auto-generated by Kocha.
    //        You can remove this method if unneeded.
    return nil
}
これに Name 属性を足します。
type User struct {
    Id int64 `db:"pk" json:"id"`
    Name string `json:"name"`
    genmai.TimeStamp
}
コントローラを作る
$ kocha g controller users
              create app/controller/users.go
              create app/view/users.html
コントローラのひな形が生成されました。生成されるコードは以下の様になっています。
package controller
import (
    "github.com/naoina/kocha"
)
type Users struct {
    *kocha.DefaultController
}
func (us *Users) GET(c *kocha.Context) kocha.Result {
    // FIXME: auto-generated by kocha
    return kocha.Render(c)
}
func (us *Users) POST(c *kocha.Context) kocha.Result {
    // FIXME: auto-generated by kocha
    return kocha.Render(c)
}
これを、昨日の例を参考に以下の様に修正します。POST は name パラメータを貰ってモデルに登録します。
package controller
import (
    "github.com/mattn/kocho/db"
    "github.com/mattn/kocho/app/model"
    "github.com/naoina/kocha"
)
type Users struct {
    *kocha.DefaultController
}
func (us *Users) GET(c *kocha.Context) kocha.Result {
    var users []model.User
    err := db.Get("default").Select(&users)
    if err != nil {
        return kocha.RenderError(c, 500, err)
    }
    return kocha.RenderJSON(c, users)
}
func (us *Users) POST(c *kocha.Context) kocha.Result {
    name := c.Params.Get("name")
    if name == "" {
        return kocha.RenderError(c, 400, "Bad request")
    }
    user := &model.User{Name: name}
    _, err := db.Get("default").Insert(user)
    if err != nil {
        return kocha.RenderError(c, 500, err)
    }
    return kocha.RenderJSON(c, user)
}
ビューを作る
今回作るサンプルアプリケーションは SPA (Single Page Application) なので、ルートのページのみ編集します。
root.html の編集前は以下の様になっています。
{{define "content"}}
<h1>Welcome to Kocha</h1>
{{end}}
これを弄って以下の様にしました。
{{define "content"}}
<script>
$(function() {
    function update() {
        $.getJSON("/users", function(data) {
            var $u = $('#users').empty();
            $.each(data, function(index, user) {
                $('<li/>').text(user.name).appendTo($u)
            });
        })
    }
    $('#send').click(function() {
        $.post("/users", {"name": $('#name').val()}, function() {
            $('#name').val('');
            update();
        });
    });
    update();
})
</script>
<h1 class="title">ユーザ一覧</h1>
<label for="name">名前</label>
<input id="name" type="text">
<input id="send" type="submit" value="追加">
<ul id="users"></ul>
{{end}}
jQuery を使うので app/view/layout/app.html も以下の様に修正しました。
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
  <link rel="stylesheet" href="style.css" media="all">
  <title>ユーザ一覧</title>
</head>
<body>
  {{template "content" .}}
</body>
</html>
名前欄に入力された文字列を Ajax で POST して、成功すれば一覧を更新する単純なアプリケーションが出来ました。
まとめ
始めに仕組みを覚えるまでに間違ったルータを作ってしまったりモデルが決まらなかったりしますが、一旦作り方を覚えてしまえば rails 並みの開発効率が生み出せると思います。
実際にこのサンプルアプリケーションを作るのに20分程度でした(僕は kocha はずいぶん前から遊んでいたので使い方は大体知ってはいましたが)。
ちなみに kocha、Windows でも動きます!(ここ大事)
例によって作ったプリケーションを github に置いておきます。
mattn/kocho - GitHub
https://github.com/mattn/kocho
