こんにちは、KOUKIです。
GolangのWebフレームワークであるfiberを使ってAPIを開発しています。
前回は、Redisにキャッシュデータを保存する処理を実装しました。
今回は、プロダクトの検索機能を実装します。
尚、この記事に出てくるソースコードは、Udemyの「React, NextJS and Golang: A Rapid Guide – Advanced」コースを参考にしています。解釈は私が勝手に付けているので、本物をみたい場合は受講をお勧めします!
<目次>
前回
作るもの
Ambassdor機能を作りたいと思います。エンドポイントは、次の通りです。
- GET /api/ambassador/products/frontend
- GET /api/ambassador/products/backend
- POST /api/ambassador/links
- GET /api/ambassador/stats
- GET /api/ambassador/rankings
今回は、「/api/ambassador/products/backend」への処理を追加します。
プロダクトの検索
実務で、検索機能を実装する機会は結構あります。
実装方法はいくつかありますが、ここではURLの末尾にクエリを仕込み、GETリクエストを送ることで実現したいと思います。
1 2 |
// 例: query tkcで検索 http://localhost:8000/api/ambassador/products/backend?q=tkc |
上記のURLでは、「tkc」という文字列を使って、プロダクトの検索を行います。検索クエリに「q=」を指定していますが、これはプログラムで自由に決められるのでなんでも良いです。
ルートの追加
「/api/ambassador/products/backend」へのルートを追加します。
1 2 3 4 5 6 7 8 9 10 11 |
// routes/routes.go ... func Setup(app *fiber.App) { // Group // admin ... // Ambassador ... ambassador.Get("products/backend", controllers.ProductBackend) ... } |
コントローラーの追加
「/api/ambassador/products/backend」へのコントローラーを追加します。
ここに、検索機能を実装します。
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 |
// productController.go func ProductBackend(ctx *fiber.Ctx) error { var products []models.Product var c = context.Background() redisKey := "products_backend" expiredTime := 30 * time.Minute // キャッシュ操作 result, err := database.Cache.Get(c, redisKey).Result() if err != nil { database.DB.Find(&products) productBytes, err := json.Marshal(&products) if err != nil { panic(err) } database.Cache.Set(c, redisKey, productBytes, expiredTime).Err() } else { json.Unmarshal([]byte(result), &products) } var searchProducts []models.Product // 検索 // urlの?q=XXXXから文字列を取得 if q := ctx.Query("q"); q != "" { // 大文字小文字の区別をなくすため、全て小文字扱いにする lower := strings.ToLower(q) for _, product := range products { // 検索条件1: Title if strings.Contains(strings.ToLower(product.Title), lower) || // 検索条件2: Description strings.Contains(strings.ToLower(product.Description), lower) { searchProducts = append(searchProducts, product) } } } else { // 検索しない場合は、全てのデータを返却 searchProducts = products } return ctx.JSON(searchProducts) } |
ここで実装した検索機能ロジックは、以下の通りです。
- Queryメソッドで、URLに”q”の文字列があるかチェック
- qが存在していた場合、検索を行う
- qが存在していない場合、検索を行わず、全てのプロダクト情報を返却
- 検索文字列、検索対象文字列の比較は小文字で行う(大小の区別をなくすため)
- 検索対象は、プロダクトのTitleとDescription
検証
以下のパラメータで、リクエストを送ってみましょう。
- URL: http://localhost:8000/api/ambassador/products/backend?q=tkc
- 形式: GET
- query: tkc
TitleかDescriptionに、tkcが含まれているプロダクトを検索します。

1 2 3 4 5 6 7 8 9 |
[ { "id": 1, "title": "tkcJbpq", "description": "RUcXZlT", "image": "http://tbKWSFP.net/rkODPuv.html", "price": 33 } ] |
Titleにtkcが含まれているので、検索ができました。
次は、クエリなしで実行してみましょう。
- URL: http://localhost:8000/api/ambassador/products/backend

クエリがない場合は、検索ではないとみなされるので、全てのデータを取得しました!
次回
次回は、ソート機能実装しましょう。
Go言語まとめ
ソースコード
ここまでのソースコードを以下に記載します。
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 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 |
// productController.go package controllers import ( "admin/src/database" "admin/src/models" "context" "encoding/json" "strconv" "strings" "time" "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 } func ProductFrontend(ctx *fiber.Ctx) error { var products []models.Product var c = context.Background() redisKey := "products_frontend" expiredTime := 30 * time.Minute // products_frontend keyでRedisからデータを取得 result, err := database.Cache.Get(c, redisKey).Result() if err != nil { database.DB.Find(&products) // Redisにデータを格納するため、エンコードする productBytes, err := json.Marshal(&products) if err != nil { panic(err) } // products_fronend keyでRedisにデータを格納 err = database.Cache.Set(c, redisKey, productBytes, expiredTime).Err() if err != nil { panic(err) } } else { // デコードする json.Unmarshal([]byte(result), &products) } return ctx.JSON(products) } func ProductBackend(ctx *fiber.Ctx) error { var products []models.Product var c = context.Background() redisKey := "products_backend" expiredTime := 30 * time.Minute // キャッシュ操作 result, err := database.Cache.Get(c, redisKey).Result() if err != nil { database.DB.Find(&products) productBytes, err := json.Marshal(&products) if err != nil { panic(err) } database.Cache.Set(c, redisKey, productBytes, expiredTime).Err() } else { json.Unmarshal([]byte(result), &products) } var searchProducts []models.Product // 検索 // urlの?q=XXXXから文字列を取得 if q := ctx.Query("q"); q != "" { // 大文字小文字の区別をなくすため、全て小文字扱いにする lower := strings.ToLower(q) for _, product := range products { // 検索条件1: Title if strings.Contains(strings.ToLower(product.Title), lower) || // 検索条件2: Description strings.Contains(strings.ToLower(product.Description), lower) { searchProducts = append(searchProducts, product) } } } else { // 検索しない場合は、全てのデータを返却 searchProducts = products } return ctx.JSON(searchProducts) } |
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 35 36 37 38 39 40 41 42 43 44 45 46 |
// 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 admin := api.Group("admin") admin.Post("register", controllers.Register) admin.Post("login", controllers.Login) 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) // Ambassador ambassador := api.Group("ambassador") ambassador.Post("register", controllers.Register) ambassador.Post("login", controllers.Login) ambassador.Get("products/frontend", controllers.ProductFrontend) ambassador.Get("products/backend", controllers.ProductBackend) ambassadorAuthentication := ambassador.Use(middleware.IsAuthenticate) ambassadorAuthentication.Get("user", controllers.User) ambassadorAuthentication.Post("logout", controllers.Logout) ambassadorAuthentication.Put("users/info", controllers.UpdateInfo) ambassadorAuthentication.Put("users/password", controllers.UpdatePassword) } |
コメントを残す
コメントを投稿するにはログインしてください。