#自己紹介
普段私は、
一番得意な機械学習(深層学習)をしたり、
Python/Django でWebアプリを開発したり、
TypeScript/Vue or React でフロントエンドの開発をしたり、
PHP/Laravel でWebアプリを開発したり、
さまざまなことを行っています。
趣味で休みの日にGo言語で色々作成しているのですが、型のある世界は素敵だなと昨今感じています。
今最もやりたいことは、Goで大規模なWebアプリケーションを作成したい。
企業案件やご連絡等ございましたらお気軽に下記よりご連絡いただければと思います。
kohei0801nagamatsu@gmail.com
今回やりたいこと
Go言語でもORMマッパーを使用したいということで、
GORMと呼ばれるパッケージを使用するまでについて記事を書こうと思います。
バックエンドをGo言語のEcho
フロントエンドをVue.js(composition api)
で分けているため、今回の記事はバックエンドまでの説明になりますので、
最終的なCRUDの確認はcurl
orpost man
で確認してみてください!!
#環境
MacOS
go v1.16.4
Echo v3.3.10+incompatible
GORM v1.21.11
Goのプロジェクトは全てGo Modulesで管理しています。
#手順
Echoはインストール済みとします。
インストール方法は以下の記事がわかりやすいです。
参考記事
#ディレクトリ構造
.
├── LICENSE
├── README.md
├── common
│ ├── ats.go
│ └── spreadsheet.go
├── config
│ └── config.go
├── config.ini
├── go.mod
├── go.sum
├── handler
│ ├── auth.go
│ ├── create.go
│ └── handler.go
├── main.go
├── model
│ ├── db.go
│ ├── todo.go
│ └── user.go
├── public
│ ├── service-account.json
│ └── test.txt
├── router.go
└── service
└── file.go
GORMのインストール
GORMはGo言語のObject-relational mappingです。
$ go get -u gorm.io/gorm
$ go get -u gorm.io/driver/mysql
configの設定
DBの情報をconfig.iniファイルに記載して読み込みます。
configファイルの読み込みはini.v1を使用する、今回SpreadSheetの操作もあるためSpreadSheetIdも記載しています。
設定方法は以下の記事を参考にしてください
[Go言語]Configファイルをini.v1で読み込む方法
[db]
host = 127.0.0.1
port = 8889
user = root
password = root
name = example
[spreadsheet]
spreadsheetID = XXXXXXXXXXXXXXXXX
package config
import (
"log"
"os"
"gopkg.in/ini.v1"
)
type ConfigList struct {
DbHost string
DbPort string
DbUser string
DbPassword string
DbName string
SpreadID 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(),
SpreadID: cfg.Section("spreadsheet").Key("spreadsheetID").String(),
}
}
#MySQLへの接続
MySQLと接続します。
今回MySQLをbrewコマンドでinstallしたものを使用しています。
環境に合わせて接続先を変えてください。
package model
import (
"make-ats-code/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"
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&User{})
}
#Model の作成
package model
type User struct {
ID int `json:"id" gorm:"praimaly_key"`
Name string `json:"name"`
Password string `json:"password"`
}
func CreateUser(user *User) {
db.Create(user)
}
func FindUser(u *User) User {
var user User
db.Where(u).First(&user)
return user
}
jsonで返す場合のkeyをjson:"id"
のように記載しています。記載しないとそのまま大文字のIDが使われてしまいます。
今回User構造体をあらかじめ定義しており、その構造体を元にDBの操作を行なっております。
Go言語にはクラスの概念がなく、structとinterfaceでクラスの役割を果たせます。
CreateUser関数 新規ユーザーの作成
FindUser関数 ユーザーを検索しuserのポインタ(格納先にのアドレス)を返す
また、MySQLの型をgorm:"praimaly_key"
のように設定しています。マイグレーション時に、指定した型でテーブルが作成されます。
#コントローラーを作成(handlerディレクトリ)
次にhandlerの作成をします。
sign up
log in
を行います。
ログイン時はその人が正しいかどうかを判定して、jwtトークンを持たせた構造体をreturnしています。
package handler
import (
"fmt"
"net/http"
"time"
"make-ats-code/model"
"github.com/dgrijalva/jwt-go"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
type jwtCustomClaims struct {
UID int `json:"uid"`
Name string `json:"name"`
jwt.StandardClaims
}
var signingKey = []byte("secret")
var Config = middleware.JWTConfig{
Claims: &jwtCustomClaims{},
SigningKey: signingKey,
}
func Signup(c echo.Context) error {
user := new(model.User)
if err := c.Bind(user); err != nil {
return err
}
if user.Name == "" || user.Password == "" {
return &echo.HTTPError{
Code: http.StatusBadRequest,
Message: "invalid name or password",
}
}
if u := model.FindUser(&model.User{Name: user.Name}); u.ID != 0 {
return &echo.HTTPError{
Code: http.StatusConflict,
Message: "name already exists",
}
}
model.CreateUser(user)
user.Password = ""
return c.JSON(http.StatusCreated, user)
}
func Login(c echo.Context) error {
u := new(model.User)
if err := c.Bind(u); err != nil {
return err
}
user := model.FindUser(&model.User{Name: u.Name})
if user.ID == 0 || user.Password != u.Password {
return &echo.HTTPError{
Code: http.StatusUnauthorized,
Message: "invalid name or password",
}
}
claims := &jwtCustomClaims{
user.ID,
user.Name,
jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Hour * 72).Unix(),
},
}
fmt.Println(claims.UID, claims.Name)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
t, err := token.SignedString(signingKey)
if err != nil {
return err
}
return c.JSON(http.StatusOK, map[string]string{
"token": t,
})
}
#ルーティングの設定
package main
import (
"make-ats-code/handler"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
func newRouter() *echo.Echo {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Static("/assets", "public/assets")
e.File("/", "public/index.html")
e.POST("/signup", handler.Signup)
e.POST("/login", handler.Login)
return e
}
#実行
$ go run main.go router.go
この時にusersテーブルが存在しなければテーブルが作成されます。
#ユーザーの作成
curlコマンドでAPIを試します。
postで新しくレコードを追加します。
$ curl --location --request POST 'http://localhost:8080/signup' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "テスト",
"password": "test password"
}'
#まとめ
Echo+GORMでMySQLと接続してCRUDを試すことができました。
みなさんもGoライフを楽しみましょう。