ORMのgormを利用して Echo で サインアップとログイン機能を追加する
はじめに
サーバーサイドについて実際に手を動かして勉強するために簡単なアプリケーションを作成しました。 今回は、以前からEchoで開発しているAPIアプリを利用しViewとModelを追加してサインアップとログイン機能を実装。
DBの利用
今回はDockerでMySQLコンテナを利用しました。O/Rマッパーは gorm
を利用しました。
gorm(MySQL)を利用するには、下記のようにimportします。
import ( "gorm.io/driver/mysql" "gorm.io/gorm" )
■ GORMのドキュメント gorm.io
■ GORMのGitHub github.com
model/db.go
package model import ( "log" "github.com/yhidetoshi/apiEchoGAE-local/conf" "gorm.io/driver/mysql" "gorm.io/gorm" ) var db *gorm.DB const ( dbEndpoint = conf.DB_USER + ":" + conf.DB_PASS + "@tcp(" + conf.DB_HOST + ":" + conf.DB_PORT + ")/" + conf.DB_NAME + "?charset=utf8mb4&parseTime=True&loc=Local" ) func init() { var err error //dsn := "root:root@tcp(127.0.0.1:3306)/echo?charset=utf8mb4&parseTime=True&loc=Local" dsn := dbEndpoint db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { log.Println(err) } // Migration // db.AutoMigrate(&User{}) // ex) // カラム削除 //db.Migrator().DropColumn(&User{}, "Test") // レコード追加 //user := User{Name: "sample_user", Password: "hoge1"} //db.(&user) }
データソースネームの指定方法
conf/config.go
dbユーザとパスは "app" などのユーザを作成して付与すべきですが、今はとりあえずrootで。
package conf const ( // MySQL DB_HOST = "localhost" DB_NAME = "echo" DB_PORT = "3306" DB_USER = "root" DB_PASS = "root" // Redis REDIS_HOST = "localhost" REDIS_PORT = "6379" )
MySQLコンテナの用意
- (ex)
docker-compose.yml
version: '3' services: mysql: build: ./docker/mysql/ volumes: - ./docker/mysql/data:/docker-entrypoint-initdb.d - ./docker/mysql/data:/var/lib/mysql - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf image: mysql ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=${PASSWORD}
- (ex)
./docker/mysql/Dockerfile
FROM mysql:8.0.28-debian ENV MYSQL_DATABASE=${DB_NAME} ENV MYSQL_USER=${USER_NAME} ENV MYSQL_PASSWORD=${USER_PASSWPRD} ENV TZ='Asia/Tokyo' #ポートを開ける EXPOSE 3306 #MySQL設定ファイルをイメージ内にコピー ADD ./my.cnf /etc/mysql/conf.d/my.cnf #docker runに実行される CMD ["mysqld"]
./docker/mysql/my.cnf
[mysqld] character-set-server=utf8 [mysql] default-character-set=utf8 [client] default-character-set=utf8
Migrationについて
Migration | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
カラムの追加と削除
- カラム "test" を追加する
type User struct { ID int `json:"id" gorm:"praimaly_key"` Name string `json:"name"` Password string `json:"password"` Test string `json:"test"` // <---- Add }
- カラム "test" を削除する
- User{}の構造体から削除して、以下のコードを追加したら削除できた。
db.Migrator().DropColumn(&User{}, "Test")
model/db.go:22 SLOW SQL >= 200ms [254.879ms] [rows:0] ALTER TABLE `users` DROP COLUMN `Test`
レコードの追加と削除
以下のコードを追加して、ユーザとパスワードを追加。一括でレコードを追加する事も可能。
レコードの作成 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
user := User{Name: "sample_user", Password: "pass"} db.Create(&user)
コード
ディレクトリ構成は以下。
❯ tree . -L 3 . ├── README.md ├── api │ ├── base.go │ ├── client.go │ ├── crypto_coincheck.go │ ├── crypto_coincheck_test.go │ ├── crypto_gmo.go │ ├── crypto_gmo_test.go │ ├── healthcheck.go │ ├── healthcheck_test.go │ ├── investment_trust.go │ └── metal.go ├── conf │ └── config.go ├── docker │ └── mysql │ ├── Dockerfile │ ├── data │ └── my.cnf ├── docker-compose.yml ├── go.mod ├── go.sum ├── handler │ ├── auth.go │ └── top.go ├── main.go ├── model │ ├── db.go │ └── user.go ├── staticcheck.conf └── view ├── login.html ├── signup.html └── top.html
参考) htmlページをレンダリングする事については、前回の記事で書きました。
/apiのコードについては、以前の記事にまとめています。
main.go
package main import ( "io" "net/http" "text/template" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/yhidetoshi/apiEchoGAE-local/api" "github.com/yhidetoshi/apiEchoGAE-local/handler" ) var e = createMux() func createMux() *echo.Echo { e := echo.New() return e } type TemplateRender struct { templates *template.Template } func (t *TemplateRender) Render(w io.Writer, name string, data interface{}, c echo.Context) error { return t.templates.ExecuteTemplate(w, name, data) } func main() { // Echoインスタンス作成 // http.Handle("/", e) e.Use(middleware.Logger()) e.Use(middleware.Recover()) e.Use(middleware.Gzip()) cGmo := api.ClientCryptoGMO{} cGmo.NewClientCryptoGMO() cCc := api.ClientCryptoCoincheck{} cCc.NewClientCryptoCoincheck() // Template renderer := &TemplateRender{ templates: template.Must(template.ParseGlob("view/*.html")), } e.Renderer = renderer // ルーティング // // ヘルスチェック apiG := e.Group("/api") apiG.GET("/healthcheck", api.Healthcheck) // /api/healthcheck (/apiを省略する形になる) // 貴金属の価格を取得 apiG.GET("/metal", api.FetchMetal) // ISINコードを引数に基準価格を取得 apiG.GET("/investment-trust/:isin", api.FetchInvestTrust) // 仮想通貨の価格を取得 apiG.GET("/crypto/gmo", cGmo.FetchCryptoGMO) apiG.GET("/crypto/coincheck", cCc.FetchCryptoCoincheck) // HTMLページ e.GET("/", handler.ShowTopHTML) e.GET("/signup", handler.ShowSignUpHTML) e.POST("/signup", handler.SignUp) e.GET("/login", handler.ShowLoginHTML) e.POST("/login", handler.Login) e.Start(":8080") }
./handler/auth.go
- サインアップとログイン機能
package handler import ( "html" "net/http" "github.com/labstack/echo/v4" "github.com/yhidetoshi/apiEchoGAE-local/model" ) // FormData Loginフォームのデータ処理用 type FormData struct { Name string Password string Message string } // ResponseJSON レスポンスメッセージ type ResponseJSON struct { Message string `json:"message"` } // ShowSignUpHTML サインアップのページ表示 func ShowSignUpHTML(c echo.Context) error { return c.Render(http.StatusOK, "signup", FormData{}) } // ShowLoginHTML Loginページの表示 func ShowLoginHTML(c echo.Context) error { return c.Render(http.StatusOK, "login", FormData{}) } // SignUp サインアップ処理 func SignUp(c echo.Context) error { signUpForm := FormData{ Name: c.FormValue("name"), Password: c.FormValue("password"), } name := html.EscapeString(signUpForm.Name) // Form(Name)に入力されたデータを取得 password := html.EscapeString(signUpForm.Password) // Form(Password)に入力されたデータを取得 if name == "" || password == "" { // nullだけ弾く signUpForm.Message = "Invalid Name or Password" return c.Render(http.StatusOK, "signup", signUpForm) } user := new(model.User) user.Name = name user.Password = password err := c.Bind(user) if err != nil { return err } u := model.GetUser(&model.User{ Name: user.Name, }) if u.ID != 0 { return &echo.HTTPError{ Code: http.StatusConflict, Message: "Name already exists", } } model.CreateUser(user) user.Password = "" responseJSON := ResponseJSON{ Message: "SignUp Success", } return c.JSON(http.StatusOK, responseJSON) } // Login ログイン処理 func Login(c echo.Context) error { loginForm := FormData{ Name: c.FormValue("name"), Password: c.FormValue("password"), } name := html.EscapeString(loginForm.Name) password := html.EscapeString(loginForm.Password) u := new(model.User) u.Name = name u.Password = password err := c.Bind(u) if err != nil { return err } user := model.GetUser( &model.User{Name: u.Name}, ) if u.Name != user.Name || u.Password != user.Password { // FormとDBのデータを比較 return &echo.HTTPError{ Code: http.StatusUnauthorized, Message: "Invalid Name or Password", } } responseJSON := ResponseJSON{ Message: "Login Success", } return c.JSON(http.StatusOK, responseJSON) }
model
model/db.go
は上記に記載済みmodel/user.go
type User struct {}
でDBマイグレーション、ログインするためのユーザを定義。
package model type User struct { ID int `json:"id" gorm:"primaryKey"` Name string `json:"name"` Password string `json:"password"` } func GetUser(u *User) User { var user User db.Where(u).First(&user) return user } func CreateUser(u *User) { db.Create(u) }
view
view/signup.html
{{define "signup"}} <!DOCTYPE html> <html lang="jp"> <head> <meta charset="UTF-8"> <title>サインアップページ</title> </head> <body> <form action="/signup" method="post"> {{if ne .Message ""}} <p>{{.Message}}</p> {{end}} <p>Name</p> <p><input type="text" name="name" value="{{.Name}}"></p> <p>Password</p> <p><input type="password" name="password" value="{{.Password}}"></p> <p><input type="submit" value="Sign Up"></p> </form> </body> </html> {{end}}
view/login.html
{{define "login"}} <!DOCTYPE html> <html lang="jp"> <head> <meta charset="UTF-8"> <title>ログインページ</title> </head> <body> <form action="/login" method="post"> {{if ne .Message ""}} <p>{{.Message}}</p> {{end}} <p>Name</p> <p><input type="text" name="name"></p> <p>Password</p> <p><input type="password" name="password"></p> <p><input type="submit" value="Login"></p> </form> </body> </html> {{end}}
実行結果
http://127.0.0.1:8080/signup
http://127.0.0.1:8080/login
さいごに
今回は MySQLをコンテナで用意して、サインアップやログイン機能を追加しました。 次は、セッションを追加して、Redis管理などを実際に手を動かしながら学習していければと思います。