はじめに
Go
を勉強し始めたということで、O/Rマッパー
も体験しておきます。
今回は、Go で API 作るの続きとして、Gorm
を入れて試してみたいと思います。
早速作ってみる
今回もこれをベースに組み込んでいきます。
Go で API 作るの続きなので、API部分はgin
を使います。
※gin
の使い方は、上記Qiita記事で紹介しているので割愛します。
Gorm を取得する
Docker
で開発しているので、docker compose
コマンドとなります。
DB
はPostgreSQL
を使ってみます。
$ docker compose run --rm back go get -u gorm.io/gorm
$ docker compose run --rm back go get -u gorm.io/driver/postgres
DB と接続
取得したgorm
とpostgres
をimport
import (
+ "gorm.io/driver/postgres"
+ "gorm.io/gorm"
)
DB
と接続するメソッドを作成します。
func gormConnect() *gorm.DB {
dsn := os.Getenv("DATABASE_URL")
db, _ := gorm.Open(postgres.Open(dsn), &gorm.Config{})
db.AutoMigrate(&Todo{})
return db
}
Heroku
にデプロイも試したかったので、dsn
は、DATABASE_URL
の環境変数から取るようにしました。
DATABASE_URL=host=postgres user=postgres password=password dbname=go-todo-api-db port=5432 sslmode=disable TimeZone=Asia/Tokyo
todos
テーブルは、AutoMigrate
を使って作られるようにしました。
db.AutoMigrate(&Todo{})
todos
テーブルはこんな感じで定義しています。
type Todo struct {
gorm.Model
Title string
}
リスト取得(GET: /api/todos
)を作ってみる
上記のようにルートを用意し、getTodos
メソッドが呼ばれるようにします。
Rails
で言う、Todo.all
のように、DB
から取得します。
func getTodos(c *gin.Context) {
var todos [] Todo
db := gormConnect()
db.Find(&todos)
c.JSON(http.StatusOK, gin.H { "todos": todos })
}
Find
メソッドを使います。
データが空なので、何も帰ってこないと分かりづらいです。
試しに2つデータを登録して、GET
で/api/todos
を叩いてみました。
todos
テーブルを作成する際に使っているTodo
の型には、Title
カラムしか指定していないのに、ID
、CreatedAt
、UpdatedAt
、DeletedAt
カラムが追加されています。
これは、gorm.Model
に定義されているそうです。
デフォルトでID
カラムは主キーで、自動でインクリメントもしてくれました。
ちなみにID
カラム以外の独自に定義したカラムを主キー且つ、自動インクリメントしたい場合は、以下のようにすると出来ました。
Id uint `gorm:"primaryKey;autoIncrement`
Title string
リスト追加(POST: /api/todos
)を作ってみる
上記のようにルートを用意し、addTodo
メソッドが呼ばれるようにします。
func addTodo(c *gin.Context) {
type InputTodo struct {
Title string
}
var inputTodo InputTodo
c.BindJSON(&inputTodo)
newTodo := Todo { Title: inputTodo.Title }
db := gormConnect()
db.Create(&newTodo)
c.JSON(http.StatusOK, gin.H { "todo": newTodo })
}
Create
メソッドを使います。
リクエストを投げるだけでは分かりづらいので、追加したTodo
をレスポンスとして返すようにしてみました。
POST
で/api/todos
を叩いてみます。
ID
も自動インクリメントされているし、渡したTitle
が入ったデータが作られていそうです。
リスト更新(PATCH: /api/todos/:ID
)を作ってみる
上記のようにルートを用意し、updateTodo
メソッドが呼ばれるようにします。
※パラメタの受け取り方をId
からID
に変更しました。(r.PATCH("/api/todos/:ID", updateTodo)
)
func updateTodo(c *gin.Context) {
type InputTodo struct {
Title string
}
Id := c.Param("ID")
id, _ := strconv.Atoi(Id)
db := gormConnect()
var todo Todo
db.First(&todo, id)
var inputTodo InputTodo
c.BindJSON(&inputTodo)
db.Model(&todo).Update("Title", inputTodo.Title)
c.JSON(http.StatusOK, gin.H { "todo": todo })
}
Update
メソッドを使います。
First
やFind
で取得した後、カラム毎に値を代入して、Save
もできるそうです。
PATCH
で/api/todos/1
を叩いてみます。
渡したTitle
でID
が1
のレコードが更新されていそうです。
リスト削除(DELETE: /api/todos/:ID
)を作ってみる
上記のようにルートを用意し、deleteTodo
メソッドが呼ばれるようにします。
※パラメタの受け取り方をId
からID
に変更しました。(r.DELETE("/api/todos/:ID", deleteTodo
)
func deleteTodo(c *gin.Context) {
Id := c.Param("ID")
id, _ := strconv.Atoi(Id)
db := gormConnect()
var todo Todo
db.Delete(&todo, id)
}
Delete
メソッドを使います。
DELETE
で/api/todos/1
を叩いてみます。
削除されたことが分かりづらいので、GET
で/api/todos
も叩いてみます。
ID
が1
のレコードが削除されていそうです。
おまけ(Cross-Domain
問題)
今回紹介した方法で、リクエストを送っているツールは、Insomnia
を使っています。
仮に、フロントエンドアプリからリクエストを送るとしたら、Cross-Domain
問題が発生すると思います。
その場合は、cors
の設定を行います。
$ docker compose run --rm back go get -u github.com/gin-contrib/cors
import (
+ "github.com/gin-contrib/cors"
)
r := gin.Default()
+r.Use(cors.New(cors.Config{
+ AllowOrigins: []string{os.Getenv("CORS_ORIGIN")},
+ AllowMethods: []string{"PUT", "PATCH", "DELETE"},
+}))
GET
とPOST
までは問題なかったですが更新のPATCH
あたりからこの問題に直面しました。
適宜、使用するHTTPリクエストメソッドを追加してください。
おまけ(Heroku
のGo
のバージョン認識)
今回使用しているGo
のバージョンは、1.17.1
です。
筆者は、DockerをそのままHeroku
にデプロイしていますが、ライブラリインストールでエラーが発生しました。
../codon/tmp/cache/go-path/pkg/mod/gorm.io/gorm@v1.23.1/logger/sql.go:105:45: rv.IsZero undefined (type reflect.Value has no field or method IsZero)
note: module requires Go 1.14
Go
のバージョンを1.14
以降にしたら良さそうですが、1.17.1
の想定です。
go.mod
にも1.17
と記載されています。
Heroku
のBuild Log
を遡ってみると、1.12.17
を使っていることが判明
----->
!! The go.mod file for this project does not specify a Go version
!!
!! Defaulting to go1.12.17
!!
!! For more details see: https://devcenter.heroku.com/articles/go-apps-with-modules#build-configuration
!!
-----> Using go1.12.17
Heroku
の環境変数に、GOVERSION=1.17.1
を追加すると解決しました。
おわりに
今回のソースはこちらのGitHubに上げてます。