gin-gonic/gin

Pass var to route handler

Opened this issue ยท 13 comments

Hi,

how can I pass a var (db connection pointer) to a route handler like this:

apiV1.GET("/users/:userid", userHandler)

so that I can use in userHandler ?

Now I use a global var but I want to move the handler functions to another package

nazwa commented

You can create a database middleware and add connection to context.

func Database(connString string) gin.HandlerFunc {
    db := sqlx.MustConnect("mysql", connString)
    if err := RunMigrations(db); err != nil {
        panic(err)
    }

    return func(c *gin.Context) {
        c.Set("DB", db)
        c.Next()
    }
}



r.Use(Database(config.DSN))

Doesn't open multiple connections?
Dont we need to close other connections before we open new?

nazwa commented

No, all good. Note that the outer function wraps the scope with the db variable, and only the return func is repeated for every http connection.

Also golang mysql handles pooling and connection management of database connections, so all good here ๐Ÿ‘

top commented

@nazwa I checked your code, but still don't know how to use Database middleware :(
I use sqlite instead :)

nazwa commented

hey @top! It's super simple, really.

The middleware has two "parts".

Part one is what is executed once, when you initalize your middleware. That's where you set up all the global objects, connections etc. Everything thta happens one per application lifetime.

Part two is what executes on every request. For a database middleware you simply inject your "global" db object into the context. Once it's inside the context, you can retrieve it from within other middlewares and your handler function.

func Database(connString string) gin.HandlerFunc {
  // <----
// This is part one
// ---->

    return func(c *gin.Context) {
   // <----
   // This is part two
  // ---->
        c.Set("DB", db)
        c.Next()
    }
}

And inside my handler:

func (this *UploadsHandler) Upload(c *gin.Context) {
    db := c.MustGet("DB").(*bolt.DB) // MustGet returns an interface so you have to type cast it first :)

Let me know if it's still unclear

How to use context.Context store DB in gin?

dzpt commented

@nazwa what's the purpose of using Next()? Do i need to use it after each Set()? I've read the comment but don't understand

You may have registered many middlewares in chain. Next() will keep calling next middleware and your handler func in last. If you do not call Next, your request won't be processed any further.

@nazwa @rskumar You do not need c.Next() in this case. Because all of middlewares & handlers in the chain are called in a row automatically without c.Next().

Here's the flow to handle requests in gin.

  1. net/http calls ServeHTTP() method in *gin.Engine.
  2. handleHTTPRequest() method will be called, and the first c.Next() is called.
  3. c.Next() executes for loop to make the chain work. This calls handlers/middlewares sequentially, so each one does not need to call c.Next() in itself.

But you need to call c.Next() explicitly when you do something after the children. Here's the example.

func someMW() gin.HandlerFunc {
  return func(c *gin.Context) {
    log.Println("before calling handler")
    c.Next()
    log.Println("after calling handler")
  }
}

func someHandler() gin.HandlerFunc {
  return func(c *gin.Context) {
    log.Println("start handler")
    doSomeFantasticThings()
    log.Println("finish handler")
  }
}

r := gin.New()
r.GET("/", someMW(), someHandler())

This shows log below...

before calling handler
start handler
finish handler
after calling handler

You can try to remove c.Next() and see the order of logs changes, and middlewares/handlers are called sequentially without c.Next().

before calling handler
after calling handler
start handler
finish handler

When you want to cut the chain and return on ahead, you should call c.Abort() explicitly. If you replace the c.Next() with c.Abort(), you will see this.

before calling handler
# it aborts here
after calling handler
# show `after` log, but it does not continue into the handler

What is the difference between passing variables through middleware and custom structure like in #932 (comment).

What is the right way ?

nazwa commented

@alexmatsak I personally use a combination of the two. "global" things (db, logger) etc go through context, more individual items go directly into the "module" struct object.

Context is a bit more flexible because it allows you to do self-registering modules but it's all down to what you really need.

Hi @nazwa, thanks for the example, could you please elaborate a bit more and tell how to make sure that connection is closed gracefully when server is shutdown?

Add a custom Header in middleware then, receive the Header content in handler. Just a theory though!