Middleware is a very important concept in Koa, the use of middleware, users can easily handle the request. Since ThinkJS 3.0 was built on the Koa @ 2 release, it is fully compatible with Koa's middleware.
module.exports = options => {
return (ctx, next) => {
// do something
}
}
The middleware is a higher-order function, and the external function receives an options
parameter, which makes it easy for the middleware to provide some configuration information to turn on / off some functions. The function returns ctx
, next
, and ctx
is a shorthand forcontext
, which is an object of the current request's life cycle and stores some information about the current request, next
to call the subsequent middleware, the return value is Promise, so you can easily handle the post-logic.
The middleware execution process is an onion model, similar to the following picture:
If you want to implement a middleware to print the current request execution time, you can use something like the following:
const defaultOptions = {
consoleExecTime: true // to print or not
}
module.exports = (options = {}) => {
// merge options
options = Object.assign({}, defaultOptions, options);
return (ctx, next) => {
if(!options.consoleExecTime) {
return next(); // If print the execution time is not needed, call the subsequent execution logic directly
}
const startTime = Date.now();
let err = null;
// Call next statistics all the time the logic of the follow-up
return next().catch(e => {
err = e; // Here first save the error on a Error object, to facilitate the statistics of the execution time under the error circumstances
}).then(() => {
const endTime = Date.now();
console.log(`request exec time: ${endTime - startTime}ms`);
if(err) return Promise.reject(err); // If subsequent execution logic has an error, return it
})
}
}
In Koa, you can use middleware by calling app.use
, such as:
const app = new Koa();
const execTime = require('koa-execTime'); // statistical execution time module
app.use(execTime({})); // this middle should be use first, and then introduce the rest middle
The use of middleware through app.use
is not conducive to the unified maintenance of middleware.
The default middleware pass options
parameter, and some middleware need to read the app related information, the framework extend it to auto-pass app object to the middleware.
module.exports = (options, app) => {
// here the app is think.app object
return (ctx, next) => {
}
}
The you need to use properties or methods in think object, you can get through app.think.xxx
.
In order to facilitate the management and use of middleware, the framework uses a unified configuration file to manage the middleware, the configuration file is src/config/middleware.js
(multi-module project configuration file is sr/common/config/middleware.js
) .
const path = require('path')
const isDev = think.env === 'development'
module.exports = [
{
handle: 'meta', // middleware handler
options: { // configurations of the middleware
logRequest: isDev,
sendResponseTime: isDev,
},
},
{
handle: 'resource',
enable: isDev, // whether to enable the current middleware
options: {
root: path.join(think.ROOT_PATH, 'www'),
publicPath: /^\/(static|favicon\.ico)/,
},
}
]
Configuration items are middlewares list used in the project, each support handle
,enable
,options
,match
and other attributes.
Middleware processing function, it can be system built-in, or introduced outside or middleware in the project.
handle function signature:
module.exports = (options, app) => {
return (ctx, next) => {
}
}
The parameters received by the middleware in addition to options, but also a extra app
object, which is Koa Application instance.
Whether to enable current middleware, such as: a middleware only in the development environment to take effect.
{
handle: 'resouce',
enable: think.env === 'development' //only in development environment to take effect
}
Configuration item passed to middleware, formatted as an object, get this configuration form middlewrae.
module.exports = [
{
options: {
key: value
}
}
]
Sometimes the configuration items need to be obtained from remote, such as: the configuration item is stored in database which need to be obtained asynchronously, this time define options as a function.
module.exports = [
{
// to define options as async function and return fetched configuration.
options: async () => {
const config = await getConfigFromDb();
return {
key: config.key,
value: config.value
}
}
}
]
To enable this middleware when specific rules are matched, support two ways, one is path matching, one is a custom function matching. Such as:
module.exports = [
{
handle: 'xxx-middleware',
match: '/resource' // The middleware is enabled when URL is /resource
}
]
module.exports = [
{
handle: 'xxx-middleware',
match: ctx => { // match is a function passed ctx, is the result is true, enable the middleware
return true;
}
}
]
Framework comes with a few middlewares that can be configured by string name.
module.exports = [
{
handle: 'meta', // built-in middleware can be confirued by string name.
options: {}
}
]
Sometimes we need to add middleware according to specific project requirement, it can put them into src/middleware
directory, by which you can reference them through file name.
Such as: Add src/middleware/csrf.js
, then you can reference this middleware directly through csrf
string.
module.exports = [
{
handle: 'csrf',
options: {}
}
]
The introduction of external middleware is every simple, just need to require it.
const csrf = require('csrf');
module.exports = [
...,
{
handle: csrf,
options: {}
},
...
]
Middleware execution is performed in the order of configuration, so developers need to consider the order of configuration.
You can view the information using DEBUG=koa:application node development.js
to start the application, in console message as bellow will be printed: koa:application use ...
.
Note: if multiple workers are started then middleware info will be printed multiple times.
Sometimes we want to configure some data in middleware and access it in the subsequent Logic, Controller middleware, this can be done use ctx.state
, for detail refers to passing data.
In the middleware, we can get the parameters of the query or the data submitted by the form through ctx.param
, ctx.post
, etc. However, in some middleware, we hope to set some parameter values and form values to be used in subsequent Logic, Controller in this time can be set by ctx.param
,ctx.post
:
// set param name=value,so that to get value through this.get('name') in Logic, Controller
// it will override the value if it already exists.
ctx.param('name', 'value');
// set post value,later in Logic or Controller to get value through this.post('name2')
ctx.post('name2', 'value');
Inappropriate
, the middleware provides options
parameter to set configuration, no need to configure additional parameters in config.js.
module.exports = [
{
handle: xxxMiddleware,
options: { // middleware configuration
key1: value1,
key2: think.env === 'development' ? value2 : value3
}
}
]
If some configuration are env
related, then you can make judgments here.