こんにちは、KOUKIです。
GolangのWebフレームワークであるfiberを使ってAPIを開発しています。
前回は、UserとProductsモデルを関連付ける(LINK)テーブルモデル/APIを実装しました。
今回は、Order APIを実装します。
尚、この記事に出てくるソースコードは、Udemyの「React, NextJS and Golang: A Rapid Guide – Advanced」コースを参考にしています。解釈は私が勝手に付けているので、本物をみたい場合は受講をお勧めします!
<目次>
前回
事前準備
フォルダ/ファイル作成
1 2 3 4 |
touch src/models/order.go touch src/controllers/orderController.go mkdir src/commands/order touch src/commands/order/populateOrders.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
今回は、「/api/admin/orders」への処理を実装します。
Order API
Order APIは、ユーザーの注文情報を扱うAIPです。
Order モデルの追加
Order(注文)とOrderItem(商品)を分けて、構造体を作成します。
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 |
// order.go package models type Order struct { Model TransactionID string `json:"transaction_id" gorm:"null"` UserID uint `json:"user_id"` Code string `json:"code"` AmbassadorEmail string `json:"ambassador_email"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email"` Address string `json:"address" gorm:"null"` City string `json:"city" gorm:"null"` Country string `json:"country" gorm:"null"` Zip string `json:"zip" gorm:"null"` Complete bool `json:"complete" gorm:"default:false"` OrderItems []OrderItem `json:"order_items" gorm:"foreignKey:OrderID"` } type OrderItem struct { Model OrderID uint `json:"order_id"` ProductTitle string `json:"product_title"` Price float64 `json:"price"` Quantity uint `json:"quantity"` AdminRevenue float64 `json:"admin_revenue"` AmbassadorRevenue float64 `json:"ambassador_revenue"` } |
マイグレーションの追加
GORMのマイグレーション機能を使って、Order/OrderItemモデルからDBのテーブルスキーマーを作成します。
1 2 3 4 5 6 7 |
// database/db.go ... func AutoMigrate() { // User構造体に沿ってテーブルのスキーマーを作成する DB.AutoMigrate(models.User{}, models.Product{}, models.Link{}, models.Order{}, models.OrderItem{}) } |
これで、dockerコンテナを立ち上げるだけで、テーブルスキーマーが自動作成されるようになりました。
1 |
docker-compose up |
ルートの追加
「/api/admin/orders」のルートを追加します。
1 2 3 4 5 6 7 8 9 10 |
// routes/routes.go ... func Setup(app *fiber.App) { ... // Middleware ... adminAuthenticated.Get("orders", controllers.Orders) } |
コントローラーの追加
Orderコントローラーを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// controllers/orderController.go package controllers import ( "admin/src/database" "admin/src/models" "github.com/gofiber/fiber/v2" ) func Orders(ctx *fiber.Ctx) error { var orders []models.Order // DB検索 database.DB.Find(&orders) return ctx.JSON(orders) } |
ここでは、Orderテーブルから全てのデータを取得する処理を実装しました。
動作検証
以下のパラメーターで、動作確認をしましょう。
- URL: http://localhost:8000/api/admin/orders
- 形式: GET
※ログインしていない場合は、ログインしてください

データがない為、戻り値は空ですが、特にエラーが発生しなかったのでこれでOKです。
テストデータの追加
テストデータを追加するプログラムを実装しましょう。
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 |
// orderProducts.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 Order...") for i := 0; i < 30; i++ { var orderItems []models.OrderItem for j := 0; j < rand.Intn(5); j++ { price := float64(rand.Intn(90) + 10) qty := uint(rand.Intn(5)) orderItems = append(orderItems, models.OrderItem{ ProductTitle: faker.Word(), Price: price, Quantity: qty, AdminRevenue: 0.9 * price * float64(qty), AmbassadorRevenue: 0.1 * price * float64(qty), }) database.DB.Create( &models.Order{ UserID: uint(rand.Intn(38) + 1), Code: faker.Username(), AmbassadorEmail: faker.Email(), FirstName: faker.FirstName(), LastName: faker.LastName(), Email: faker.Email(), Complete: true, OrderItems: orderItems, }) log.Println(fmt.Sprintf("Created Test Order %d", i+1)) } } log.Println("Finish Test Order!") } |
Makefileに起動コマンドを追加します。
1 2 3 4 5 6 |
.PHONY: create-user create-product create-order up # テストデータ作成 ... create-order: docker-compose run --rm backend sh -c "go run src/commands/order/populateOrders.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 |
$ make create-order docker-compose run --rm backend sh -c "go run src/commands/order/populateOrders.go" Starting go-admin_db_1 ... done go: downloading github.com/bxcodec/faker/v3 v3.6.0 2021/06/12 23:12:07 Creating Test Order... 2021/06/12 23:12:07 Created Test Order 1 2021/06/12 23:12:07 Created Test Order 1 2021/06/12 23:12:07 Created Test Order 2 2021/06/12 23:12:07 Created Test Order 2 2021/06/12 23:12:07 Created Test Order 4 2021/06/12 23:12:07 Created Test Order 4 2021/06/12 23:12:07 Created Test Order 5 2021/06/12 23:12:07 Created Test Order 5 2021/06/12 23:12:07 Created Test Order 5 2021/06/12 23:12:07 Created Test Order 6 2021/06/12 23:12:07 Created Test Order 7 2021/06/12 23:12:07 Created Test Order 7 2021/06/12 23:12:07 Created Test Order 7 2021/06/12 23:12:07 Created Test Order 8 2021/06/12 23:12:07 Created Test Order 9 2021/06/12 23:12:07 Created Test Order 10 2021/06/12 23:12:07 Created Test Order 10 2021/06/12 23:12:07 Created Test Order 11 2021/06/12 23:12:07 Created Test Order 12 2021/06/12 23:12:07 Created Test Order 13 2021/06/12 23:12:07 Created Test Order 14 2021/06/12 23:12:07 Created Test Order 15 2021/06/12 23:12:07 Created Test Order 15 2021/06/12 23:12:07 Created Test Order 16 2021/06/12 23:12:07 Created Test Order 16 2021/06/12 23:12:07 Created Test Order 16 2021/06/12 23:12:07 Created Test Order 16 2021/06/12 23:12:07 Created Test Order 17 2021/06/12 23:12:07 Created Test Order 17 2021/06/12 23:12:07 Created Test Order 19 2021/06/12 23:12:07 Created Test Order 20 2021/06/12 23:12:07 Created Test Order 20 2021/06/12 23:12:07 Created Test Order 20 2021/06/12 23:12:07 Created Test Order 21 2021/06/12 23:12:07 Created Test Order 22 2021/06/12 23:12:07 Created Test Order 22 2021/06/12 23:12:07 Created Test Order 23 2021/06/12 23:12:07 Created Test Order 23 2021/06/12 23:12:07 Created Test Order 24 2021/06/12 23:12:07 Created Test Order 24 2021/06/12 23:12:07 Created Test Order 26 2021/06/12 23:12:07 Created Test Order 26 2021/06/12 23:12:07 Created Test Order 27 2021/06/12 23:12:07 Created Test Order 27 2021/06/12 23:12:07 Created Test Order 28 2021/06/12 23:12:07 Created Test Order 28 2021/06/12 23:12:07 Created Test Order 29 2021/06/12 23:12:07 Created Test Order 30 2021/06/12 23:12:07 Finish Test Order! |
Order APIを叩いて、先ほどのデータが取得できるか確認してみましょう。

Orderデータの取得ができましたね。
order_itemsがnullになっていますが、Preloadingを使うと取得できるようになります。
Preloading
order_itemsを取得するには、GORMのPreloadを使うと便利です。
1 2 3 4 5 6 7 8 |
// controllers/orderController.go ... func Orders(ctx *fiber.Ctx) error { ... // DB検索(Preload付き) database.DB.Preload("OrderItems").Find(&orders) ... } |
DBの検索時に、Preloadを噛ませる。ただこれだけです。
データを再取得しましょう。

今度は、order_itemsのデータが取得できましたね。
Full Name
first_nameとlast_nameが分離しているので、一つにまとめましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// order.go package models type Order struct { ... FirstName string `json:"-"` // 修正 LastName string `json:"-"` // 修正 Name string `json:"name" gorm:"-"` // 追加 ... } type OrderItem struct {...} func (o *Order) FullName() string { return o.FirstName + " " + o.LastName } |
FullNameメソッドを追加しました。これを、コントローラーの中から呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// controllers/orderController.go ... func Orders(ctx *fiber.Ctx) error { var orders []models.Order // DB検索 database.DB.Preload("OrderItems").Find(&orders) for i, order := range orders { orders[i].Name = order.FullName() // Full Nameを追加 } return ctx.JSON(orders) } |
データを確認しましょう。

「name」にFull Nameが表示されているので、OKですね。
合計金額
Full Nameと同じ容量で、order_itemsの合計値の表示にもチャレンジしてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// order.go package models type Order struct { ... Total float64 `json:"total" gorm:"-"` // 追加 OrderItems []OrderItem `json:"order_items" gorm:"foreignKey:OrderID"` } type OrderItem struct {....} ... func (o *Order) GetTotal() float64 { var total float64 = 0 for _, orderItem := range o.OrderItems { total += orderItem.Price * float64(orderItem.Quantity) } return total } |
GetTotalメソッドで、Total金額を計算します。これもコントローラーから呼び出しましょう。
1 2 3 4 5 6 7 8 9 10 11 |
// controllers/orderController.go ... func Orders(ctx *fiber.Ctx) error { ... for i, order := range orders { orders[i].Name = order.FullName() orders[i].Total = order.GetTotal() // 追加 } return ctx.JSON(orders) } |
先ほどと同様に、データを取得してみましょう。

Linkモデルの修正
前回実装したLinkモデルとOrderを紐付けます。
1 2 3 4 5 6 7 |
// link.go package models type Link struct { ... Orders []Order `json:"orders" gorm:"-"`// 追加 } |
Linkは、モデルを繋ぐ関連テーブルです。そこにOrderも追加した感じですね。
Linkコントローラーに以下の修正を加えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// linkController.go package controllers .... func Link(ctx *fiber.Ctx) error { id, _ := strconv.Atoi(ctx.Params("id")) var links []models.Link database.DB.Where("user_id = ?", id).Find(&links) // 追加 for i, link := range links { var orders []models.Order database.DB.Where("code = ? and complete = true", link.Code).Find(&orders) links[i].Orders = orders } return ctx.JSON(links) } |
この修正をしても現時点では何も変わりませんが、後々必要になってくると思います。
次回
次回は、Ambassador APIを実装しましょう。
Go言語まとめ
ソースコード
ここまでのソースコードを以下に記載します。
link.go
1 2 3 4 5 6 7 8 9 10 11 |
// link.go package models type Link struct { Model Code string `json:"code"` UserID uint `json:"user_id"` User User `json:"user" gorm:"foreignKey:UserID"` Products []Product `json:"products" gorm:"many2many:link_products"` Orders []Order `json:"orders" gorm:"-"` } |
order.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 |
// order.go package models type Order struct { Model TransactionID string `json:"transaction_id" gorm:"null"` UserID uint `json:"user_id"` Code string `json:"code"` AmbassadorEmail string `json:"ambassador_email"` FirstName string `json:"-"` LastName string `json:"-"` Name string `json:"name" gorm:"-"` Email string `json:"email"` Address string `json:"address" gorm:"null"` City string `json:"city" gorm:"null"` Country string `json:"country" gorm:"null"` Zip string `json:"zip" gorm:"null"` Complete bool `json:"complete" gorm:"default:false"` Total float64 `json:"total" gorm:"-"` OrderItems []OrderItem `json:"order_items" gorm:"foreignKey:OrderID"` } type OrderItem struct { Model OrderID uint `json:"order_id"` ProductTitle string `json:"product_title"` Price float64 `json:"price"` Quantity uint `json:"quantity"` AdminRevenue float64 `json:"admin_revenue"` AmbassadorRevenue float64 `json:"ambassador_revenue"` } func (o *Order) FullName() string { return o.FirstName + " " + o.LastName } func (o *Order) GetTotal() float64 { var total float64 = 0 for _, orderItem := range o.OrderItems { total += orderItem.Price * float64(orderItem.Quantity) } return total } |
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 |
// 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.Product{}, models.Link{}, models.Order{}, models.OrderItem{}) } |
routes.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 |
// routes/routes.go package routes import ( "admin/src/controllers" "admin/src/middleware" "github.com/gofiber/fiber/v2" ) func Setup(app *fiber.App) { // GROUP api := app.Group("api") admin := api.Group("admin") // No Middleware admin.Post("register", controllers.Register) admin.Post("login", controllers.Login) // Middleware adminAuthenticated := admin.Use(middleware.IsAuthenticate) adminAuthenticated.Get("user", controllers.User) adminAuthenticated.Post("logout", controllers.Logout) adminAuthenticated.Put("info", controllers.UpdateInfo) adminAuthenticated.Put("password", controllers.UpdatePassword) adminAuthenticated.Get("ambassadors", controllers.Ambassadors) adminAuthenticated.Get("products", controllers.Products) adminAuthenticated.Post("products", controllers.CreateProducts) adminAuthenticated.Get("products/:id", controllers.GetProduct) adminAuthenticated.Put("products/:id", controllers.UpdateProduct) adminAuthenticated.Delete("products/:id", controllers.DeleteProduct) adminAuthenticated.Get("users/:id/links", controllers.Link) adminAuthenticated.Get("orders", controllers.Orders) } |
linkController.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 |
// linkController.go package controllers import ( "admin/src/database" "admin/src/models" "strconv" "github.com/gofiber/fiber/v2" ) func Link(ctx *fiber.Ctx) error { id, _ := strconv.Atoi(ctx.Params("id")) var links []models.Link database.DB.Where("user_id = ?", id).Find(&links) for i, link := range links { var orders []models.Order database.DB.Where("code = ? and complete = true", link.Code).Find(&orders) links[i].Orders = orders } return ctx.JSON(links) } |
orderController.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// controllers/orderController.go package controllers import ( "admin/src/database" "admin/src/models" "github.com/gofiber/fiber/v2" ) func Orders(ctx *fiber.Ctx) error { var orders []models.Order // DB検索 database.DB.Preload("OrderItems").Find(&orders) for i, order := range orders { orders[i].Name = order.FullName() orders[i].Total = order.GetTotal() } return ctx.JSON(orders) } |
orderProducts.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 |
// orderProducts.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 Order...") for i := 0; i < 30; i++ { var orderItems []models.OrderItem for j := 0; j < rand.Intn(5); j++ { price := float64(rand.Intn(90) + 10) qty := uint(rand.Intn(5)) orderItems = append(orderItems, models.OrderItem{ ProductTitle: faker.Word(), Price: price, Quantity: qty, AdminRevenue: 0.9 * price * float64(qty), AmbassadorRevenue: 0.1 * price * float64(qty), }) database.DB.Create( &models.Order{ UserID: uint(rand.Intn(38) + 1), Code: faker.Username(), AmbassadorEmail: faker.Email(), FirstName: faker.FirstName(), LastName: faker.LastName(), Email: faker.Email(), Complete: true, OrderItems: orderItems, }) log.Println(fmt.Sprintf("Created Test Order %d", i+1)) } } log.Println("Finish Test Order!") } |
Makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.PHONY: create-user create-product create-order 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" create-order: docker-compose run --rm backend sh -c "go run src/commands/order/populateOrders.go" # コンテナ起動 up: docker-compose up |
コメントを残す
コメントを投稿するにはログインしてください。