Flow: A tiny but powerful HTTP router for Go

 

Last year I wrote a new HTTP router for Go called Flow. I've been using it in production on this site and in a couple of other projects since, and I'm pretty happy with how it's working out so decided to share it a bit more widely.

My aim with Flow was to bring together my favourite features from other popular routers that I frequently used. It has:

  • A very small and readable codebase (approx. 160 LOC) with pattern-matching logic similar to matryer/way.
  • Middleware management like chi — including the ability to create route 'groups' which use different middleware.
  • Optional regexp support for tighter pattern matching, similar to chi and gorilla/mux.
  • Automatic handling of OPTIONS requests, like julienschmidt/httprouter.
  • Automatic handling of HEAD requests, like bmizerany/pat.
  • An Allow header is automatically set on all OPTIONS and 405 Method Not Allowed responses, like julienschmidt/httprouter.
  • Ability to map multiple HTTP methods to the same handler in one declaration, like gorilla/mux.

Additionally:

  • It has a very small API (see the Go docs) so there's not much to learn.
  • It's designed to work nicely with http.Handler, http.HandlerFunc, and the standard Go middleware pattern.
  • The handlers for 404 Not Found and 405 Method Not Allowed responses are customizable.
  • Conflicting routes are permitted (e.g. /posts/:id and posts/new), with routes matched in the order that they are declared.
  • It has zero dependencies.

Below is a quick example of the syntax, and if you like the look of it you can check out the full README on GitHub.

mux := flow.New()

// The Use() method can be used to register middleware. Middleware declared at
// the top level will used on all routes (including error handlers and OPTIONS
// responses).
mux.Use(exampleMiddleware1)

// Routes can use multiple HTTP methods.
mux.HandleFunc("/profile/:name", exampleHandlerFunc1, "GET", "POST")

// Optionally, regular expressions can be used to enforce a specific pattern
// for a named parameter.
mux.HandleFunc("/profile/:name/:age|^[0-9]{1,3}$", exampleHandlerFunc2, "GET")

// The wildcard ... can be used to match the remainder of a request path.
// Notice that HTTP methods are also optional (if not provided, all HTTP
// methods will match the route).
mux.Handle("/static/...", exampleHandler)

// You can create route 'groups'.
mux.Group(func(mux *flow.Mux) {
    // Middleware declared within in the group will only be used on the routes
    // in the group.
    mux.Use(exampleMiddleware2)

    mux.HandleFunc("/admin", exampleHandlerFunc3, "GET")

    // Groups can be nested.
    mux.Group(func(mux *flow.Mux) {
        mux.Use(exampleMiddleware3)

        mux.HandleFunc("/admin/passwords", exampleHandlerFunc4, "GET")
    })
})

A note on performance

I haven't done any benchmarking against other routers, so I can't speak about the relative performance of Flow. What I can say it has been plenty fast enough for all of my use-cases so far and not a hot spot when profiling my applications under load.

If you enjoyed this post...

You might like to check out my other Go tutorials on this site, or if you're after something more structured, my books Let's Go and Let's Go Further cover how to build complete, production-ready web apps and APIS with Go.

Not sure how to structure your Go web application?

My new book guides you through the start-to-finish build of a real world web application in Go — covering topics like how to structure your code, manage dependencies, create dynamic database-driven pages, and how to authenticate and authorize users securely.

Take a look!