Posted on

2019年前半の振り返り

2019年が半年を過ぎたので振り返ります。

2019年の抱負

本業頑張るのはもとより、2019年の抱負がある程度固まってきたので書いておきます。 これまでAWSを自分で多く触るケースはあまりありませんでしたが、GCPを使うケースが増えてきたので今年からはAWSではなくGCPをたくさん触っていこうと思います。 …

Google Cloud Platformを使う

Firebaseを触る機会があって、FirestoreCloud Functionsを扱いました。あとGCEに migro instance を使って sandbox ページ( http://sandbox.taisablog.com/)を立ててみたりしました。 FirebaseGCEもかなりいい感じなので今後は個人でちょくちょく使っていくと思います。

数学をやる

間に合わせ程度に以下の投稿にある内容をやりました。次はまたしばらく必要になりそうなタイミングになったら進めてみようと思います。

宅建をとる

いろいろと調べたり少しやってみたりしたところ、以下をやればなんとか合格できる気がしましたが状況が変わってきたので受験するのはやめました。

  • 宅建みやざき塾でテキストを購入(テキストだけの購入は不可)
  • 何回か講義を受ける
  • 3月頃にはじめて10月に向けて通勤時間などを活用して約300時間くらい費やす
  • 直前に対策講義を受ける

体力をつける

相変わらず走れていませんが軽い筋トレをしつつ体重は4キロ程減らせました。あと3キロ減が目標です。

ブログ記事

1月から6月末までで11記事でした。年間月2記事で24記事を目安にしているので1記事程足りないペースでした。

7月の記事

目標の見直し

2019年後半は以下をがっちり身に着けることを目標にします。目標の詳細については違うタイミングで少しブレイクダウンする予定です。

技術系

  • JavaScript(Vue.js)
  • Go
  • CSS / Stylus
  • AWS

その他

  • 健康体を維持する / 体力をつける
Posted on

Go言語 GORM+GinでTODOリストのAPIを作ってみた

前回の「Go言語 GORM+GinでTODOリストを作ってみた」に続いて「GORM+Gin」でTODOリストのAPIを作ってみました。ソースコードは前回からの差分だけを記載しています。できたものは下記URLから確認できます。
http://sandbox.taisablog.com/api/v1/

GinのGithub

事前情報

ルーティングは今回はAPIなので以下としました。モデルをtasksにすればよかったと思いましたが一旦このままにしておきます。

[GIN-debug] GET    /todo                     --> main.main.func1 (3 handlers) // 一覧表示
[GIN-debug] POST   /todo                     --> main.main.func2 (3 handlers) // 新規作成
[GIN-debug] GET    /todo/:id                 --> main.main.func3 (3 handlers) // 編集画面表示
[GIN-debug] PUT    /todo/:id                 --> main.main.func4 (3 handlers) // 編集
[GIN-debug] DELETE /todo/:id                 --> main.main.func5 (3 handlers) // 削除

ディレクトリ構成

.
├── api
│   └── v1
│       └── todo.go
├── controllers
│   └── todo.go
├── db
│   └── db.go
├── main.go
├── models
│   └── todo.go
├── router
    └── router.go

router.go

router.gor.Group("/api/v1")のAPI用のグループを追加してルーティングを追加しました。

package router
import (
  "github.com/gin-contrib/cors"
  "github.com/gin-gonic/gin"
  "github.com/jinzhu/gorm"
  v1 "github.com/taisa831/sandbox-gin/api/v1"
  "github.com/taisa831/sandbox-gin/controllers"
  "time"
)
func Router(dbConn *gorm.DB) {
  todoHandler := controllers.TodoHandler{
    Db: dbConn,
  }
  r := gin.Default()
  r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"*"},
    AllowMethods:     []string{"PUT", "PATCH", "DELETE", "POST", "GET"},
    AllowHeaders:     []string{"Origin"},
    ExposeHeaders:    []string{"Content-Length"},
    AllowCredentials: true,
    AllowOriginFunc: func(origin string) bool {
      return origin == "*"
    },
    MaxAge: 12 * time.Hour,
  }))
  r.LoadHTMLGlob("templates/*")
  r.GET("/todo", todoHandler.GetAll) // 一覧画面
  r.POST("/todo", todoHandler.CreateTask) // 新規作成
  r.GET("/todo/:id", todoHandler.EditTask) // 編集画面
  r.POST("/todo/edit/:id", todoHandler.UpdateTask) // 更新
  r.POST("/todo/delete/:id", todoHandler.DeleteTask) // 削除
  apiV1 := r.Group("/api/v1")
  {
    apiTodoHandler := v1.TodoHandler{
      Db: dbConn,
    }
    apiV1.GET("/todo", apiTodoHandler.GetAll) // 一覧画面
    apiV1.POST("/todo", apiTodoHandler.CreateTask) // 新規作成
    apiV1.GET("/todo/:id", apiTodoHandler.EditTask) // 編集画面
    apiV1.PUT("/todo/:id", apiTodoHandler.UpdateTask) // 更新
    apiV1.DELETE("/todo/:id", apiTodoHandler.DeleteTask) // 削除
  }
  r.Run(":9000")
}

api/v1/todo.go

JSONで受けた値を処理してJSONを返すように変更しました。

package v1
import (
  "github.com/gin-gonic/gin"
  "github.com/jinzhu/gorm"
  "github.com/taisa831/gin-sandbox/models"
  "net/http"
)
type TodoHandler struct {
  Db *gorm.DB
}
func (h *TodoHandler) GetAll(c *gin.Context) {
  var todos []models.Todo
  h.Db.Find(&todos)
  c.JSON(http.StatusOK, todos)
}
func (h *TodoHandler) CreateTask(c *gin.Context) {
  todo := models.Todo{}
  err := c.BindJSON(&todo)
  if err != nil {
    c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
    return
  }
  h.Db.Create(&todo)
  c.JSON(http.StatusOK, &todo)
}
func (h *TodoHandler) EditTask(c *gin.Context) {
  todo := models.Todo{}
  id := c.Param("id")
  h.Db.First(&todo, id)
  c.JSON(http.StatusOK, todo)
}
func (h *TodoHandler) UpdateTask(c *gin.Context) {
  todo := models.Todo{}
  id := c.Param("id")
  h.Db.First(&todo, id)
  err := c.BindJSON(&todo)
  if err != nil {
    c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
      "error": err.Error(),
    })
  }
  h.Db.Save(&todo)
  c.JSON(http.StatusOK, &todo)
}
func (h *TodoHandler) DeleteTask(c *gin.Context) {
  todo := models.Todo{}
  id := c.Param("id")
  h.Db.First(&todo, id)
  err := h.Db.First(&todo, id).Error
  if err != nil {
    c.AbortWithStatus(http.StatusNotFound)
    return
  }
  h.Db.Delete(&todo)
  c.JSON(http.StatusOK, gin.H{
    "status": "ok",
  })
}

動作確認

ターミナルでjsonをみやすくするようにjsonppを入れておきます。

brew install jsonpp

一覧取得

% curl -X GET -H "Content-Type: application/json" http://localhost:9000/api/v1/todo | jsonpp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   271  100   271    0     0  44172      0 --:--:-- --:--:-- --:--:-- 45166
[
  {
    "ID": 1,
    "CreatedAt": "2019-07-04T10:16:28+09:00",
    "UpdatedAt": "2019-07-04T10:16:28+09:00",
    "DeletedAt": null,
    "Text": "テスト",
    "Status": 1
  },
  {
    "ID": 2,
    "CreatedAt": "2019-07-04T10:16:38+09:00",
    "UpdatedAt": "2019-07-04T10:16:38+09:00",
    "DeletedAt": null,
    "Text": "実装",
    "Status": 1
  }
]

新規作成

% curl -X POST -H "Content-Type: application/json" -d '{"text":"test", "status":2}' http://localhost:9000/api/v1/todo | jsonpp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   172  100   145  100    27  23592   4393 --:--:-- --:--:-- --:--:-- 24166
{
  "ID": 3,
  "CreatedAt": "2019-07-04T10:18:45.041387+09:00",
  "UpdatedAt": "2019-07-04T10:18:45.041387+09:00",
  "DeletedAt": null,
  "Text": "test",
  "Status": 2
}

更新

% curl -X PUT -H "Content-Type: application/json" -d '{"text":"update", "status":3}' http://localhost:9000/api/v1/todo/1 | jsonpp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   169  100   140  100    29  20951   4340 --:--:-- --:--:-- --:--:-- 23333
{
  "ID": 1,
  "CreatedAt": "2019-07-04T10:16:28+09:00",
  "UpdatedAt": "2019-07-04T10:19:45.818126+09:00",
  "DeletedAt": null,
  "Text": "update",
  "Status": 3
}

削除

% curl -X DELETE -H "Content-Type: application/json" http://localhost:9000/api/v1/todo/1 | jsonpp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    16  100    16    0     0   2025      0 --:--:-- --:--:-- --:--:--  2285
{
  "status": "ok"
}

参考:
https://github.com/hugomd/go-todo
https://github.com/gin-gonic/gin
今回のソース:
https://github.com/taisa831/gin-sandbox

Posted on

Go言語 GORM+GinでTODOリストを作ってみた

前回の「Go言語 ORMライブラリ GORMの使い方」に続いて「GORM+Gin」でTODOリストを作ってみました。使い方は「GitHubのREADME」を参考にしました。できたものは下記URLから確認できます。装飾は別途やれればと。
http://gin.taisablog.com/todo

事前情報

ルーティングは通常のフォームだとPUT/DELETEが使えないので以下のようにしました。

[GIN-debug] GET    /todo                     --> main.main.func1 (3 handlers) // 一覧表示
[GIN-debug] POST   /todo                     --> main.main.func2 (3 handlers) // 新規作成
[GIN-debug] GET    /todo/:id                 --> main.main.func3 (3 handlers) // 編集画面表示
[GIN-debug] POST   /todo/edit/:id            --> main.main.func4 (3 handlers) // 編集
[GIN-debug] POST   /todo/delete/:id          --> main.main.func5 (3 handlers) // 削除

main.goだけで作成した場合

main.goに全ての処理を記述しています。

package main
import (
  "github.com/gin-gonic/gin"
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/mysql"
  "net/http"
  "strconv"
)
type Todo struct {
  gorm.Model
  Text   string
  Status uint64
}
func main() {
  db, err := gorm.Open("mysql", "gorm:gorm@/sandbox_gin?charset=utf8mb4&parseTime=True&loc=Local")
  if err != nil {
    panic("データベースへの接続に失敗しました")
  }
  defer db.Close()
  db.LogMode(true)
  db.AutoMigrate(&Todo{})
  r := gin.Default()
  r.LoadHTMLGlob("templates/*")
  // 一覧画面
  r.GET("/todo", func(c *gin.Context) {
    var todos []Todo
    db.Find(&todos)
    c.HTML(http.StatusOK, "index.html", gin.H{
      "todos": todos,
    })
  })
  // 新規作成
  r.POST("/todo", func(c *gin.Context) {
    text, _ := c.GetPostForm("text")
    status, _ := c.GetPostForm("status")
    istatus, _ := strconv.ParseUint(status, 10, 32)
    db.Create(&Todo{Text: text, Status: istatus})
    c.Redirect(http.StatusMovedPermanently, "/todo")
  })
  // 編集画面
  r.GET("/todo/:id", func(c *gin.Context) {
    todo := Todo{}
    id := c.Param("id")
    db.First(&todo, id)
    c.HTML(http.StatusOK, "edit.html", gin.H{
      "todo": todo,
    })
  })
  // 編集
  r.POST("/todo/edit/:id", func(c *gin.Context) {
    todo := Todo{}
    id := c.Param("id")
    text, _ := c.GetPostForm("text")
    status, _ := c.GetPostForm("status")
    istatus, _ := strconv.ParseUint(status, 10, 32)
    db.First(&todo, id)
    todo.Text = text
    todo.Status = istatus
    db.Save(&todo)
    c.Redirect(http.StatusMovedPermanently, "/todo")
  })
  // 削除
  r.POST("/todo/delete/:id", func(c *gin.Context) {
    todo := Todo{}
    id := c.Param("id")
    db.First(&todo, id)
    db.Delete(&todo)
    c.Redirect(http.StatusMovedPermanently, "/todo")
  })
  r.Run(":9000")
}

ファイルを分割した場合

main.goだけだとちょっと味気ないのでWebフレームワークっぽい構成にしてみました。書き方は色々だと思うので「参考」としてみてもらえればと思います。

ディレクトリ構成

.
├── controllers
│   └── todo.go
├── db
│   └── db.go
├── main.go
├── models
│   └── todo.go
├── router
│   └── router.go
└── templates
    ├── edit.html
    └── index.html

main.go

DB初期化とRouter初期化の呼び出し

package main
import (
  "github.com/taisa831/sandbox-gin/db"
  "github.com/taisa831/sandbox-gin/router"
)
func main() {
  dbConn := db.Init()
  router.Router(dbConn)
}

db/db.go

DB初期化

package db
import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/mysql"
  "github.com/taisa831/sandbox-gin/models"
)
func Init() *gorm.DB {
  db, err := gorm.Open("mysql", "gorm:gorm@/sandbox_gin?charset=utf8mb4&parseTime=True&loc=Local")
  if err != nil {
    panic("データベースへの接続に失敗しました")
  }
  db.LogMode(true)
  db.AutoMigrate(&models.Todo{})
  return db
}

models/todo.go

modelsにはモデルの情報だけ記述

package models
import "github.com/jinzhu/gorm"
type Todo struct {
 gorm.Model
 Text string
 Status uint64
}

router/router.go

ルート情報を記述して起動

package router
import (
  "github.com/gin-gonic/gin"
  "github.com/jinzhu/gorm"
  "github.com/taisa831/sandbox-gin/controllers"
)
func Router(dbConn *gorm.DB) {
  todoHandler := controllers.TodoHandler{
    Db: dbConn,
  }
  r := gin.Default()
  r.LoadHTMLGlob("templates/*")
  r.GET("/todo", todoHandler.GetAll) // 一覧画面
  r.POST("/todo", todoHandler.CreateTask) // 新規作成
  r.GET("/todo/:id", todoHandler.EditTask) // 編集画面
  r.POST("/todo/edit/:id", todoHandler.UpdateTask) // 更新
  r.POST("/todo/delete/:id", todoHandler.DeleteTask) // 削除
  r.Run(":9000")
}

controllers/todo.go

実際にWebから呼び出された時の処理

package controllers
import (
  "github.com/gin-gonic/gin"
  "github.com/jinzhu/gorm"
  "github.com/taisa831/sandbox-gin/models"
  "net/http"
  "strconv"
)
type TodoHandler struct {
  Db *gorm.DB
}
func (h *TodoHandler) GetAll(c *gin.Context) {
  var todos []models.Todo
  h.Db.Find(&todos)
  c.HTML(http.StatusOK, "index.html", gin.H{
    "todos": todos,
  })
}
func (h *TodoHandler) CreateTask(c *gin.Context) {
  text, _ := c.GetPostForm("text")
  status, _ := c.GetPostForm("status")
  istatus, _ := strconv.ParseUint(status, 10, 32)
  h.Db.Create(&models.Todo{Text: text, Status: istatus})
  c.Redirect(http.StatusMovedPermanently, "/todo")
}
func (h *TodoHandler) EditTask(c *gin.Context) {
  todo := models.Todo{}
  id := c.Param("id")
  h.Db.First(&todo, id)
  c.HTML(http.StatusOK, "edit.html", gin.H{
    "todo": todo,
  })
}
func (h *TodoHandler) UpdateTask(c *gin.Context) {
  todo := models.Todo{}
  id := c.Param("id")
  text, _ := c.GetPostForm("text")
  status, _ := c.GetPostForm("status")
  istatus, _ := strconv.ParseUint(status, 10, 32)
  h.Db.First(&todo, id)
  todo.Text = text
  todo.Status = istatus
  h.Db.Save(&todo)
  c.Redirect(http.StatusMovedPermanently, "/todo")
}
func (h *TodoHandler) DeleteTask(c *gin.Context) {
  todo := models.Todo{}
  id := c.Param("id")
  h.Db.First(&todo, id)
  h.Db.Delete(&todo)
  c.Redirect(http.StatusMovedPermanently, "/todo")
}

templates/index.html




    
    TODOリスト
    



    新規追加:
    
        未対応
        対応中
        完了
    
    


{{range .todos}} {{end}}
TODO ステータス
{{.Text}} {{if eq .Status 1}} 未対応 {{else if eq .Status 2}} 対応中 {{else if eq .Status 3}} 完了 {{end}} 編集 削除
$(function(){ $('.delete').on('click', function(){ $('#frmDelete').attr('action', '/todo/delete/' + $(this).data('id')) $('#frmDelete').submit() }) })

templates/edit.html




    
    TODOリスト 編集



    テキスト
未対応 対応中 完了

GitHubはこちら
https://github.com/taisa831/sandbox-gin