(Add star to front end to improve front end skills)

Author: Full stack growth Road No. / Mountain moon Line (this article is from the author’s submission)

When we are learning more about a framework or library, it is sometimes necessary to explore source code in order to understand its thinking and design, as well as to better use and avoid unintended bugs. For a framework/library like KOA, which is extremely simple and widely used, you should know its source code.

And to see if we know enough about it, we can implement a mini-library with only core functionality. As the saying goes, although the sparrow is small, it has all five organs.

This is just 40 lines of code to implement a small koA with its core functionality.

Source: https://github.com/shfshanyue/koa-mini


Here is a simplified example with almost all of koA’s core features:

const Koa = require('koa')const app = new Koa()app.use(async (ctx, next) => {  console.log('Middleware 1 Start')  await next()  console.log('Middleware 1 End')})app.use(async (ctx, next) => {  console.log('Middleware 2 Start')  await next()  console.log('Middleware 2 End')  ctx.body = 'hello, world'})app.listen(3000)// output// Middleware 1 Start// Middleware 2 Start// Middleware 2 End// Middleware 1 EndCopy the code

In the simplest example, you can see that there are three clear modules, as follows:

  • Application: Basic server framework
  • Context: Encapsulates the basic data structure of the server framework for HTTP request parsing and response
  • Middleware: Middleware and the central mechanism of the Onion model

We started implementing these three modules step by step:

Forget the framework and write a simple server

Let’s start with an HTTP service based on Node’s basic HTTP API and implement the simplest version of KOA:

const http = require('http')const server = http.createServer((req, res) => {  res.end('hello, world')})server.listen(3000)Copy the code

Here’s an example of implementing the simplest version of KOA, which I’ll call KOA-Mini

const Koa = require('koa-mini')const app = new Koa()app.use(async (ctx, next) => {  console.log('Middleware 1 Start')  await next()  console.log('Middleware 1 End')})app.use(async (ctx, next) => {  console.log('Middleware 2 Start')  await next()  console.log('Middleware 2 End')  ctx.body = 'hello, world'})app.listen(3000)Copy the code

Build the Application

Start with the Appliacation framework:

  • app.listen: handles requests and responses, and listens on ports
  • app.use: Middleware function that processes the request and completes the response

A simple dozen or so lines of code, as shown below:

const http = require('http')class Application { constructor () { this.middleware = null } listen (... args) { const server = http.createServer(this.middleware) server.listen(... Use (middleware) {this.middleware = middleware}}Copy the code

The code that calls Application to start the service is as follows:

const app = new Appliacation()app.use((req, res) => {  res.end('hello, world')})app.listen(3000)Copy the code

Since the app.use callback is still the native HTTP. crateServer callback, in KOA the callback argument is a Context object.

The next step will be to build the Context object.

Building the Context

In KOA, app.use’s callback argument is a CTX object, not the native REq/RES. So in this step you build a Context object and use ctx.body to build the response:

  • app.use(ctx => ctx.body = 'hello, world'): by thehttp.createServerFurther encapsulation in the callback functionContextimplementation
  • Context(req, res)To:request/responseThe data structure constructs the Context object for the subject

The core code is as follows, note the comment part:

const http = require('http')class Application { constructor () {} use () {} listen (... Args) {const server = http.createserver ((req, res) => {const CTX = new Context(req, res) Use function this.middleware(CTX); // Ctx.body: ctx.res.end(ctx.body)}) server.listen(... Constructor (req, res) {this.req = req this.res = res}}Copy the code

At this point, KOA is modified as follows and app.use can work normally:

const app = new Application()app.use(ctx => {  ctx.body = 'hello, world'})app.listen(7000)Copy the code

All of the above code is simple, leaving only one of the most important and core features: the Onion model

Onion model and middleware transformation

The above work is only a simple middleware, but in reality there will be many middleware, such as error handling, permission verification, routing, logging, traffic limiting and so on. So we’re going to revamp app. Middlewares

  • app.middlewares: Collects an array of middleware callback functions and uses itcomposetogether

The compose function is abstracted for all middleware functions, which take the Context object as an argument to receive the request and process the response:

Middlewares = compose(this.middlewares)await fn(CTX)// Just take the CTX parameter await compose(this.middlewares, CTX)Copy the code

The complete code is as follows:

const http = require('http')class Application { constructor () { this.middlewares = [] } listen (... Args) {const server = http.createserver (async (req, res) => {const CTX = new Context(req, res) Const fn = compose(this.middlewares) await fn(CTX) ctx.res.end(ctx.body)}) server.listen(... Args)} use (middleware) {// The middleware callback becomes an array this.middlewares.push(middleware)}}Copy the code

Next, focus on the compose function

Complete the compose function wrapper


Koa’s onion model states that each middleware layer is like each layer of an onion, with each layer going in and out twice as it passes through the center of the onion, and the layer that goes in first comes out last.

  • middleware: The first middleware will execute
  • next: Each middleware will execute the next middleware through next

How do we implement the Onion model for all middleware?

Let’s take a look at each of the Middleware apis below

middleware(ctx, next)Copy the code

Next in each middleware means to execute the next middleware. We extract the next function, and next has next in the next function. How do we deal with this?

const next = () => nextMiddleware(ctx, next)middleware(ctx, next(0))Copy the code

Yes, use a recursion to complete the transformation of the middleware and connect the middleware as follows:

Const dispatch = (I) => {return middlewares[I](CTX, () => dispatch(I +1))} Dispatch (0)Copy the code

Dispatch (I) represents the execution of the i-th middleware, and the next() function will execute the next middleware Dispatch (I +1), so we can easily complete the Onion model using recursion

At this point, add the recursive termination condition: when the last middleware function executes next(), it returns directly

const dispatch = (i) => {  const middleware = middlewares[i]  if (i === middlewares.length) {    return  }  return middleware(ctx, () => dispatch(i+1))}return dispatch(0)Copy the code

The final compose function code looks like this:

function compose (middlewares) {  returnctx => {    const dispatch = (i) => {      const middleware = middlewares[i]      if (i === middlewares.length) {        return      }      return middleware(ctx, () => dispatch(i+1))    }    return dispatch(0)  }}Copy the code

Now that the onion model, the core functionality of KOA, is complete, write an example to try it out:

const app = new Application()app.use(async (ctx, next) => {  ctx.body = 'hello, one'  await next()})app.use(async (ctx, next) => {  ctx.body = 'hello, two'  await next()})app.listen(7000)Copy the code

There is one small, but not globally significant, disadvantage: exception handling, and the next step will be to complete the exception catching code

Exception handling

What if your backend service crashes because of a single error?

We only need to do one exception on the middleware execution function:

try {  const fn = compose(this.middlewares)  await fn(ctx)} catch (e) {  console.error(e)  ctx.res.statusCode = 500  ctx.res.write('Internel Server Error')}Copy the code

However, when it is used in daily projects, we must capture it before the exception capture of the framework layer to do some tasks of exception structuring and exception reporting. At this time, an exception processing middleware will be used:

Use (async (CTX, next) => {try {await next(); } catch (err) {// 1. Exception classification // 3. Exception level // 4. Exception reporting}})Copy the code

summary

The core code of KOA is very simple, if you are a Node engineer, it is highly recommended to study the source code of KOA in your spare time, and implement a minimal version of KOA yourself.

My source code implementation of the warehouse for: KOA-mini

Some thinking and practice after reading KOA2 source code

Why say for… Is the of loop the gem of JS?

10,000-word analysis micro front end, micro front end framework Qiankun and source code

Find this article helpful? Please share it with more people

Pay attention to “front-end daqo” plus star label, improve front-end skills

Good article, I’m reading ❤️