preface
Middleware pattern is one of the development patterns used by various frameworks nowadays, especially Web frameworks. For example, the well-known Express, Koa, and others all use middleware to handle requests. So what are middleware patterns?
What is middleware
Processing Web request, we often need to validate the request source, check the login status, determine whether or not have enough permissions, print log, such as operation, and the repeated operations if written in the specific route processing function, clear can lead to redundant code, and this time, we can put the general process for middleware abstract function, reduce duplication of code.
We can think of a Web request as a series of pipes, with multiple levels in the pipeline. The request data flows from the source through each level, and each level operates independently, either responding directly to the data or tweaking the request to move it to the next level, the middleware.
Middleware for Koa
Let’s take Koa’s middleware as an example.
router
.get('/users', loginChecker, async ctx => {
const users = await db.getUsers()
ctx.body = users
})
.delete('/users/:id', adminChecker, async ctx => {
const { id } = ctx.params
await db.deleteUserById(id)
ctx.body = { error: false}})Copy the code
We use loginChecker to check if the user is logged in, and adminChecker to determine if the user has administrator rights for high-risk delete operations.
As you can see, we only need to consider transaction logic when writing the specific routing functions, greatly reducing the mental burden. We can even wrap an error-handling middleware around the outermost layer to catch all exceptions and handle them uniformly — that’s why I don’t wrap a try/catch layer with await.
Instead of talking about how to develop Koa middleware, let’s talk about how to implement middleware patterns.
The onion model
Koa implements middleware based on the Onion model, which always starts with the outer middleware, moves to the innermost, and back to the outermost when processing a request. Based on this conclusion, we can use an array to store the middleware sequentially and call it sequentially.
async function exec(middlewares, ctx) {
let index = 0
const next = async() = > {const current = middlewares[index++]
if (current) {
await current(ctx, next)
}
}
await next()
}
const ware1 = async (ctx, next) => {
ctx.body = 'This is ware 1'
await next()
console.log("ware1 is back")}// Do not call next, the next middleware will not be entered
const ware2 = async (next) => {
ctx.body = 'Hello, world'
}
Copy the code
CTX means Context, which is a bridge for middleware communication. Each middleware returns data by modifying CTX. For example, Koa changes the response body by modifying ctx.body.
The Onion model is implemented by passing the next method to the current middleware to control the process of middleware execution. It is worth noting that the next method is not called in Ware2, so it becomes the innermost layer in the Onion model, regardless of whether there are other middleware behind it, and then goes back to the outer middleware.
Afterword.
This article introduces what middleware is, along with a simple implementation. If you are a small program developer of wechat, you can refer to minapp-Request, a small program request library developed by me using middleware mode, for a more complete code implementation.
If you like it, please give it a thumbs-up.