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を試すことができました。お疲れ様でした。
main.goの記載が無いです!