LoginSignup
1
1

More than 3 years have passed since last update.

Goで作成したToDoアプリにRESTful APIを追加してみる

Posted at

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にアップロードしています。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1