go言語を勉強してだいたい一週間経ちました。最初の3、4日間は書籍を見ながら文法の勉強をしていましたが、今はドキュメントとパッケージのドキュメントやGitHub、Qiitaを見ながら実践してます。
概要
今回はローカル環境にインストールしているMySQLとの接続とCRUD操作を実践してみます。
環境
- go1.11.4 darwin/amd64
- mysql Ver 8.0.12
- curl 7.54.0
参考にしたもの
- https://github.com/ant0ine/go-json-rest
- http://gorm.io/docs/connecting_to_the_database.html
- MySQLインストール時にやること(DBとユーザーの作成等)
この記事でやること
- パッケージのインストール
- MySQLの権限付きユーザの生成
- goプログラム作成
- RESTfullなAPIサーバ
- CRUD操作
- MySQLとの接続
- サーバーの起動
- curlでの動作確認
パッケージのインストール
まずはMySQLドライバーと、GoのORMのパッケージインストール
go-json-rest
がない方はこちらもインストール
$ go get github.com/go-sql-driver/mysql
$ go get github.com/jinzhu/gorm
$ go get github.com/ant0ine/go-json-rest/rest
MySQLの権限付きユーザの生成
$ mysql -u root -p
MySQL内で以下の設定を行う。Host以外、ユーザ名とパスワードはそれぞれ任意でお願いします。
-- データベース名:country
CREATE DATABASE country;
-- ユーザ名:gorm パスワード:passwordで作成。 Hostは'localhost'で設定する。
CREATE USER 'gorm'@'localhost' IDENTIFIED BY 'password';
-- ユーザ名:gorm に DB:country以下のテーブルも含めて権限を与える。
GRANT ALL PRIVILEGES ON country.* TO 'gorm'@'localhost';
権限が委譲できているか確認してみましょう。
SHOW GRANTS FOR 'gorm'@'localhost';
+-----------------------------------------------------------+
| Grants for gorm@localhost |
+-----------------------------------------------------------+
| GRANT USAGE ON *.* TO `gorm`@`localhost` |
| GRANT ALL PRIVILEGES ON `country`.* TO `gorm`@`localhost` |
+-----------------------------------------------------------+
2 rows in set (0.00 sec)
goプログラムの作成
package main
import (
"log"
"net/http"
"time"
"github.com/ant0ine/go-json-rest/rest"
_ "github.com/go-sql-driver/mysql" // エイリアスでprefixを省略できる
"github.com/jinzhu/gorm"
)
type Country struct {
Id int64 `json:"id"`
Name string `sql:"size:1024" json:"name"`
CreatedAt time.Time `json:"createdAt"`
}
type Impl struct {
DB *gorm.DB
}
func (i *Impl) InitDB() {
var err error
// MySQLとの接続。ユーザ名:gorm パスワード:password DB名:country
i.DB, err = gorm.Open("mysql", "gorm:password@/country?charset=utf8&parseTime=True&loc=Local")
if err != nil {
log.Fatalf("Got error when connect database, the error is '%v'", err)
}
i.DB.LogMode(true)
}
// DBマイグレーション
func (i *Impl) InitSchema() {
i.DB.AutoMigrate(&Country{})
}
func main() {
i := Impl{}
i.InitDB()
i.InitSchema()
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
rest.Get("/countries", i.GetAllCountries),
rest.Post("/countries", i.PostCountry),
rest.Get("/countries/:id", i.GetCountry),
rest.Put("/countries/:id", i.PutCountry),
rest.Delete("/countries/:id", i.DeleteCountry),
)
if err != nil {
log.Fatal(err)
}
log.Printf("server started.")
api.SetApp(router)
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
// countriesテーブル内のデータ全出力
func (i *Impl) GetAllCountries(w rest.ResponseWriter, r *rest.Request) {
countries := []Country{}
i.DB.Find(&countries)
w.WriteJson(&countries)
}
// パスパラメータ:idの国の該当データを出力
func (i *Impl) GetCountry(w rest.ResponseWriter, r *rest.Request) {
id := r.PathParam("id")
country := Country{}
if i.DB.Find(&country, id).Error != nil {
rest.NotFound(w, r)
return
}
w.WriteJson(&country)
}
// json形式のデータをPOST {name:国名}
func (i *Impl) PostCountry(w rest.ResponseWriter, r *rest.Request) {
country := Country{}
err := r.DecodeJsonPayload(&country)
if err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
}
err = i.DB.Save(&country).Error
if err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteJson(&country)
}
// パスパラメータ:idの国の該当データのNameを変更し出力
func (i *Impl) PutCountry(w rest.ResponseWriter, r *rest.Request) {
id := r.PathParam("id")
country := Country{}
if i.DB.First(&country, id).Error != nil {
rest.NotFound(w, r)
return
}
updated := Country{}
if err := r.DecodeJsonPayload(&updated); err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
return
}
country.Name = updated.Name
if err := i.DB.Save(&country).Error; err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteJson(&country)
}
// パスパラメータ:idの国の該当データを削除
func (i *Impl) DeleteCountry(w rest.ResponseWriter, r *rest.Request) {
id := r.PathParam("id")
country := Country{}
if i.DB.First(&country, id).Error != nil {
rest.NotFound(w, r)
return
}
if err := i.DB.Delete(&country).Error; err != nil {
rest.Error(w, err.Error(), http.StatusInsufficientStorage)
return
}
w.WriteHeader(http.StatusOK)
}
サーバの起動
ターミナルでサーバを起動させます。
go run main.go
以下の内容が出力されていればサーバが起動しています。
解除したい場合は[Ctr] + [c]
[2019-01-25 15:56:32] [93.30ms] CREATE TABLE `countries` (`id` bigint AUTO_INCREMENT,`name` varchar(1024),`created_at` timestamp NULL , PRIMARY KEY (`id`))
[0 rows affected or returned ]
2019/01/25 15:56:32 server started.
マイグレーションは構造体内を分析してTable作られてるみたい。
サーバの再起動時、MySQL内にCountriesテーブル
があれば再度マイグレーションされることはありません。便利ですね〜。
curlでの動作確認
APIサーバの起動ができたら、__別のターミナルを起動し__curlで動作確認をしてみましょう。
投稿
$ curl -i -H 'Content-Type:application/json' -d '{"name":"JAPAN"}' http://localhost:8080/countries
ステータスコード200とレスポンスが返ってきたら成功です。
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Powered-By: go-json-rest
Date: Fri, 25 Jan 2019 05:43:05 GMT
Content-Length: 86
{
"id": 1,
"country": "JAPAN",
"createdAt": "2019-01-25T14:43:05.110759+09:00"
}
以下のコマンド、色々試してみてください。
全表示(index)
curl -i http://localhost:8080/countries
編集
curl -i -X PUT -H 'Content-Type:application/json' -d '{"name":"Japan"}' http://localhost:8080/countries/1
削除
curl -i -X DELETE http://localhost:8080/countries/2
感想
フレームワーク(Laravel)を経験しているので、ルーターやコントローラーの動きが理解しやすい。
また、モデルは構造体に相当している。ORMもLaravelと同じ。マイグレーションも便利ですね〜。