こんにちは、KOUKIです。
前回は、Docker環境上に「cosmtrek/air」というGoのツールを使ってLive Reloading機能(ホットリロード)を追加しました。
ここまでは、APIを開発するための基盤作りですが、今回からいよいよAPIの機能を実装していきます。
尚、この記事に出てくるソースコードは、Udemyの「React, NextJS and Golang: A Rapid Guide – Advanced」コースを参考にしています。解釈は私が勝手に付けているので、本物をみたい場合は受講をお勧めします!
前回
事前準備
フォルダ/ファイルの準備
1 2 3 4 |
mkdir src/routes touch src/routes/routes.go mkdir src/controllers touch src/controllers/authController.go |
モジュールのインストール
1 |
go get golang.org/x/crypto/bcrypt |
作るもの
認証機能のようなものを作りたいと思います。エンドポイントは、以下です。
- POST /api/admin/register
- POST /api/admin/login
- POST /api/admin/logout
- GET /api/admin/user
- PUT /api/admin/users/info
- PUT /api/admin/users/password
本記事では、register(ユーザー登録)とlogin(ログイン)機能を作成したいと思います。
ルートの作成
APIがリクエストを受け取ったときに、エンドポイントのルールに従って処理を振り分る必要があります。例えば、「http://localhost:8000/api/admin/register」にアクセスした時、「ユーザー登録処理」を実行するといった具合にです。
エンドポイントを記述する場所
routes.goには、エンドポイントの一覧を記述します。
1 2 3 4 5 6 7 8 9 10 |
// routes/routes.go package routes import ( "github.com/gofiber/fiber/v2" ) func Setup(app *fiber.App) { // エンドポイントを記述 } |
これは、main.goから呼び出されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package main import ( "admin/src/database" "admin/src/routes" "github.com/gofiber/fiber/v2" ) func main() { // Connection To Mysql database.Connect() // Migration database.AutoMigrate() // fiber API app := fiber.New() // Setup Routes routes.Setup(app) // <<<<<<<<<<<<<, app.Listen(":3000") } |
処理の振り分け先を記述する場所(コントローラー)
本APIは、「リクエスト —–> ルート ——> コントローラー」という流れで、処理がされます。つまり、コントローラーには、エンドポイントに紐づけられた処理を記述します。
1 2 3 4 5 6 7 8 9 |
package controllers import "github.com/gofiber/fiber/v2" func Register(ctx *fiber.Ctx) error { return ctx.JSON(fiber.Map{ "message": "hello", }) } |
暫定ですが、Registerのコントローラーを実装しました。
これを、ルートから呼び出すようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// routes/routes.go package routes import ( "admin/src/controllers" "github.com/gofiber/fiber/v2" ) func Setup(app *fiber.App) { // POST app.Post("/api/admin/register", controllers.Register) } |
RegisterはPOSTリクエストで送信するので、POSTメソッドを使っています。
動作確認
ここまでで、一旦動作確認をしましょう。下記のコマンドで、コンテナを立ち上げておいてください。
1 |
docker-compose up |
ChromeのTalend APIからAPIへ、以下のパラメータでリクエストを送信しましょう。
- POST -> http://localhost:8000/api/admin/register

OKですね。
エンドポイントのグループ化
Registerに設定したエンドポイント「/api/admin/register」のapiは、他のエンドポイントと共通です。これをGroupingすることが可能です。
1 2 3 4 5 6 7 8 |
func Setup(app *fiber.App) { // GROUP api := app.Group("api") admin := api.Group("admin") // POST admin.Post("register", controllers.Register) } |
「/api/admin」までのURLをグループとして登録したので、エンドポイントの記述が「register」のみになりました。大変わかりやすいですね。
ユーザー登録処理
ユーザー登録処理(Register)を実装しましょう。
データのバインド
Registerの呼び出しは、以下のパラメータ付きで行います。
1 2 3 4 5 6 7 |
{ "first_name": "self", "last_name": "note", "email": "self@ne.jp", "password": "a", "password_confirm": "a" } |
つまり、API側でこれらのパラメータを取得する必要があるわけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// controllers/auathController.go package controllers import ( "admin/src/database" "admin/src/models" "github.com/gofiber/fiber/v2" "golang.org/x/crypto/bcrypt" ) func Register(ctx *fiber.Ctx) error { var data map[string]string if err := ctx.BodyParser(&data); err != nil { return err } .... } |
BodyParserメソッドを使うと、リクエストからパラメータを取得できます。string型のmapにバインドしたので、data[“first_name”]などを指定すると、値が取得できます。
パスワードチェック
1 2 3 4 5 6 7 8 9 10 11 |
func Register(ctx *fiber.Ctx) error { var data map[string]string ... if data["password"] != data["password_confirm"] { ctx.Status(fiber.StatusBadRequest) // 400 return ctx.JSON(fiber.Map{ "message": "パスワードに誤りがあります", }) } .... } |
パラメータの「password/password_confirm(確認用のパスワード)」は一致している必要があるため、チェックを行っています。もし、不一致だった場合は、HTTPのステータスコードをStatusBadRequest(400)に設定し、呼び出し元に返す処理を実装しました。
なお、StatusBadRequestは定数であり、HTTPのステータスコードを表しています。詳しくは一覧を確認ください。
ハッシュパスワードの作成
平文のままパスワードをDBに保存するわけにもいかないので、ハッシュパスワードを作成します。
1 2 3 4 5 6 7 |
func Register(ctx *fiber.Ctx) error { ... // ハッシュパスワードを作成 pwd, _ := bcrypt.GenerateFromPassword([]byte(data["password"]), 12) ... } |
bcryptパッケージのGenerateFromPasswordメソッドを用いれば、ハッシュを簡単に作成できます。
ユーザーの登録
最後に、ユーザーをDBに登録しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
func Register(ctx *fiber.Ctx) error { var data map[string]string .... // ハッシュパスワードを作成 .... user := models.User{ FirstName: data["first_name"], LastName: data["last_name"], Email: data["email"], Password: pwd, IsAmbassdor: false, } // ユーザー作成 database.DB.Create(&user) return ctx.JSON(user) } |
「database.DB.Create」は、GORMのCreateメソッドです。SQL文無しでデータ登録ができるので、大変便利です。
検証
ユーザーが登録できるか検証します。
Talend APIから以下のパラメータで、POSTリクエストを送信します。
1 2 3 4 5 6 7 |
{ "first_name": "self", "last_name": "note", "email": "self@ne.jp", "password": "a", "password_confirm": "a" } |

OKですね。
DBも確認してみましょう。

問題なく、ユーザーが登録されてますね。
ログイン
次は、ログイン処理を実装します。
ログインするときは、emailとpasswordをAPIヘリクエストと共に送ります。
ルートの追加
ログインのルートを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 |
// routes/routes.go ... func Setup(app *fiber.App) { // GROUP api := app.Group("api") admin := api.Group("admin") // POST admin.Post("register", controllers.Register) admin.Post("login", controllers.Login) // 追加 } |
データのバインド
controllers.goにログイン処理を実装します。
Registerと同様にデータのバインド処理を実装しましょう。
1 2 3 4 5 6 7 8 9 10 |
// controllers/auathController.go func Login(ctx *fiber.Ctx) error { var data map[string]string if err := ctx.BodyParser(&data); err != nil { return err } ... } |
データ検索
GORMのWhereを使うと条件検索できます。今回は、emailを対象にしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func Login(ctx *fiber.Ctx) error { ... var user models.User database.DB.Where("email = ?", data["email"]).First(&user) if user.ID == 0 { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "ユーザーが見つかりませんでした", }) } } |
パスワードチェック
パスワードのチェックを行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func Login(ctx *fiber.Ctx) error { ... // パスワードチェック err := bcrypt.CompareHashAndPassword(user.Password, []byte(data["password"])) if err != nil { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "パスワードに誤りがあります", }) } return ctx.JSON(user) } |
CompareHashAndPasswordメソッドを使えば、パスワードのチェックは楽勝にできます。
検証
ログイン機能を検証してみましょう。
URL: http://localhost:8000/api/admin/login
形式: POST
成功
1 2 3 4 |
{ "email": "self@ne.jp", "password": "a" } |

ユーザーなし
1 2 3 4 |
{ "email": "fadfads", "password": "a" } |

パスワード不一致
1 2 3 4 |
{ "email": "self@ne.jp", "password": "dafdsfa" } |

OKですね。
エラーメッセージのリファクタリング
エラーメッセージの「ユーザーが見つかりませんでした」及び「パスワードに誤りがあります」ですが、ログインする際にどちらかが間違っているとあえて伝える必要がないので、一律に「ログイン情報に誤りがあります」に変更しておきましょう。
次回
次回は、JWTを使ってトークンの発行処理を実装します。
Go言語まとめ
ソースコード
ここまでのソースコードを記載しておきます。
main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package main import ( "admin/src/database" "admin/src/routes" "github.com/gofiber/fiber/v2" ) func main() { // Connection To Mysql database.Connect() // Migration database.AutoMigrate() // fiber API app := fiber.New() // Setup Routes routes.Setup(app) app.Listen(":3000") } |
auathController.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
// controllers/auathController.go package controllers import ( "admin/src/database" "admin/src/models" "github.com/gofiber/fiber/v2" "golang.org/x/crypto/bcrypt" ) func Register(ctx *fiber.Ctx) error { var data map[string]string if err := ctx.BodyParser(&data); err != nil { return err } if data["password"] != data["password_confirm"] { ctx.Status(fiber.StatusBadRequest) // 400 return ctx.JSON(fiber.Map{ "message": "パスワードに誤りがあります", }) } // ハッシュパスワードを作成 pwd, _ := bcrypt.GenerateFromPassword([]byte(data["password"]), 12) user := models.User{ FirstName: data["first_name"], LastName: data["last_name"], Email: data["email"], Password: pwd, IsAmbassdor: false, } // ユーザー作成 database.DB.Create(&user) return ctx.JSON(user) } func Login(ctx *fiber.Ctx) error { var data map[string]string if err := ctx.BodyParser(&data); err != nil { return err } var user models.User database.DB.Where("email = ?", data["email"]).First(&user) if user.ID == 0 { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "ログイン情報に誤りがあります", }) } // パスワードチェック err := bcrypt.CompareHashAndPassword(user.Password, []byte(data["password"])) if err != nil { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "ログイン情報に誤りがあります", }) } return ctx.JSON(user) } |
db.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// database/db.go package database import ( "admin/src/models" "gorm.io/driver/mysql" "gorm.io/gorm" ) const ( // ユーザー:パスワード@tcp(dockerのサービス名(db):port)/db名 dsn = "admin:admin@tcp(db:3306)/ambassador?charset=utf8mb4&parseTime=True&loc=Local" ) var DB *gorm.DB func Connect() { var err error DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic("Could not connect with the database!") } } func AutoMigrate() { // User構造体に沿ってテーブルのスキーマーを作成する DB.AutoMigrate(models.User{}) } |
models.go
1 2 3 4 5 6 7 8 9 10 11 |
// models/user.go package models type User struct { ID uint FirstName string LastName string Email string Password []byte IsAmbassdor bool } |
routes.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// routes/routes.go package routes import ( "admin/src/controllers" "github.com/gofiber/fiber/v2" ) func Setup(app *fiber.App) { // GROUP api := app.Group("api") admin := api.Group("admin") // POST admin.Post("register", controllers.Register) admin.Post("login", controllers.Login) } |
コメントを残す
コメントを投稿するにはログインしてください。