Flow: A delightfully 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
andgorilla/mux
. - Automatic handling of
OPTIONS
requests, likejulienschmidt/httprouter
. - Automatic handling of
HEAD
requests, likebmizerany/pat
. - An
Allow
header is automatically set on allOPTIONS
and405 Method Not Allowed
responses, likejulienschmidt/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
and405 Method Not Allowed
responses are customizable. - Conflicting routes are permitted (e.g.
/posts/:id
andposts/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.