[ReadingKoa] Day One — How Koa middleware works
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 byif (!fn) return Promise.resolve()
, theawait 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);
});