The elegance of asynchronous middleware chaining in Koa.js
When we talk about web frameworks in Node.js, Express usually comes to mind.
Express is a routing and middleware web framework that has minimal functionality of its own
Every Express application comes with a built-in app router. You can toss your endpoints and write functions to handle the request/responses. You can also apply middlewares to all requests or to some methods/paths.
Middleware functions are functions that have access to the request object (
req
), the response object (res
), and the next middleware function in the application’s request-response cycle.
Middlewares can intercept the request-response cycle and modify the response. One key point is that middlewares can be chained. In good design fashion, a middleware must have one responsibility and all complex behaviour must be achieved by chaining different middlewares.
Below is a very basic express application with one endpoint (GET /path
). It has a logging middleware applied to all requests and one logging middleware applied to the endpoint’s requests. The response is just an HTTP OK
status code.
Simple, yet powerful.
Not so great with async code
The thing that I don’t like much about Express (and Node.js in general) is its love for callbacks. I fight against them as much as I can.
To illustrate my point, let’s build a proxy for the Star Wars API. We use request to fetch some data, then filter some fields, and return a response or an error.
Not bad. Not sexy either. Let’s try with promises using node-fetch:
I like it better, but the separation between fetching the external data, filtering and sending the response is blurry. Refactoring the code using functions makes this lack of separation of concerns more evident:
The then
callbacks are a mixed bag: some related to the data and some related to the response handling (notice how the response object is injected).
The problem worsens when dealing with asynchronous middlewares, specifically, if we are trying to pull off some kind of middleware based error handling. That kind of code will vastly benefit from the new async/await syntax.
To be fair, it’s possible to use async functions with Express. However, you will need some third-party library or wrapper. It would be nice to have native support.
Enter Koa.js: everything is an async middleware
A Koa application is an object containing an array of middleware functions which are composed and executed in a stack-like manner upon request.
Koa middleware cascade in a more traditional way as you may be used to with similar tools — this was previously difficult to make user friendly with node’s use of callbacks. However with async functions we can achieve “true” middleware.
In Koa.js, everything is an async middleware. No more, no less. It’s built from the ground up to handle asynchronous code, embracing the async nature of I/O in Node.js.
Every piece in a Koa application is the same: async functions that receive the request context and the next middleware in the chain.
The context API has a very small surface and you just need to grasp a few ideas to build stuff: you can access the request info, set stuff on the response and store data to be shared between middlewares.
If you want to execute the next middleware you await
for next()
. If you want to stop the chain, you don’t await
.
Work can be done before, after or around the next middleware.
Revisiting the examples
Let’s revisit our examples using Koa. The logging middlewares example is pretty similar to the one using Express:
Let’s rewrite the proxy:
I find the Koa versions with async
/await
far more readable, even with no function extractions. It’s more natural to use try
/catch
blocks to deal with errors, and there is no trace of callbacks.
We can further embrace the Koa way and think that our requests should be processed by a chain of reusable middlewares. Let’s rewrite the example once again:
It’s more code, but, at first sight, you can tell what a GET /charaters/1
will do: with errors being handled, it will fetch a character, filter some fields, and send the character as a response.
You can also reuse every middleware of the chain, and by using some higher order functions you can build some generic pieces. Let’s refactor the code:
Now we can easily build multiple proxy endpoints like this one.
Even the router is an async middleware
In all the previous examples we have used a third party router: koa-router. If you take the time and read its code you’ll find that routes()
returns an async middleware that works by matching the request method/path and composing the middlewares defined for every route. Everything in Koa is a middleware.
Functional composition
The last piece of goodness I want to show you is that middlewares are just functions, so they can be composed using koa-compose to create a new function that executes in a fell swoop all the middlewares. So good!
Summing up
Since last year we have been working with Node.js microservices. We started by looking into the different Node.js web frameworks. We liked Express. We liked Koa.js even more.
Koa.js architecture is simple and robust. It embraces async
functions and has a very small and manageable API. There are many Koa.js middlewares readily available out there. It’s easy to write and share them, and there is much to be learned by reading their code.
I fell in love with this kind of middleware-based architecture when Rack was first introduced in Rails, and I was extremely happy when my coworker Roman Coedo pointed me towards Koa.js.