Express ES5 to Koa ES7

Express ES5 to Koa ES7
At Bandwidth we recently created a new node server for serving up our UI. Being the coding hipsters that we are, of course we looked into the latest tech stack. In this post, I am going to specifically talk about Koa, the web application framework, and how it compares to Express, the current dominator in the space.
Koa was designed by the same team that originally designed Express. Koa aims to be much lighter weight than Express by keeping the Koa core small and pushing more functionality to modules. This ideology might sound familiar—it’s how Node as a whole has been orchestrated.
One of the great advantages of Koa is that it uses generator syntax, and their 2.0 version (which I will be discussing in this post) uses async/await. Koa and Express have a lot of similarities but there are also some key differences and some gotchas which you’ll have to watch out for. I’ve created some basic github samples that you can follow along with as we walk through the differences; see if you can figure out what the results will be without reading the answer. The apps can be found at: ES5 Express Sample, ES7 Koa Sample.
Routing
Routing is a core component in Express, while it has been moved to a module in Koa. The koa-router
module has been made to mimic the Express syntax, so you’ll notice that it’s very similar. Most Koa modules have a @next
version which are the ones that use the new async/await syntax. For any modules that don’t, you can wrap them in koa-convert
for compatibility.
In Express all you needed was an Express application object. You could use the http methods directly on that:
var express = require('express');
var app = express();
app.get('/', function (req, res) {
console.log('success!');
res.send({
success: true
});
});
In Koa, you must first include the router middleware and then create a new instance of a router object. The router object has access to the HTTP methods. Once you’ve created your route you call the routes()
function, which will return a middleware you can add to your Koa app
.
import Koa from 'koa';
import Router from 'koa-router';
const app = new Koa();
const sampleRouter = new Router();
sampleRouter.get('/', async ctx => {
console.log('success!');
ctx.body = {
success: true
};
});
app.use(sampleRouter.routes());
As you can see, the Koa routing is very similar to how it worked in Express, except that now the routing has been turned into middleware. To dive into some more of the options checkout koa-router on github .
Control Flow
You might have noticed in the previous example that the control flow was slightly different between Express and Koa. Both of the GET
examples above return the same value to the user but the code flow is slightly different. In Express a response isn’t returned to the client until a piece of middleware explicitly tells it so (via something like res.send
).
Let’s go through some flows using the following code below:
var express = require('express');
var app = express();
var PORT = 8432;
app.use(function(req, res, next) {
console.log('before next');
next();
console.log('after next');
});
app.get('/', function (req, res) {
console.log('success!');
res.send({
success: true
});
});
app.listen(PORT, function () {
console.log('Server started on port: ' + PORT);
});
Let’s start with the simple stuff: given the code above, what do you think the order of the log statements will look like when the server starts up and a user makes a get request to the url: http://localhost:8432/?
If you thought something like this…
before next
success!
after next
Then you are almost correct! Just missing the Server started on port: 8432
log which should happen first. The app.listen
happens right when the server starts up. The flow goes through our first piece of middleware, logs out “before next” then when next it called it moves onto the next piece of middleware, which in this case is the route handler. The route handler logs out “success!” and then calls res.send
which triggers Express to start executing back up the stack. Express then goes back to our previous piece of middleware and executes everything AFTER our next()
call, which in this case will log out “after next”.
An example of where you might want to take advantage of this flow is timing how long it takes to process an incoming request. You could add the following piece of middleware before any other to get the total time its taking to process a user’s request on the server.
app.use(function(req, res, next) {
var startTime = Date.now();
next();
var timeToProcess = Date.now() - startTime;
var fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
console.log(req.method + ' ' + fullUrl + ' - ' + timeToProcess + ' ms');
});
That covers the basics of the control flow in Express. Now let’s take a look at Koa’s control flow and how that compares. Here is a sample piece of code that does something identical to what we did with Express.
import Koa from 'koa';
import Router from 'koa-router';
const app = new Koa();
const sampleRouter = new Router();
const PORT = 8431;
app.use(async (ctx, next) => {
console.log('before next');
await next();
console.log('after next');
});
sampleRouter.get('/', async ctx => {
console.log('success!');
ctx.body = {
success: true
};
});
app.use(sampleRouter.routes());
app.listen(PORT, () => {
console.log('Server started on port: ' + PORT);
});
This code’s output will be identical to Express output from earlier. One of the key differences is how you continue down the stack. The next()
function now returns a promise that you are able to await
. I think this more cleanly shows that you want to execute the next piece of middleware, wait until that is done, then come back and execute the rest of this function.
Let’s take a closer look at this piece of middleware
app.use(async (ctx, next) => {
console.log('before next');
await next();
console.log('after next');
});
What do you think would happen if we removed await next()
?
We get a 404 Not Found
. Essentially what is happening is this piece of middleware gets executed and since next()
is never called, it ends at this piece of middleware and goes back up the stack. Since neither a ctx.status
nor a ctx.body
is ever set the default of ctx.status = 404
is returned instead.
Let’s compare that to how Express works so we can really see the difference.
app.use(function(req, res, next) {
console.log('before next');
next();
console.log('after next');
});
Remember this piece of middleware from earlier? What do you think will happen there if we remove the next()
line?
The correct answer is: nothing! Since we never sent a response via res.send()
the process just hangs until eventually the client times out the request. With Express you must explicitly attach middleware to handle the 404
case whereas Koa comes with that by default.
You’ll notice that previously we only set the ctx.body
, we never set the ctx.status
. By default the status will be set to 200
, assuming no errors. Let’s play around with this some more and see what happens when we ONLY set a status but don’t set a body.
sampleRouter.get('/created', async ctx => {
console.log('statusOnly');
ctx.status = 201;
});
sampleRouter.get('/noContent', async ctx => {
console.log('statusOnly');
ctx.status = 204;
});
sampleRouter.get('/tooManyRequests', async ctx => {
console.log('statusOnly');
ctx.status = 429;
});
Here are a few different status-only examples, so we can really see what is going on. If you hit those endpoints you’ll notice that if the ctx.body
isn’t set, then Koa will default it to the normal http status message. For example the /created
endpoint will set the body to Created
. The only notable exception to this is a status of 204
which will appropriately leave the body empty, since 204
means No Content
.
Those were most of the interesting control flow differences that we experienced when switching from Express to Koa. Feel free to experiment on your own and discover the rest of the subtle differences! If you are having trouble figuring out Koa, check out the source code, there isn’t much of it.
Final Thoughts
We have been working with Koa for about 6 months now and have been appreciating the differences. It definitely takes some getting used to when you first make the switch, but after working with it a while we have adapted and believe that it is a bit more intuitive. We also approve of how Koa is trying to keep the core library small. In general, we take that approach to most things, do one thing and do it well. It allows the library to really focus instead of being spread too thin. Being able to code using async/await syntax has been awesome. It is the most intuitive way to do synchronous coding in an asynchronous environment. Koa’s middleware ecosystem isn’t as big as Express’s but it is still sizable and you are able to build a production ready server with what is out there. You can certainly use ES7 features with Express but since Koa 2.0 includes async/await by default it requires using Babel which means we get all of the other juicy features that come along with it. Our code organization has greatly improved since switching to ES7 syntax. If you are considering spinning up a new node server consider looking into Koa, I think once you make the switch you won’t want to go back.
TLDR;
- Async/await syntax is great
- Koa has a more intuitive control flow
- ES7 features have greatly improved code quality
- Koa core is smaller than Express, 3rd party middleware fills in the gaps
- Koa 2.0 is still in the early phases but the conversion library will bridge the gap
- Routing is not part of Koa core