[ReadingKoa] Day One — How Koa middleware works

Alick Wong
ITNEXT
Published in
3 min readJun 7, 2019

--

The middleware in Koa is different from Express, Koa use the onion model. This amazing framework Koa only contain four files, today we will only look at the main file — application.js. It already contains the core logic of how middleware works.

Preparation

git clone git@github.com:koajs/koa.git
npm install

Than we add a index.js at the root of the project for testing purpose

You may run this to start the server:

node index.js

Visit http://localhost:3000, you will see 1, 2, 3, 4, 5, 6 output. This is called Onion Model (middleware)

How Onion Model Works

Let read the core of koa to see how middleware works. In the index.js , we use middleware like this:

const app = new Koa();
app.use(// middleware);
app.use(// middleware);
app.listen(3000);

Take a look in application.js, here is those codings related to middleware, I have added some comments in the code.

About Compose Function

More information about compose function, we can take a look at the koa-compose package

All middleware is passed into compose function and it return dispatch(0), it execute dispatch function immediately and return a promise. Before we understand the content of dispatch function, we have to understand the syntax of promise.

About Promise

Normally we use promise like this:

const promise = new Promise(function(resolve, reject) {
if (success){
resolve(value);
} else {
reject(error);
}
});

In Koa, it use like this:

let testPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('test success');
}, 1000);
});
Promise.resolve(testPromise).then(function (value) {
console.log(value); // "test success"
});

Therefore, we know that in the compose function, it return a promise.

Back to Koa — compose middleware

dispatch is a recursive function that will loop all middleware. In our index.js , we have 3 middleware, all 3 middleware will execute those coding before await next();

app.use(async (ctx, next) => {
console.log(2);
const start = Date.now();
await next(); // <- stop here and wait for the next middleware complete
console.log(5);
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});

We can take a look at the execution order of those three middleware in index.js:

  • When executing dispatch(0) , Promise.resolve(fn(context, dispatch.bind(null, 0 + 1))) is executed
  • first middleware content will run until await next()
  • next() = dispatch.bind(null, 0 + 1) , which is the second middleware
  • second middleware will run until await next()
  • next() = dispatch.bind(null, 1 + 1), which is the third middleware
  • third middleware will run until await next()
  • next() = dispatch.bind(null, 2 + 1), there is no fourth middleware, it will return immediately by if (!fn) return Promise.resolve() , the await next() in third middleware is resolved, remaining code in third middleware is executed.
  • the await next() in second middleware is resolved, remaining code in second middleware is executed.
  • he await next() in first middleware is resolved, remaining code in first middleware is executed.

Why Onion Model?

Coding will be more simple if we have async/ await in middleware. When we want to write a time logger for the api request, it can be extremely easy by adding this middleware:

app.use(async (ctx, next) => {
const start = Date.now();
await next(); // your API logic
const ms = Date.now() - start;
console.log('API response time:' + ms);
});

Next Post: Read all codes in Koa

Reference

--

--

AWS Solutions Architect. Former co-founder of a game studio. Web Developer. Passionate about web technology. https://www.linkedin.com/in/alick-wong