Go x Gin x GORMでToDoアプリを作成してみるで作成したToDoアプリに追加する形で機能拡張や使用したことのない技術を試していきます。
今回はCRUDのRESTful APIを追加します。
開発環境
- MacBook Pro
- go version : 1.12.9
version
$ go version
go version go1.12.9 darwin/amd64
事前情報
Webフレームワーク : Gin
ORM : GORM
DB : sqlite3
依存関係管理ツールdep : dep
プロジェクト構成
├── Gopkg.lock
├── Gopkg.toml
├── README.md
├── api
│ └── v1
│ └── api_task_controller.go
├── controllers
│ └── task_controller.go
├── db
│ └── db.go
├── main.go
├── models
│ └── task.go
├── router
│ └── router.go
├── task.db
├── templates
│ ├── edit.html
│ └── index.html
└── vendor
ソースコードはGitHubにアップロードしています。
api/v1/api_task_controller.go
CRUDの挙動を記述しています。
api/v1/api_task_controller.go
package v1
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/hanadaUG/go-gin-gorm-todo-app/models"
"github.com/jinzhu/gorm"
"net/http"
)
type ApiTaskHandler struct {
Db *gorm.DB
}
// 全件取得
// $ curl -X GET -H "Content-Type: application/json" http://localhost:8080/api/v1/ | jsonpp
func (handler *ApiTaskHandler) GetAll(c *gin.Context) {
var tasks []models.Task // レコード一覧を格納するため、Task構造体のスライスを変数宣言
handler.Db.Find(&tasks) // DBから全てのレコードを取得する
c.JSON(http.StatusOK, tasks) // JSONで全てのレコードを渡す
}
// 新規作成
// $ curl -X POST -H "Content-Type: application/json" -d '{"text":"test"}' http://localhost:8080/api/v1/ | jsonpp
func (handler *ApiTaskHandler) Create(c *gin.Context) {
task := models.Task{}
err := c.BindJSON(&task)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
handler.Db.Create(&task) // レコードを挿入する
c.JSON(http.StatusOK, &task) // JSONで結果を返す
}
// 取得
// $ curl -X GET -H "Content-Type: application/json" http://localhost:8080/api/v1/1 | jsonpp
func (handler *ApiTaskHandler) Get(c *gin.Context) {
task := models.Task{} // Task構造体の変数宣言
id := c.Param("id") // リクエストからidを取得
handler.Db.First(&task, id) // idに一致するレコードを取得する
c.JSON(http.StatusOK, task) // JSONで取得したレコードを返す
}
// 更新
// $ curl -X PUT -H "Content-Type: application/json" -d '{"text":"update"}' http://localhost:8080/api/v1/1 | jsonpp
func (handler *ApiTaskHandler) Update(c *gin.Context) {
task := models.Task{} // Task構造体の変数宣言
id := c.Param("id") // idを取得
handler.Db.First(&task, id) // idに一致するレコードを取得する
request := models.Task{} // Task構造体の変数宣言
err := c.BindJSON(&request)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
task.Text = request.Text // textを上書きする
handler.Db.Save(&task) // 指定のレコードを更新する
c.JSON(http.StatusOK, &task) // JSONで結果を返す
}
// 削除
// $ curl -X DELETE -H "Content-Type: application/json" http://localhost:8080/api/v1/1 | jsonpp
func (handler *ApiTaskHandler) Delete(c *gin.Context) {
task := models.Task{} // Task構造体の変数宣言
id := c.Param("id") // リクエストからidを取得
handler.Db.First(&task, id) // idに一致するレコードを取得する
handler.Db.Delete(&task) // 指定のレコードを削除する
msg := fmt.Sprintf("Task [%s] has been deleted.", id)
c.JSON(http.StatusOK, gin.H{
"message": msg,
})
}
router/router.go
ルーティングの設定を行います。
router/router.go
@@ -2,6 +2,7 @@ package router
import (
"github.com/gin-gonic/gin"
+ v1 "github.com/hanadaUG/go-gin-gorm-todo-app/api/v1"
"github.com/hanadaUG/go-gin-gorm-todo-app/controllers"
"github.com/hanadaUG/go-gin-gorm-todo-app/db"
)
@@ -24,10 +25,27 @@ func Router() {
// 第一引数にパス、第二引数にHandlerを登録する
router.GET("/", handler.GetAll) // 一覧表示
router.POST("/", handler.Create) // 新規作成
- router.GET("/:id", handler.Edit) // 編集画面
+ router.GET("/edit/:id", handler.Edit) // 編集画面
router.POST("/update/:id", handler.Update) // 更新
router.POST("/delete/:id", handler.Delete) // 削除
+ // 共通の /api/v1 をパスに持つルートをグループ化する
+ apiV1 := router.Group("/api/v1")
+ {
+ api := v1.ApiTaskHandler{
+ Db: db.Get(),
+ }
+ // 編集画面の /:id というパスは :id をワイルドカードとして http://localhost:8080/1 だけではなく
+ // http://localhost:8080/api/vi/ も同一パスとして判定してしまう
+ // そのため /edit/:id に変更した
+ // panic: 'api' in new path '/api/v1/' conflicts with existing wildcard ':id' in existing prefix '/:id'
+ apiV1.GET("/", api.GetAll) // 全件取得
+ apiV1.POST("/", api.Create) // 新規作成
+ apiV1.GET("/:id", api.Get) // 取得
+ apiV1.PUT("/:id", api.Update) // 更新
+ apiV1.DELETE("/:id", api.Delete) // 削除
+ }
+
// Routerをhttp.Serverに接続し、HTTPリクエストのリスニングとサービスを開始する
router.Run()
}
templates/index.html
templates/index.html
@@ -16,7 +16,7 @@
{{ range .tasks }}
<li>
<form method="POST" action="/delete/{{.ID}}">
- {{.ID}} : {{ .Text }} [<a href="/{{.ID}}">編集</a>]
+ {{.ID}} : {{ .Text }} [<a href="/edit/{{.ID}}">編集</a>]
<input type="submit" value="削除">
</form>
</li>
動作確認
起動
# Go製ツールでjsonを整形するjsonppをインストールする
# https://jmhodges.github.io/jsonpp/
$ brew install jsonpp
# アプリを起動する
$ go run main.go
[GIN-debug] Listening and serving HTTP on :8080
# curlコマンドでAPIをテストする
$ curl -X GET -H "Content-Type: application/json" http://localhost:8080/api/v1/ | jsonpp
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 135 100 135 0 0 18223 0 --:--:-- --:--:-- --:--:-- 22500
[
{
"ID": 1,
"CreatedAt": "2019-11-04T12:51:05.009403+09:00",
"UpdatedAt": "2019-11-04T12:51:05.009403+09:00",
"DeletedAt": null,
"Text": "test"
}
]
ソースコードはGitHubにアップロードしています。