A Koa application is an object containing a set of middleware functions that are organized and executed in a stack-like fashion.

This is koA’s introduction to itself, and all other libraries koA relies on can be considered middleware, including the KOA-Router.

Ps: The Chinese explanation in this code is the explanation of the code, ellipsis (…). The project address that represents the truncated router at the end of the article

Conjecture for koA-Router

Koa’s simplest Hellow World example shows how native requests are handled:

const Koa = require('koa');
const app = new Koa();

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);
Copy the code

If we want to implement routing simply, we can add some criteria

app.use(async ctx => {
  if (ctx.path === '/one' && ctx.method === 'get') {
    ctx.body = 'Hello World';
  } else {
    ctx.status = 404;
    ctx.body = ' '; }});Copy the code

This allows for simple routing implementations, but the more routes there are, the more performance costs, and it is not easy to add middleware for particular routes. A better approach is to use an object-oriented approach that returns the appropriate middleware handler and execution functions based on the path and method of the request.

Interpret ideas

Here is how I interpret the source code of koA-Router. I will download the source code of KOA-Router locally and read it through (because the source code is relatively few) to get a general idea of the koA-Router execution process, and then debug the analysis through the unit test.

Router Execution Flowchart

I think there are four basic and core APIS for koA-Router:

  1. Router. match Can filter out matching routes based on the path and method of the request
  2. The router. The register register the route
  3. Route. routes returns the middleware used for koA loading, which compresses the middlewares into a function via koa-compose
  4. Router. method(get, POST, etc.) You can define a router based on path, method, and you can bind middleware to a route

Interpretation of the

We can combine code with unit tests to understand the source code, starting with the simplest tests:

it('router can be accecced with ctx'.function (done) {
      var app = new Koa();
      var router = new Router();
      router.get('home'.'/'.function (ctx) {
          ctx.body = {
            url: ctx.router.url('home')}; }); console.log(router.routes()); // Routes app.use(router.routes()); request(http.createServer(app.callback())) .get('/')
          .expect(200)
          .end(function (err, res) {
              if (err) return done(err);
              expect(res.body.url).to.eql("/");
              done(a); }); });Copy the code

The router routes () returns:

function dispatch(ctx, next) {
    debug('%s %s', ctx.method, ctx.path); var path = router.opts.routerPath || ctx.routerPath || ctx.path; var matched = router.match(path, ctx.method); var layerChain, layer, i; . ctx.router = router;if(! matched.route)returnnext(); Var matchedLayers = matched. PathAndMethod... LayerChain = matchedlayers.reduce (layerChain = matchedlayers.reduce (function(memo, layer) {
      ...
      returnmemo.concat(layer.stack); } []);return compose(layerChain)(ctx, next);
  }
Copy the code

Router.routes () returns a dispatch function, from which you can see that the request is routed through router.match, and the matching route execution function is pushed into the array. And the compose(koa-compose) function is merged back.

Body = {url: ctx.router.url(‘home’)}; The compose wrapper function of the

app.use(ctx => {
  ctx.body = {
    url: ctx.router.url('home')}; });Copy the code
  • Router constructor
function Router(opts) {
  if(! (this instanceof Router)) {returnnew Router(opts); } this.opts = opts || {}; / / define each method enclosing the methods = this. Opts. The methods | | ['HEAD'.'OPTIONS'.'GET'.'PUT'.'PATCH'.'POST'.'DELETE']; this.params = {}; // Initialize the route stack this.stack = []; };Copy the code
  • Analyze the router.method method
// methods ['get'.'post'.'delete'.'put'.'patch'. ]  methods.forEach(function (method) {
  Router.prototype[method] = function (name, path, middleware) {
    var middleware;

    if (typeof path === 'string'| | path instanceof RegExp) {/ / if the second parameter is a string or a regular expression, Will the back of the parameters as middleware. Middleware = Array prototype. Slice. The call (the arguments, 2); }else{/ / or pass without the name parameter, would be the first parameter setting for the path, after the parameters are classified as middleware. Middleware = Array prototype. Slice. The call (the arguments, 1); path = name; name = null; This. register(path, [method], middleware, {name: name}); // Return the Router object, which can be called chainedreturn this;
  };
});
Copy the code
  • Analyze the router.register method
Router.prototype.register = function(path, methods, middleware, opts) { opts = opts || {}; var stack = this.stack; . // create route // instantiate a Layer object that converts path to regexp, Var route = New Layer(Path, methods, Middleware, {end: opts.end ===false ? opts.end : true,
    name: opts.name,
    sensitive: opts.sensitive || this.opts.sensitive || false,
    strict: opts.strict || this.opts.strict || false,
    prefix: opts.prefix || this.opts.prefix || "", ignoreCaptures: opts.ignoreCaptures }); console.log(route); /** * Layer { * ... * methods: ['HEAD'.'GET' ],
   * stack: [ [Function] ],
   * path: '/', * regexp: { /^(? : \ /? ) = $)? $/ I keys: []}} // Place the registered route in the stack queue stack.push(route);return route;
};
Copy the code

The register method is mainly used to instantiate the Layer object, and supports the simultaneous registration of multiple paths and adding route prefixes (the display code is ignored).

  • Analysis of the router. The match
Router.prototype.match = function(path, method) {// Get registered routes (instantiate Layer object) var layers = this.stack; var layer; var matched = { path: [], pathAndMethod: [], route:false}; // Loop to find a matching routefor (var len = layers.length, i = 0; i < len; i++) {
    layer = layers[i];

    debug('test %s %s', layer.path, layer.regexp); // Match according to layer.regexp.test(path)if(layer.match(path)) { matched.path.push(layer); // the todo ~ operator is not yet availableif(layer.methods.length === 0 || ~layer.methods.indexOf(method)) { matched.pathAndMethod.push(layer); // Set the matching flag route totrueI think hitRoute is easier to understandif (layer.methods.length) matched.route = true; }}}return matched;
};
Copy the code

Implement a simplified Router

From the above analysis, we have covered the core of koA-Router: Construct router object => define router entry => match route => merge middleware and execute function output; These four apis can handle simple restful requests, and additional apis such as redirection, router.use, route prefixes, and so on are much easier to read once you know the core code; The simple version is a simplified version of the above API. For the same principle, you can go to my project to see the simple-koa-router: github.com/masongzhi/s…

conclusion

Koa-router helped us define and select the appropriate route, add middleware to the route, and do some compatibility and validation work; On the basis of the application of KOA middleware, it is easier to understand the implementation of middleware, KOA-Router for us to do a better routing layer management, in the design can refer to the implementation, at the same time the study of beautiful source code is also a kind of promotion of their own.