こんにちは、KOUKIです。
GolangのWebフレームワークであるfiberを使ってAPIを開発しています。
前回は、Products APIを実装しました。
今回は、ModelのEmbeddedとProductデータの作成プログラムを実装します。
尚、この記事に出てくるソースコードは、Udemyの「React, NextJS and Golang: A Rapid Guide – Advanced」コースを参考にしています。解釈は私が勝手に付けているので、本物をみたい場合は受講をお勧めします!
<目次>
前回
事前準備
フォルダ/ファイル作成
1 2 3 |
touch src/models/model.go mkdir src/commands/product touch src/commands/product/populateProducts.go |
作るもの
Admin機能を作りたいと思います。エンドポイントは、次の通りです。
- GET/POST /api/admin/products
- GET/PUT/DELETE /api/admin/products/{product_id}
- GET /api/admin/users/{user_id}/links
- GET /api/admin/orders
- GET /api/admin/ambassadors
ModelのEmbedded
GoのStructは、共通箇所を抜き出すことができます。
1 2 3 4 5 6 |
// model.go package models type Model struct { ID uint `json:"id"` } |
抜き出したStructは、他のStructに組み込む(Embedded)ことが可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// product.go package models type Product struct { Model Title string `json:"title"` Description string `json:"description"` Image string `json:"image"` Price float64 `json:"price"` } // models/user.go package models import "golang.org/x/crypto/bcrypt" type User struct { Model FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email" gorm:"unique"` Password []byte `json:"-"` IsAmbassador bool `json:"-"` } |
※ ProductのPriceですが、string -> flost64に直しました(間違えてました…)。
この変更で、コントローラーの処理でいくつか修正する箇所があります。
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 |
// // controllers/auathController.go func UpdateInfo(ctx *fiber.Ctx) error { ... user := models.User{ FirstName: data["first_name"], LastName: data["last_name"], Email: data["email"], } user.ID = id // 外だし ... } func UpdatePassword(ctx *fiber.Ctx) error { ... id, _ := middleware.GetUserID(ctx) user := models.User{} user.ID = id ... } // productController.go func UpdateProduct(ctx *fiber.Ctx) error { ... product := models.Product{} product.ID = uint(id) ... } func DeleteProduct(ctx *fiber.Ctx) error { ... product := models.Product{} product.ID = uint(id) ... } |
Productsデータの作成
以前、Userデータの作成プログラムを実装しましたが、そのProductバージョンを実装します。
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 |
// populateProducts.go package main import ( "admin/src/database" "admin/src/models" "fmt" "log" "math/rand" "github.com/bxcodec/faker/v3" ) func main() { // DB接続 database.Connect() log.Println("Creating Test Product...") for i := 0; i < 30; i++ { product := models.Product{ Title: faker.Username(), Description: faker.Username(), Image: faker.URL(), Price: float64(rand.Intn(90) + 10), } database.DB.Create(&product) log.Println(fmt.Sprintf("Created Test Product %d", i+1)) } log.Println("Finish Test Product!") } |
Makefileに以下のコマンドを追加します。
1 2 3 4 5 6 7 |
.PHONY: create-user create-product up # テストデータ作成 ... create-product: docker-compose run --rm backend sh -c "go run src/commands/product/populateProducts.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 |
$ make create-product docker-compose run --rm backend sh -c "go run src/commands/product/populateProducts.go" Starting go-admin_db_1 ... done go: downloading github.com/bxcodec/faker/v3 v3.6.0 2021/06/11 22:18:35 Creating Test Product... 2021/06/11 22:18:35 Created Test Product 1 2021/06/11 22:18:35 Created Test Product 2 2021/06/11 22:18:35 Created Test Product 3 2021/06/11 22:18:35 Created Test Product 4 2021/06/11 22:18:35 Created Test Product 5 2021/06/11 22:18:35 Created Test Product 6 2021/06/11 22:18:35 Created Test Product 7 2021/06/11 22:18:35 Created Test Product 8 2021/06/11 22:18:35 Created Test Product 9 2021/06/11 22:18:35 Created Test Product 10 2021/06/11 22:18:35 Created Test Product 11 2021/06/11 22:18:35 Created Test Product 12 2021/06/11 22:18:35 Created Test Product 13 2021/06/11 22:18:35 Created Test Product 14 2021/06/11 22:18:35 Created Test Product 15 2021/06/11 22:18:35 Created Test Product 16 2021/06/11 22:18:35 Created Test Product 17 2021/06/11 22:18:35 Created Test Product 18 2021/06/11 22:18:35 Created Test Product 19 2021/06/11 22:18:35 Created Test Product 20 2021/06/11 22:18:35 Created Test Product 21 2021/06/11 22:18:35 Created Test Product 22 2021/06/11 22:18:35 Created Test Product 23 2021/06/11 22:18:35 Created Test Product 24 2021/06/11 22:18:35 Created Test Product 25 2021/06/11 22:18:35 Created Test Product 26 2021/06/11 22:18:35 Created Test Product 27 2021/06/11 22:18:35 Created Test Product 28 2021/06/11 22:18:35 Created Test Product 29 2021/06/11 22:18:35 Created Test Product 30 2021/06/11 22:18:35 Finish Test Product! |
OKですね。DBも確認します。

問題ないですね。
次回
次回は、Link APIを実装しましょう。
Go言語まとめ
ソースコード
ここまでのソースコードを以下に記載します。
model.go
1 2 3 4 5 6 |
// model.go package models type Model struct { ID uint `json:"id"` } |
product.go
1 2 3 4 5 6 7 8 9 10 |
// product.go package models type Product struct { Model Title string `json:"title"` Description string `json:"description"` Image string `json:"image"` Price float64 `json:"price"` } |
user.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// models/user.go package models import "golang.org/x/crypto/bcrypt" type User struct { Model FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email" gorm:"unique"` Password []byte `json:"-"` IsAmbassador bool `json:"-"` } func (u *User) SetPassword(pwd string) { // ハッシュパスワードを作成 hashPwd, _ := bcrypt.GenerateFromPassword([]byte(pwd), 12) u.Password = hashPwd } func (u *User) ComparePassword(pwd string) error { return bcrypt.CompareHashAndPassword(u.Password, []byte(pwd)) } |
productController.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 72 73 74 75 76 |
// productController.go package controllers import ( "admin/src/database" "admin/src/models" "strconv" "github.com/gofiber/fiber/v2" ) func Products(ctx *fiber.Ctx) error { var products []models.Product // 全てのプロダクトを取得 database.DB.Find(&products) return ctx.JSON(products) } func CreateProducts(ctx *fiber.Ctx) error { var product models.Product // リクエストデータをパースする if err := ctx.BodyParser(&product); err != nil { return err } // プロダクト取得 database.DB.Create(&product) return ctx.JSON(product) } func GetProduct(ctx *fiber.Ctx) error { // リクエストからIDを取得 id, _ := strconv.Atoi(ctx.Params("id")) var product models.Product product.ID = uint(id) // プロダクト検索 database.DB.Find(&product) return ctx.JSON(product) } func UpdateProduct(ctx *fiber.Ctx) error { // リクエストからIDを取得 id, _ := strconv.Atoi(ctx.Params("id")) product := models.Product{} product.ID = uint(id) if err := ctx.BodyParser(&product); err != nil { return err } // プロダクト更新 database.DB.Model(&product).Updates(&product) return ctx.JSON(product) } func DeleteProduct(ctx *fiber.Ctx) error { // リクエストからIDを取得 id, _ := strconv.Atoi(ctx.Params("id")) product := models.Product{} product.ID = uint(id) // プロダクト削除 database.DB.Delete(&product) return nil } |
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
// controllers/auathController.go package controllers import ( "admin/src/database" "admin/src/middleware" "admin/src/models" "strconv" "time" "github.com/dgrijalva/jwt-go" "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, IsAmbassador: false, } // パスワードセット user.SetPassword(data["password"]) // ユーザー作成 result := database.DB.Create(&user) if result.Error != nil { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "そのEmailは既に登録されています", }) } 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 := user.ComparePassword(data["password"]) if err != nil { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "ログイン情報に誤りがあります", }) } // トークンの発行 payload := jwt.StandardClaims{ Subject: strconv.Itoa(int(user.ID)), ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), } token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, payload).SignedString([]byte("secret")) if err != nil { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "ログイン情報に誤りがあります", }) } // Cookieに保存 cookie := fiber.Cookie{ Name: "jwt", Value: token, Expires: time.Now().Add(time.Hour * 24), HTTPOnly: true, } ctx.Cookie(&cookie) return ctx.JSON(fiber.Map{ "message": "success", }) } func User(ctx *fiber.Ctx) error { id, _ := middleware.GetUserID(ctx) // ユーザー検索 var user models.User database.DB.Where("id = ?", id).First(&user) return ctx.JSON(user) } func Logout(ctx *fiber.Ctx) error { // cookieをクリアする cookie := fiber.Cookie{ Name: "jwt", Value: "", Expires: time.Now().Add(-time.Hour * 24), // -を指定 HTTPOnly: true, } ctx.Cookie(&cookie) return ctx.JSON(fiber.Map{ "message": "success", }) } func UpdateInfo(ctx *fiber.Ctx) error { var data map[string]string // リクエストデータをパースする if err := ctx.BodyParser(&data); err != nil { return err } // cookieからidを取得する id, _ := middleware.GetUserID(ctx) user := models.User{ FirstName: data["first_name"], LastName: data["last_name"], Email: data["email"], } user.ID = id // ユーザー情報更新 database.DB.Model(&user).Updates(&user) return ctx.JSON(user) } func UpdatePassword(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": "パスワードに誤りがあります", }) } // cookieからidを取得する id, _ := middleware.GetUserID(ctx) user := models.User{} user.ID = id // パスワードセット user.SetPassword(data["password"]) // ユーザー情報更新 database.DB.Model(&user).Updates(&user) return ctx.JSON(user) } |
populateProducts.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 |
// populateProducts.go package main import ( "admin/src/database" "admin/src/models" "fmt" "log" "math/rand" "github.com/bxcodec/faker/v3" ) func main() { // DB接続 database.Connect() log.Println("Creating Test Product...") for i := 0; i < 30; i++ { product := models.Product{ Title: faker.Username(), Description: faker.Username(), Image: faker.URL(), Price: float64(rand.Intn(90) + 10), } database.DB.Create(&product) log.Println(fmt.Sprintf("Created Test Product %d", i+1)) } log.Println("Finish Test Product!") } |
Makefile
1 2 3 4 5 6 7 8 9 10 11 12 |
.PHONY: create-user create-product up # テストデータ作成 create-user: docker-compose run --rm backend sh -c "go run src/commands/user/populateUsers.go" create-product: docker-compose run --rm backend sh -c "go run src/commands/product/populateProducts.go" # コンテナ起動 up: docker-compose up |
コメントを残す
コメントを投稿するにはログインしてください。