Home > Enterprise >  How to add different endpoints with the same path prefix and param in Gin?
How to add different endpoints with the same path prefix and param in Gin?

Time:12-24


I have built an API in Go using the Gin framework. I have one route in API where I can get users with id. But now I also want to get users by their username. So I tried the same way which I did for getting users with id. But it is giving me an error during compile. Can you guys please tell me how can I do that?
Thank you.
Routes -

grp.GET("/users", controllers.GetUsers)
grp.GET("/users/:id", controllers.GetUser)
grp.GET("/users/:username", controllers.GetUserByUsername)   //error - panic: ':username' in new path '/users/:username' conflicts with existing wildcard ':id' in existing prefix '/users/:id'
grp.POST("/users", controllers.CreateUser)
grp.PATCH("/users/:id", controllers.UpdateUser)
grp.DELETE("/users/:id", controllers.DeleteUser)

Controller -

func GetUser(c *gin.Context) {
    paramID := c.Params.ByName("id")

    ctx := context.Background()
    sa := option.WithCredentialsFile(firestoreFilePath)
    app, err := firebase.NewApp(ctx, nil, sa)
    if err != nil {
        log.Fatalln(err)
    }

    client, err := app.Firestore(ctx)
    if err != nil {
        log.Fatalln(err)
    }
    defer client.Close()

    dsnap, err := client.Collection("users").Doc(paramID).Get(ctx)
    if err != nil {
        fmt.Print(err)
        c.IndentedJSON(http.StatusNotFound, gin.H{
            "message": "User not found",
        })
        return
    }
    m := dsnap.Data()

    c.IndentedJSON(http.StatusNotFound, gin.H{
        "User":    m,
        "message": "User returned successfully",
    })

}

API response -

[
  {
     "id": 1,
     "name": "Leanne Graham",
     "username": "Bret",
     "email": "[email protected]",
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "username": "Antonette",
    "email": "[email protected]",
  },
  {
    "id": 3,
    "name": "Clementine Bauch",
    "username": "Samantha",
    "email": "[email protected]",
  }
]

CodePudding user response:

You can't declare routes with the same method, the same path prefix and a path parameter in the same position. The route suffix, unfortunately, isn't inspected during route matching.

You have to give it a different name. Since the route presumably is mapped to one "user" resource, you can call it like this, and keep it RESTful:

// instead of /users
grp.GET("/user/:username", controllers.GetUserByUsername)

However the user id is likely unique too, so it would make equal sense to call the other route /user/:id. So you can keep /users as prefix and provide a different fixed path segment between the prefix and the param:

// prefix is /users/id
grp.GET("/users/id/:id", controllers.GetUser)

// prefix is /users/username
grp.GET("/users/username/:username", controllers.GetUserByUsername)

Or you could do that with one route and query, and inspect the query parameters in an intermediate handler. This has the advantage of supporting an arbitrary number of search parameters with one route:

// invoked as /users/find?p=xxx&t=yyy
// p stands for 'parameter'
// t stands for 'type'
grp.GET("/users/find", func(c *gin.Context) {
    switch c.Query("t") {
        case "id":
            // inspect p in this handler knowing it's an id
            controllers.GetUser(c) 
        case "username":
            // inspect p in this handler knowing it's a username
            controllers.GetUserByUsername(c) 
        default:
            c.AbortWithStatus(http.StatusBadRequest)
    }
})

CodePudding user response:

This is a known limitation in gin. You'll have to make all paths unique. Like adding a prefix as below:

grp.GET("/users/username/:username", controllers.GetUserByUsername)

There's more info on this issue thread: https://github.com/gin-gonic/gin/issues/1301

  • Related