【Go言語】Echo+GORM+MySQLでCRUDを試す

APIフレームワークEchoでapiを作成し、ORMを提供するGORMでMySQLと接続します。

環境は以下です。

  • MacOS
  • go v1.15
  • Echo v3.3.10
  • GORM v1.20.5

手順

今回Echoはインストール済みとします。インストール方法は以下の記事を参考にしてください。

[Go言語]Echoのインストールからハローワールドまで

構造

├── config
│   └── config.go
├── config.ini
├── controller
│   ├── product_controller.go
├── main.go
├── model
│   ├── db.go
│   ├── product.go
├── router
│   └── router.go

GORMインストール

$ go get -u gorm.io/gorm
$ go get -u gorm.io/driver/mysql

参考

コンフィグファイル

DBの情報をconfigファイルに記載して読み込みます。configファイルの読み込みはini.v1を利用しています。

設定方法は以下の記事を参考にしてください

[Go言語]Configファイルをini.v1で読み込む方法

config.ini

[db]
host = 127.0.0.1
port = 8889
user = root
password = root
name = example

config.go

package config

import (
	"log"
	"os"

	"gopkg.in/ini.v1"
)

type ConfigList struct {
	DbHost     string
	DbPort     string
	DbUser     string
	DbPassword string
	DbName     string
}

var Config ConfigList

func init() {
	cfg, err := ini.Load("config.ini") // configファイル読み込み
	if err != nil {
		log.Printf("Failed to read file: %v", err)
		os.Exit(1) // プログラム終了
	}

	Config = ConfigList{
		DbHost:     cfg.Section("db").Key("host").String(),
		DbPort:     cfg.Section("db").Key("port").String(),
		DbUser:     cfg.Section("db").Key("user").String(),
		DbPassword: cfg.Section("db").Key("password").String(),
		DbName:     cfg.Section("db").Key("name").String(),
	}
}

MySQLと接続します。

MySQLと接続します。今回はMAMPのMySQLです。

db.go:

package model

import (
	"log"

	"github.com/selegee/app2/config"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var DB *gorm.DB
var err error

func init() {
	// connect DB
	conf := config.Config
	dsn := conf.DbUser + ":" + conf.DbPassword + "@tcp(" + conf.DbHost + ":" + conf.DbPort + ")/" + conf.DbName + "?charset=utf8mb4&parseTime=True&loc=Local"
	// dsn := "root:root@tcp(127.0.0.1:8889)/app2?charset=utf8mb4&parseTime=True&loc=Local"
	DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) // NOTE: using = to global var , := is only this function
	if err != nil {
		log.Fatalln(dsn + "database can't connect")
	}
	DB.AutoMigrate(&Product{})
}

ポイントは以下のDBとのコネクション作成部分で:=を使わないことです。:=を使うと他のファイルから利用できません。var で定義したDBとは別の変数DBを作成して代入されてしまうためです。

	DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})

モデルを作成

Productモデルを作成します。NameとPriceを持ちます。

product.go:

package model

import (
	"time"

	"gorm.io/gorm"
)

type Product struct {
	ID        uint      `json:"id"`
	Name      string    `json:"name" gorm:"type:varchar(255);not null"`
	Price     uint      `json:"price" gorm:"not null;default:0"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

func (p *Product) FirstById(id uint) (tx *gorm.DB) {
	return DB.Where("id = ?", id).First(&p)
}

func (p *Product) Create() (tx *gorm.DB) {
	return DB.Create(&p)
}

// all collums update
func (p *Product) Save() (tx *gorm.DB) {
	return DB.Save(&p)
}

func (p *Product) Updates() (tx *gorm.DB) {
	// db.Model(&product).Updates(Product{Name: "hoge", Price: 20})
	return DB.Model(&p).Updates(p)
}

func (p *Product) Delete() (tx *gorm.DB) {
	return DB.Delete(&p)
}

func (p *Product) DeleteById(id uint) (tx *gorm.DB) {
	return DB.Where("id = ?", id).Delete(&p)
}

jsonで返す場合のkeyをjson:"id"のように記載しています。記載しないとそのまま大文字のIDが使われてしまいます。

また、MySQLの型をgorm:"type:varchar(255);not null"のように設定しています。マイグレーション時に、指定した型でテーブルが作成されます。

コントローラーを作成

product_controller:

package controller

import (
	"net/http"
	"strconv"

	"github.com/labstack/echo"
	"github.com/selegee/app2/model"
)

func GetProduct(c echo.Context) error {
	i, _ := strconv.Atoi(c.Param("id"))
	id := uint(i)
	product := model.Product{}
	product.FirstById(id)

	return c.JSON(http.StatusOK, product)
}

func CreateProduct(c echo.Context) error {
	name := c.FormValue("name")
	p, _ := strconv.Atoi(c.FormValue("price"))
	price := uint(p)

	product := model.Product{
		Name:  name,
		Price: price,
	}
	product.Create()

	return c.JSON(http.StatusOK, product)
}

func UpdateProduct(c echo.Context) error {
	i, _ := strconv.Atoi(c.Param("id"))
	id := uint(i)
	name := c.FormValue("name")
	p, _ := strconv.Atoi(c.FormValue("price"))
	price := uint(p)

	product := model.Product{
		ID:    id,
		Name:  name,
		Price: price,
	}
	product.Updates()
}

func DeleteProduct(c echo.Context) error {
	i, _ := strconv.Atoi(c.Param("id"))
	id := uint(i)
	product := model.Product{}
	product.DeleteById(id)

	return c.JSON(http.StatusOK, product)
}

APIのrouteを作成

router.go:

package router

import (
	"net/http"

	"github.com/labstack/echo"
	"github.com/selegee/app2/controller"
)

func Init() *echo.Echo {
	e := echo.New()
	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello, World!")
	})

	e.GET("/api/product/:id", controller.GetProduct)
	e.POST("/api/product", controller.CreateProduct)
	e.PUT("/api/product/:id", controller.UpdateProduct)
	e.DELETE("/api/product/:id", controller.DeleteProduct)

	return e
}

実行

今回はポート1323でリクエストを待ち受けます。

$ go run main.go
⇨ http server started on [::]:1323

この時にproductsテーブルが存在しなければテーブルが作成されます。

CREATE TABLE `products` (
  `id` bigint(20) UNSIGNED NOT NULL,
  `name` varchar(255) NOT NULL,
  `price` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
  `created_at` datetime(3) DEFAULT NULL,
  `updated_at` datetime(3) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Create

curlコマンドでAPIを試します。

postで新しくレコードを追加します。

$ curl -X POST http://localhost:1323/api/product  -d "name=hoge&price=100"
{"id":5,"name":"hoge","price":20,"created_at":"2020-11-03T16:00:59.687936+09:00","updated_at":"2020-11-03T16:00:59.687936+09:00"}

Update

先ほど作成したid:5のレコードのpriceを更新します。

$ curl -X PUT http://localhost:1323/api/product/5 -d "name=hoge&price=1000"
{"id":5,"name":"hoge","price":1000,"created_at":"0001-01-01T00:00:00Z","updated_at":"2020-11-03T16:02:52.126321+09:00"}

Read

更新したid:5のレコードを読み込みます。

$ curl -XGET http://localhost:1323/api/product/5 
{"id":5,"name":"hoge","price":1000,"created_at":"2020-11-03T16:00:59.688+09:00","updated_at":"2020-11-03T16:02:52.126+09:00"

Delete

$ curl -X DELETE http://localhost:1323/api/product/5
{"id":0,"name":"","price":0,"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z"}

まとめ

Echo+GORMでMySQLと接続してCRUDを試すことができました。お疲れ様でした。

1 COMMENT

現在コメントは受け付けておりません。