Path Prefix Middleware in Go

I was recently working on a project where I found myself wanting to set up some custom middleware based on the prefix path of a route. I didn’t see a lot of great resources explaining how to do this, so this is my attempt at explaining it.

Unfortunately I can’t share my real code, but what I can do is pretend that we are building an app and api to help people compress things. To start, let’s look at a few of the routes we expect our app to have.

Now that we have the gist of our routes, let’s look at some of the libraries we will be using.

The libraries

We are going to use:

You can easily apply the technique we will be using to other libraries, but for simplicity’s sake we are going to use specific packages so that we aren’t limited to pseudo-code.

Coding the routes

Lets start by coding our routes up quickly.

func main() {
	r := mux.NewRouter().StrictSlash(false)
	r.HandleFunc("/blog", blog)
	r.HandleFunc("/pricing", pricing)

	dash := r.PathPrefix("/dashboard").Subrouter()
	dash.HandleFunc("/", dashboardIndex)
	dash.HandleFunc("/things", dashboardThings)
	dash.HandleFunc("/things/compress", dashboardCompressAllTheThings)

	api := r.PathPrefix("/api").Subrouter()
	api.HandleFunc("/things", apiThings)
	api.HandleFunc("/things/compress", apiCompressAllTheThings)

	http.ListenAndServe(":3000", r)
}

We don’t have any http.Handler implementations here, but assume that all of the functions passed into the HandleFunc functions are of the type http.HandlerFunc.

The middleware

First, lets get a classic negroni instance created and apply this to all of our routes. This handles things like logging and handling panic recovery.

func main() {
	// ... this is all roughly the same code from our
	// last example

	n := negroni.Classic()
	n.UseHandler(r)
	http.ListenAndServe(":3000", n)
}

The last three lines are the only changed/new ones in the main function. We created an instance of negroni.Negroni using negroni.Classic, then told it to use our mux.Router as a handler, and finally started a server listening on port 3000 with our negroni handler.

Before we can move on we need to write some middleware for our other routes. Remember, we need to verify that users are logged in for each of our different types of endpoints.

func DashboardMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	// Verify that the user is logged in w/ a valid cookie
	validCookie := true
	if validCookie {
		next(w, r)
	} else {
		// redirect to login
		http.Redirect(w, r, "/login", http.StatusFound)
	}
}

func APIMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	// Verify that the user is logged in w/ a valid header
	validHeader := true
	if validHeader {
		next(w, r)
	} else {
		// redirect to login
		http.Redirect(w, r, "/login", http.StatusFound)
	}
}

Okay, so obviously I’m not about to actually write up all that code, but let’s pretend that we did.

Specifying middleware based on route prefixes

This is where the magic happens, and it is also where things get confusing.

The easiesy way I have found to specify middleware for a path prefix is to setup a second muxer (we use the sirMuxalot variable for ours below) that has the path prefixes we want to apply middleware to, and to then pass in our original router wrapped in some middleware for those routes.

This works because the sirMuxalot router won’t ever call the middleware-wrapped router unless the path prefix we define gets matched with the incoming web request’s path.

sirMuxalot := http.NewServeMux()
sirMuxalot.Handle("/", r)
sirMuxalot.Handle("/api/", negroni.New(
	negroni.HandlerFunc(APIMiddleware),
	negroni.Wrap(r),
))
sirMuxalot.Handle("/dashboard/", negroni.New(
	negroni.HandlerFunc(DashboardMiddleware),
	negroni.Wrap(r),
))

n := negroni.Classic()
n.UseHandler(sirMuxalot)
http.ListenAndServe(":3000", n)

It probably seems weird to wrap our router in multiple different middleware, but this works perfectly fine and is one of the more elegant solutions to the problem.

And that’s it. If the code still seems a bit fuzzy I recommend playing around with it a bit. Again, you don’t need to use negroni or any other specific middleware for this to work, so feel free to use custom code or different libraries.

Learn Web Development with Go!

Sign up for my mailing list and I'll send you a FREE sample from my course - Web Development with Go. The sample includes three chapters from the book, and over 2.5 hours of screencasts.

You will also receive notifications when I release new articles, along with other freebies that I only share with my mailing list.

Avatar of Jon Calhoun
Written by
Jon Calhoun

Jon Calhoun is a full stack web developer who also teaches about Go, web development, algorithms, and anything programming related. He also consults for other companies who have development needs. (If you need some development work done, get in touch!)

Jon is a co-founder of EasyPost, a shipping API that many fortune 500 companies use to power their shipping infrastructure, and prior to founding EasyPost he worked at google as a software engineer.

Related articles

Spread the word

Did you find this page helpful? Let others know about it!

Vote on Hacker News

Sharing helps me continue to create both free and premium Go resources.

Want to discuss the article?

See something that is wrong, think this article could be improved, or just want to say thanks? I'd love to hear what you have to say!

You can reach me via email or via twitter.

Recent Articles All Articles Mini-Series Tags About Me Go Courses

©2018 Jonathan Calhoun. All rights reserved.