updated on

from express to koa

Introduction

There are a few nodeJs web framework. One of the most popular is express.js.

I want to explain here why when writing server code, I choose to move from express.js to koa.

To understand this article you should know about:

TL;DR

With NodeJS version 7 came the support of async/await function.
Koa just plays more naturally with them ➡️ use Koa.

Express

We will write a simple route that will:

  1. query a database to get some stuff
  2. pass the result to a second database call
  3. then send the final result as the response

node style callbacks

We will use here the nodeJs style callback signature:
A callback function with error as the first argument & result as the second.

01-express-node-callback.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
app.get("/", (request, response, next) => {
database.doStuff((error, firstResult) => {
// will send it the error middleware below
if (error) return next(error);
// we need another database call
// a little bit of callback hell here but:
// – we could have extract our callback to functions…
// …living on the first level of our route handler
database.doAnotherStuff(firstResult, (error, finalResult) => {
// will send it the error middleware below. Again.
if (error) return next(error);
// no error, send the result
response.json(finalResult);
});
});
});
// our middleware that handle any errors
// – will catch anything that might have happened in our route
// only if called with next(error)
// – won't catch an JSON.parse() error
app.use(function errorMiddleware(error, request, response, next) {
response.status(500);
response.send(error);
});

So far so good.
But luckily for us our database object support also promises.

promises

The following will do the same as the code above but:

  • we achieved to flatten our code
  • we don’t duplicate anymore the error control
  • the catch will not handle synchronous errors
02-express-promise.jsview raw
1
2
3
4
5
6
7
8
9
10
11
app.get("/", (request, response, next) => {
database
.doStuff()
.then(firstResult => database.doAnotherStuff(firstResult))
.then(finalResult => response.json(finalResult))
.catch(next);
});
app.use(function errorMiddleware(error, request, response, next) {
response.status(500);
response.send(error);
});

So far so good.
But luckily since we use nodeJS >= 7 we can use async/await.

async/await

The following will do the same as the code above but:

  • we achieved to have a less cumbersome code
  • we still don’t duplicate the error control
  • any error inside the try/catch will be handled
03-express-async-await.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// declare an async function to allow “await”
app.get("/", async (request, response, next) => {
try {
const firstResult = await database.doStuff();
const finalResult = await database.doAnotherStuff(firstResult);
response.json(finalResult);
} catch (error) {
next(error);
}
});
app.use(function errorMiddleware(error, request, response, next) {
response.status(500);
response.send(error);
});

So far so good.
But it will get a little messier if we add more routes:

04-express-async-multiple-routes.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
app.get("/:id", async (request, response, next) => {
const { id } = req.params;
try {
const firstResult = await database.doStuff(id);
const finalResult = await database.doAnotherStuff(firstResult);
response.json(finalResult);
} catch (error) {
next(error);
}
});
app.get("/", async (request, response, next) => {
try {
const firstResult = await database.doStuff();
const finalResult = await database.doAnotherStuff(firstResult);
response.json(finalResult);
} catch (error) {
next(error);
}
});
app.use(function errorMiddleware(error, request, response, next) {
response.status(500);
response.send(error);
});

You see?
We write again and again try {} catch(error){ next(error) }
Not a big deal but quite boring at the end…
But luckily we can write a wrapper function for that!

better async/await

So let’s write our wrapper:

05-express-async-wrapper.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// make a closure to keep a reference to our original async function
function asyncWrapper(asyncRouteHandler) {
// this is what will be called by express.js
return function routeHandler(request, response, next) {
// because it's an async function it will always return a promise
// – just call it with express' callback parameters
return (
asyncRouteHandler(request, response, next)
// catch any error that might happen in our async function
.catch(next)
);
};
}
// OR:
// thanks to arrow functions and params destructuring
// we can write it that way:
const asyncWrapper = fn => (...args) => fn(...args).catch(args[2]);

A more detailed article about that was written by Alex Bazhenov

Finally lets use it in our code:

06-express-async-with-wrapper.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const asyncWrapper = fn => (...args) => fn(...args).catch(args[2]);

app.get(
"/:id",
asyncWrapper(async (request, response, next) => {
const { id } = req.params;
const firstResult = await database.doStuff(id);
const finalResult = await database.doAnotherStuff(firstResult);
response.json(finalResult);
})
);

app.get(
"/",
asyncWrapper(async (request, response, next) => {
const firstResult = await database.doStuff();
const finalResult = await database.doAnotherStuff(firstResult);
response.json(finalResult);
})
);

app.use(function errorMiddleware(error, request, response, next) {
response.status(500);
response.send(error);
});

So far so good.
But we still have to write some boilerplate to handle that…
Here comes KOA!

KOA

what is KOA?

to sum up: it’s the same team behind express.js that have written a web framework using the recent additions in the Javascript language.
At its core it’s using promises with async/await
You can find the full introduction here

Setting up a server with Koa is very straightforward.
For the routing, as nothing is provided by default, we will use koa-router

setting up the router and error middleware

This snippet should be enough:

07-koa-boilerplate.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const Koa = require("koa");
const Router = require("koa-router");

const app = new Koa();
const router = new Router();

// unlike in Express.js
// – declare our error middleware to the top most position
// – this will ensure to catch all the errors
// that might happen in the following middleware call
app.use(async function handleError(context, next) {
// call our next middleware
try {
await next();
// catch any error that might have occurred
} catch (error) {
context.status = 500;
context.body = error;
}
});

/*
* We will configure here our router later
*/

// mount the router to our web application
app.use(router.routes());
app.use(router.allowedMethods());

// launch the server
app.listen(3000);

writing our routes

And this is how we will write our application code:

08-koa-with-routes.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
app.use(async function handleError(context, next) {
try {
await next();
} catch (error) {
context.status = 500;
context.body = error;
}
});

router
.get("/:id", async (context, next) => {
const { id } = context.params;
const firstResult = await database.doStuff(id);
const finalResult = await database.doAnotherStuff(firstResult);
context.body(finalResult);
})
.get("/", async (context, next) => {
const firstResult = await database.doStuff();
const finalResult = await database.doAnotherStuff(firstResult);
context.body(finalResult);
});

which appears to me more leaner 😀

  • no duplicated try/catch
  • no need to write an async middleware
  • no need to wrap all our route handlers into that middleware
  • handle both sync/async errors

About Koa ecosystem

As for now, Koa hasn’t as much middleware as express.js.
This can be an issue in migrating.

But the must have middlewares are already here, and writing your own is quite easy.
I never found myself in a situation where I couldn’t achieve what I wanted to do with Koa.

So if you like async/await code style, give Koa a try 🙂