While writing complex services in go, one typical topic that you will encounter is middleware
. This topic has been discussed again
, and again
, and again
, on internet. Essentially a middleware should allow us to:
- Intercept a
ServeHTTP call, and execute any arbitrary code.
- Make changes to request/response flow along continuation chain.
- Break the middleware chain, or continue onto next middleware interceptor eventually leading to real request handler.
All of this would sound very similar to what express.js middleware
do. We explored various libraries
and found existing solutions
that closely matched what we wanted, but they either had unnecessary extras
, or were not delightful
for our taste buds. It was pretty obvious that we can write express.js
inspired middleware, with cleaner installation API under 20 lines of code ourselves.
While designing the abstraction, we first imagined how we want to write our middleware functions (referred as interceptors from this point onwards), and the answer was pretty obvious:
They look just like
, but having an extra parameter
that continues the handler chain. This would allow anyone to write interceptor as simple function similar to
that can intercept the call, do what they want, and pass on the control if they want to.
Next we imagined how to hook these interceptors to our
. In order to do so, first thing do is define
which is simply a type of
type MiddlewareHandlerFunc http.HandlerFunc
). This will allow us to build a nicer API on top of stock
. Now given a
we want our chain-able API to look somewhat like this:
, and then calling
method to install our interceptor. The return type of
is again a
, which allows us to call
One important thing to note with
scheme is the order of execution. Since calling
is indirectly invoking last interceptor, the execution of interceptors is reversed i.e. it goes from interceptor at tail all the way back to handler at head. Which makes perfect sense because you are intercepting
the call; so you should get a chance to execute before your parent.
While this reverse chaining system makes the abstraction more fluent, turns out most the time we have a precompiled array of interceptors that will be reused with different handlers. Also when we are defining a chain of middleware as an array, we would naturally prefer to declare them in the order of their execution (not reversed order). Let's call this array interceptors
. We want our middleware chains to look somewhat like:
Note these middleware will be invoked in same order as the appear in the chain i.e.
. This adds both reusability, and readability to our code.
Once we designed the abstraction the implementation was pretty straight forward:
So under 20 lines of code (excluding comments), we were able to build a nice middleware library. It's almost barebones, but the coherent abstraction of these few lines is just amazing. It enables us to write some slick middleware chains, without any fuss. Hopefully these few lines will inspire your middleware experience to be delightful as well.
Like what you see? Come join us