In the last days I’ve played a little bit with negroni middleware and httprouter router. I found that the documentation about how can we use negroni middlewares for specific routes with httprouter is pretty poor. So I decided to write this article to share with you what I’ve found.

Negroni is an idiomatic approach to web middleware in Golang which will help you build and stack middleware very easily. It comes with some default middlewares like:

  • negroni.Recovery – Panic Recovery Middleware.
  • negroni.Logger – Request/Response Logger Middleware.
  • negroni.Static – Static File serving under the “public” directory.

But it also letting you create your own middlewares very easily:

func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    // do some stuff before
    next(rw, r)
    // do some stuff after
}

…
n := negroni.New()
n.Use(negroni.HandlerFunc(MyMiddleware))

Negroni is BYOR (Bring your own Router) so we can use it with httprouter.

Httprouter is a Golang lightweight and high performance HTTP request router which is fast and has low memory consumption. TLDR: it is one of the fastest routers.

Here you have an example on how a simple route can be added when using httprouter:

router := httprouter.New()
router.POST("/login", loginHandler)

Where loginHandler will look like:

func loginHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
    // login controller logic
}

Now lets suppose you want to access GET profile endpoint but you need to be authenticated.
We will start by creating an authentication middleware:

// auth middleware
func auth(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
    // do some stuff before
    log.Println("auth middleware -> before executing controller")

    // call endpoint handler
    next(rw, r)

    // do some stuff after
    log.Println("auth middleware -> after the controller was executed")
}

Then we will create a handler which will return profile information after the authentication will be done. This handler will be sent to the auth middleware as a callback using “next” parameter:

func profileHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "This is the content of the profile controller\n")
    log.Println("executing profile controller")
}

Now let’s send everything to the router:

nProfile := negroni.New()
// set the middleware
nProfile.Use(negroni.HandlerFunc(auth))
// set the handler
nProfile.UseHandlerFunc(profileHandler)
// attach negroni middleware and handler to our route
router.Handler("GET", "/profile", nProfile)

That was simple, but now if we have a route which contains parameters inside it (like /hello/:name) we cannot access it from our handler function. To solve that, I’ve written the following functions which will help us call the handler with the parameters:

// callwithParams function is helping us to call controller from middleware having access to URL params
func callWithParams(router *httprouter.Router, handler func(w http.ResponseWriter, r *http.Request, ps httprouter.Params)) func(w http.ResponseWriter, r *http.Request) {
    return func(w http.ResponseWriter, r *http.Request) {
        params := getUrlParams(router, r)
        handler(w, r, params)
    }
}

// getUrlParams function is extracting URL parameters
func getUrlParams(router *httprouter.Router, req *http.Request) httprouter.Params {
    _, params, _ := router.Lookup(req.Method, req.URL.Path)

    return params
}

And will be used like this:

router := httprouter.New()
nHello := negroni.New()
// add auth middleware
nHello.Use(negroni.HandlerFunc(auth))
// add handler using callWithParams function so we can access the URL parameters in handler
nHello.UseHandlerFunc(callWithParams(router, helloHandler))
router.Handler("GET", "/hello/:name", nHello)

n.UseHandler(router)

log.Fatal(http.ListenAndServe(":8080", n))

 

The full working example can be found here.