WESEEK Tech Blog

WESEEK のエンジニアブログです

go言語での開発始めてみる〜beego で掲示板っぽいもの作ってみる編〜

前回の続きでgo言語用のwebフルスタックフレームワークである beego で実際にサービスを作ってみようと思います。前回はコード自動生成機能を試してみて CURD するためのコードを作成しました。何を作るかですがとりあえず掲示板みたいなものを作ろうかと思います。

ユースケースとしてはユーザがフォームから文字列を送信するとDBに文字列が保存され、その文字列を表示ができる。という具合で進めようと思いつつ、beego の機能を触って見ていこうと思います。

書き込みページの作成

scaffold を使っての view は自動作成してくれなかったので自前で HTML を書いていこうと思います。掲示板ということなのでまずは書き込みのページを作っていこうと思います。トップページから掲示板へ遷移して、フォームが見えるというころまでを行おうと思います。

bootstrap 導入

webサイト作りに個人的に欠かせないのは CSSフレームワークである bootstrap こいつを導入していこうと思います。 まずは共通で読み込みのできる layout.html を作っていきます。 公式のドキュメントはこのへん サンプルにはすでに bootstrap3 が入っていましたが、せっかくなので bootstrap4 を入れてみようと思います。 views/layout/layout.html 作成

<!DOCTYPE html>
<html>
<head>
    <title>haruch</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    {{.HtmlHead}}
</head>
<body>

    <div class="container">
        {{.LayoutContent}}
    </div>
    <div>
        {{.SideBar}}
    </div>
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
    {{.Scripts}}
</body>
</html>

そして views/index.tpl に適当に bootstrap のクラスを書き込み動作テストです。

    <div class="alert alert-primary" role="alert">
      bootstrap 入ったよ!
    </div>

特に意味なくアラートですがちゃんと表示されました。 bootstrap_test.png

書き込みページへの遷移

遷移させるには router の機能を使い URL と controller と view をマッピングしていきます。 一旦HTML を表示させるだけにしようと思うので controller の scaffold で自動生成されたコードは無視していこうと思います。

  • router.go の編集 新しく
beego.Include(&controllers.PostController{})

こちらを init() メソッドに追加します。 これを追加することで scaffold で作成された controller に指定した URL でアクセスできるようになります。

  • controller/post.go の編集 index メソッドを生やし、 scaffold で作成された空の post/index.tpl を紐付けます。

 

// Index ...
// @Title Index
// @Description show BBS
// @Success 200
// @Failure 403 body is empty
// @router /post/index [get]
func (c *PostController) Index() {
    c.TplName = "post/index.tpl"
}

そしてアノテーションに書かれている /post/index へアクセスすると何もないページへアクセスできることがわかります。

フォームのコーディング

bootstrap を導入したのでサクッとフォームを作っていきます。 ここはもう適当に bootstrap の公式から example を持ってきます。

<form>
  <div class="form-group">
    <label>タイトル</label>
    <input class="form-control" placeholder="タイトル">
  </div>
  <div class="form-group">
    <label>内容</label>
    <textarea class="form-control" rows="3" placeholder="書き込む内容"></textarea>
  </div>
  <button type="submit" class="btn btn-primary">送信</button>
</form>

bootstrapを適用するために post.goindex() メソッドに c.Layout も忘れずに追加してあげます。

// Index ...
// @Title Index
// @Description show BBS
// @Success 200
// @Failure 403 body is empty
// @router /post/index [get]
func (c *PostController) Index() {
    c.Layout = "layout/layout.html"
    c.TplName = "post/index.tpl"
}

フォーム

サクッと作れます。

DBへの接続設定追加

正直なんでこのタイミングでってやってて思うんですが、 scaffold でコードの自動生成しただけでば DB の接続設定などは一切やってくれないので DB に対しての操作をしようとすると下記のようなエラーが出て、アプリが落ちると思います。

must have one register DataBase alias named `default`

ってことなのでマニュアルに従って設定していきます。 main.go に下記追加です。

func init() {
    orm.RegisterDriver("postgres", orm.DRPostgres)
    orm.RegisterDataBase(
        "default",
        "postgres",
        "user=user password=pass host=127.0.0.1 port=5432 dbname=postgres sslmode=disable")

    orm.RunSyncdb("default", false, true)
}

フォーム入力値をバックエンドへの送信

DB への設定を書いたところで実際に作ったフォームから ORM を利用して DB に値を保存してみようと思います。 まず View のほうの編集をします。 form タグに actuon と method の追加と、inpot と textarea に name を追加しました。

<form action="/post" method="post">
  <div class="form-group">
    <label>タイトル</label>
    <input name="title" class="form-control" placeholder="タイトル">
  </div>
  <div class="form-group">
    <label>内容</label>
    <textarea name="body" class="form-control" rows="3" placeholder="書き込む内容"></textarea>
  </div>
  <button type="submit" class="btn btn-primary">送信</button>
</form>

Controllerのほうは Post() メソッドの @router が ただの / になっていたので下記のように直すのと、そもそも scaffold で生成されたコードは JSON でデータが送られてくることが前提だったみたいなので、POST でパラメータがそのまま送られてきたのを受け取るように修正します。さらに、JSONを返されても困るので、そのまま掲示板へとリダイレクトするように一旦設定しました。

// Post ...
// @Title Post
// @Description create Post
// @Param    body        body    models.Post true        "body for Post content"
// @Success 201 {int} models.Post
// @Failure 403 body is empty
// @router /post [post]
func (c *PostController) Post() {
    var post models.Post
    // データ取得して post オブジェクト作成
    post = models.Post{
        Title: c.GetString("title"),
        Body:  c.GetString("body"),
    }
    // データ保存
    if _, err := models.AddPost(&post); err == nil {
        c.Ctx.Output.SetStatus(201)
        // 一応 json 格納しておく
        c.Data["json"] = post
        // 成功したら掲示板トップにリダイレクト
        c.Redirect("/post/index", 302)
    } else {
        c.Data["json"] = err.Error()
        // 失敗時は一旦エラーをそのまま描画することにする
        c.ServeJSON()
    }
}

これで書き込み画面からタイトルと内容を入力して送信すると DB に値が保存されるようになります。

DBから書き込みデータをViewに表示

今のままでは一方的にDBに書き込むだけで書き込みが閲覧ができません。 当然ですが掲示板サービスなので書き込みが web 上で閲覧できなくては意味がありません。 自動生成された GetAll()メソッドを参考に書かれた内容の取得と view への表示を実装していきたいと思います。

一旦取得時の検索パラメータなどは無視して GetAll() からの内容をほぼコピペし、最新10件を取得して Viewに渡そうと思います。

// Index ...
// @Title Index
// @Description show BBS
// @Success 200
// @Failure 403 body is empty
// @router /post/index [get]
func (c *PostController) Index() {
    c.Layout = "layout/layout.html"
    c.TplName = "post/index.tpl"

    var fields []string
    var sortby []string
    var order []string
    var query = make(map[string]string)
    var limit int64 = 10
    var offset int64

    l, err := models.GetAllPost(query, fields, sortby, order, offset, limit)

    if err != nil {
        c.Data["json"] = err.Error()
        c.ServeJSON()
    } else {
        c.Data["posts"] = l
    }
}

これで View には posts という名前で DB から取得した値が渡ったのではとは HTML を編集していきます。

<hr>

{{range $val := .posts}}
  <div class="card bg-light mb-3">
    <div class="card-header">{{$val.Id}}. ななしさん</div>
    <div class="card-body">
      <h5 class="card-title">{{$val.Title}} </h5>
      <p class="card-text">{{$val.Body}}</p>
    </div>
  </div>
{{end}}

先ほど作成した form タグの下に Controller から送られてくる postsrange を使って表示させてきます。 名前フィールドは DB に定義していなかったので強制的に名無しさんです。

掲示板

こんな感じで書いた内容が反映されるようになりました!

まとめ

何度も言ってしまっていますがDB接続設定などあれこれ自動でやってくれると思いきや、やってくれなかったりするところがありますが、公式に設定方法はあたりまえですが書いてあるので冷静に対処すれば良いという印象でした。

とはいえ自分が go になれてないせいがほとんどだとは思いつつも scaffold で生成されたコードが読みにくく、go って雰囲気じゃ書けない言語なんだなと思い知らされています。C言語をちゃんと学んだことのある人はまた違うとは思うのですが。

完全な個人的な比較にはなってしまいますが Ruby on Rails を何も知らずに初めて触ったときはここまで苦労しなかったって印象でいっぱいですw

とっつきとしてはフレームワークを使って成果物を作るのはすごい良いと思いますが、ここまでくると言語自体もちゃんと勉強したいなと思いました。

次回は機能を追加するか、もしくは雰囲気で書いていたGo言語自体を読み解いていくかのどちらかを考えています。