ginを使ってMVCモデルのWebアプリを作成する
【環境】
MacBook Air (M1, 2020)
OS: MacOS Big Sur version11.6
Docker Desktop for Mac version4.5.0
golangのフレームワークginを使って簡単なMVCモデルWebサイトを作成してみます。
MVCモデル
Model, View, Controllerと役割を分けて整理するモデルです。
Model
- ビジネスロジックを担当する。具体的にはデータベースへアクセスしCRUDを実行する。
- データをデータベースやgolangで使いやすい形に変換する。
View
- ブラウザに表示するhtml等、ユーザーが見て確認できる部分を担当する。
- ここではgolangの標準パッケージhtml/templateをViewとする。
Controller
- ModelとViewの橋渡し役を担当する。
- クライアントから送られたURIをrouter.goでルーティングしModelでCRUDを実行→Modelで得たデータをViewで表示など。
- ビジネスロジックは全てModelに任せる。(Controllerは薄く作る。)
ディレクトリ構成
mvc_test
├── cmd
│ └── mvc_test
│ └── main.go
├── controller
│ ├── blog_controller.go
│ └── router.go
├── go.mod
├── go.sum
├── model
│ ├── blog_model.go
│ └── database.go
└── view
├── create.html
├── delete.html
├── edit.html
├── index.html
└── show.html
MySQLをDocker経由でインストールしましたが、記事内では省略しました。
Dockerやデータベースについてはこちらの記事をご参照ください。
ルーティング実装
まずはサーバーへURIが投げられた際のルーティングです。
package controller
import (
"github.com/gin-gonic/gin"
)
func GetRouter() *gin.Engine {
r := gin.Default()
r.LoadHTMLGlob("view/*html")
r.GET("/", ShowAllBlog)
r.GET("/show/:id", ShowOneBlog)
r.GET("/create", ShowCreateBlog)
r.POST("/create", CreateBlog)
r.GET("/edit/:id", ShowEditBlog)
r.POST("/edit", EditBlog)
r.GET("/delete/:id", ShowCheckDeleteBlog)
r.POST("/delete", DeleteBlog)
return r
}
router.goでginを使ったルーティングを実装しています。
Handlerとして登録している関数は同じcontrollerパッケージのblog_controller.goのものです。
package main
import (
"mvc_test/controller"
)
func main() {
router := controller.GetRouter()
router.Run(":8080")
}
controllerパッケージのrouter.goのGetRouter()を実行しgin.Engineを取得します。
Model実装
packageだけblog_model.goに合わせてmodelとします。
package model
import (
"gorm.io/gorm"
)
type BlogEntity struct {
gorm.Model
Title string
Body string
}
func GetAll() (datas []BlogEntity) {
result := Db.Find(&datas)
if result.Error != nil {
panic(result.Error)
}
return
}
func GetOne(id int) (data BlogEntity) {
result := Db.First(&data, id)
if result.Error != nil {
panic(result.Error)
}
return
}
func (b *BlogEntity) Create() {
result := Db.Create(b)
if result.Error != nil {
panic(result.Error)
}
}
func (b *BlogEntity) Update() {
result := Db.Save(b)
if result.Error != nil {
panic(result.Error)
}
}
func (b *BlogEntity) Delete() {
result := Db.Delete(b)
if result.Error != nil {
panic(result.Error)
}
}
GetAll(全件取得)、GetOne(1件取得)でデータベースに登録したBlogEntityデータを取得します。
またCreate(新規作成)、Update(更新)、Delete(削除)はBlogEntityのメソッドとします。(funcと関数名の間に(b *BlogEntity)と記述すると、[データ].メソッド()という形で使用できる。)
Controller実装
package controller
import (
"fmt"
"mvc_test/model"
"strconv"
"github.com/gin-gonic/gin"
)
func ShowAllBlog(c *gin.Context) {
datas := model.GetAll()
c.HTML(200, "index.html", gin.H{"datas": datas})
}
func ShowOneBlog(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
data := model.GetOne(id)
c.HTML(200, "show.html", gin.H{"data": data})
}
func ShowCreateBlog(c *gin.Context) {
c.HTML(200, "create.html", gin.H{})
}
func CreateBlog(c *gin.Context) {
title := c.PostForm("title")
body := c.PostForm("body")
data := model.BlogEntity{Title: title, Body: body}
data.Create()
c.Redirect(301, "/")
}
func ShowEditBlog(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
data := model.GetOne(id)
c.HTML(200, "edit.html", gin.H{"data": data})
}
func EditBlog(c *gin.Context) {
id, _ := strconv.Atoi(c.PostForm("id"))
data := model.GetOne(id)
title := c.PostForm("title")
data.Title = title
body := c.PostForm("body")
data.Body = body
data.Update()
c.Redirect(301, "/")
}
func ShowCheckDeleteBlog(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
data := model.GetOne(id)
c.HTML(200, "delete.html", gin.H{"data": data})
}
func DeleteBlog(c *gin.Context) {
id, _ := strconv.Atoi(c.PostForm("id"))
fmt.Println("delete:", id)
data := model.GetOne(id)
data.Delete()
c.Redirect(301, "/")
}
router.goから呼び出す関数を記述しました。
データの取得・保存・成形は全てModelに任せController内はなるべく薄くするべきとのことですが、初めて使うのであまりうまくできないないように思います...。
View実装
<!DOCTYPE html>
<html>
<head>
<meta httpequiv="ContentType" content="text/html;charset=utf8">
<title>INDEX</title>
</head>
<body>
<h1>INDEX</h1>
{{ range .datas }}
<p>{{ .ID }}: <a href="/show/{{.ID}}">{{.Title}}</a> / <a href="/edit/{{.ID}}">編集</a> / <a href="/delete/{{.ID}}">削除</a></p>
{{ end }}
<p><a href="/create">新規作成</a></p>
</body>
</html>
BlogEntityの一覧を表示します。
<!DOCTYPE html>
<html>
<head>
<meta httpequiv="ContentType" content="text/html;charset=utf8">
<title>新規作成</title>
</head>
<body>
<form method="POST" action="/create" id="create_form">
<p>タイトル: <input type="text" name="title" value="" /></p>
<p>本文:</p>
<p><textarea name="body" rows="10" cols="40"></textarea></p>
</form>
<p>
<input type="submit" form="create_form" value="作成" />
<a href="/"><input type="button" value="戻る" /></a>
</p>
</body>
</html>
BlogEntity新規作成の情報を入力しPOST(/create)でサーバーへ送信します。
<!DOCTYPE html>
<html>
<head>
<meta httpequiv="ContentType" content="text/html;charset=utf8">
<title>記事表示(ID:{{.data.ID}})</title>
</head>
<body>
<p>タイトル: {{.data.Title}}</p>
<p>本文:</p>
<p>{{.data.Body}}</p>
<p><a href="/edit/{{.data.ID}}"><input type="button" value="編集" /></a>
<a href="/"><input type="button" value="戻る" /></a></p>
</body>
</html>
BlogEntity情報を表示します。
<!DOCTYPE html>
<html>
<head>
<meta httpequiv="ContentType" content="text/html;charset=utf8">
<title>記事編集(ID:{.{data.ID}})</title>
</head>
<body>
<form method="POST" action="/edit" id="edit_form">
<input type="hidden" name="id" value="{{ .data.ID }}" />
<p>タイトル: <input type="text" name="title" value="{{.data.Title}}" /></p>
<p>本文:</p>
<p><textarea name="body" rows="10" cols="40">{{.data.Body}}</textarea></p>
</form>
<p>
<button type="submit" form="edit_form">更新</button>
<a href="/"><input type="button" value="戻る" /></a>
</p>
</body>
</html>
BlogEntity情報を表示・変更しPOST(/edit)でサーバーへ送信します。
<!DOCTYPE html>
<html>
<head>
<meta httpequiv="ContentType" content="text/html;charset=utf8">
<title>記事削除(ID:{{.data.ID}})</title>
</head>
<body>
<form method="POST" action="/delete" id="delete_form">
<input type="hidden" name="id" value="{{ .data.ID }}" />
<p>タイトル: {{.data.Title}}</p>
<p>本文:</p>
<p>{{.data.Body}}</p>
<h2>削除しますか?</h2>
</form>
<p>
<button type="submit" form="delete_form">削除</button>
<a href="/"><input type="button" value="キャンセル" /></a>
</p>
</body>
</html>
削除予定のBlogEntity情報を表示しPOST(/delete)でサーバーへ送信します。
参考
Discussion