はじめに
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に上げてます。





