I don’t read books, never went to school, I just read other people’s code and always wonder how things work. — TJ Holowaychuk

Introduction to the

The main purpose of this article is to give us an in-depth understanding of Express by studying the core source code of Express. We can not only use Express, but also understand the ideas behind it and improve development efficiency. Study express source code, learn the code structure of God. This article covers only the core code and core flow, not the type determination and use of Express.

express

The core files in Express are Index, Express, Application, Router/Index, Router/Layer, router/ Route. There’s only one sentence in index

module.exports = require('./lib/express');
Copy the code

Import Express, and export it. Express files export many apis, such as Express, express.router, etc. The express() we are developing is actually createApplication(). Application is the API associated with the app. Router /index is the code associated with the router, which sends requests to the route. We don’t call the router/layer method directly. Layer is an abstract concept. In Express, the middleware and routes are stored in app._router. Route also has a stack, and the element inside it is also a layer.

🌰

  • Start with the express source code:
  • Make a simple server with Express, go to http://localhost:3000 and return “Hello World”
const express = require('express');
const app = express();
app.get('/', (req, res,next)=>{
    res.send('Hello World');
    next()
});
app.listen(3000, () = > {console.log('server is ok');
});
Copy the code
  • Express (), which actually calls createApplication() and returns an app function.
function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };
  // Give the proto method to the app initializer.
  return app;
}
Copy the code
  • This app has all the methods on proto, the exported object of “application.js”. Proto names the app in “application.js”, which will be called app for convenience.
  • There is a lazyRouter () method on the app, which determines whether app._router exists or not. If not, assign new Router to app._router.
  • The core code in app.get is as follows:
  app.get = function(path){
    this.lazyrouter();
    var route = this._router.route(path);
    route[method].apply(route, slice.call(arguments.1));
    return this;
  };
Copy the code
  • In route[method], loop arguments, create a layer at a time, handle is a callback to app.get, and place the layer on the route stack. The core code for route[method] is:
 var layer = Layer('/', {}, handle);
 layer.method = method;
 this.methods[method] = true;
 this.stack.push(layer);
Copy the code
  • This. _router is an instance of Router. This._router.route (path) This._router.route(path)
proto.route = function route(path) {
  var route = new Route(path);
  var layer = new Layer(path, {}, route.dispatch.bind(route));
  layer.route = route;
  this.stack.push(layer);
  return route;
};
Copy the code
  • The route method creates a route and a Layer. The third parameter of the Layer, Handle, is the core of the Express middleware execution. Stack = app._router = stack = app._router = stack App._router. Stack houses the middleware. Finally, return route. App. get: app.listen:
app.listen = function listen() {
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};
Copy the code
  • Listen createServer creates a server and passes parameters to the server. Listen createServer calls this. We can see from createApplication that app is now a function, so when the request comes in, execute app.handle. App.handle actually implements this._router. Handle (req, res, done). Express uses many proxy modes. In router.handle, you process some request urls and params, call the internal next method, find the layer matching the request from router.stack, and finally call the layer.handle_request method, passing next as an argument. In layer.handle_request, this.handle is called, and this. Handle is route.dispatch. Execute next at Dispatch to find the layer in the stack, execute layer.handle_request, and pass next. Layer. handle_request executes handle, the app.get callback function.
  • Initialize the
  • The get method
  • The initiating

routing

There are several ways to create routes in Express:

  • app.METHODS
  • app.route().get().post()
  • app.all()
  • Express.router (), this is a bit different from the above method. App. Use (path,router) is required. The detailed data structure is given below. The core code is:
this.lazyrouter();
var route = this._router.route(path);
route[methods](fn);
Copy the code
  • Express creates a route by calling _router.route() and route.methods.
proto.route = function route(path) {
  var route = new Route(path);
  var layer = new Layer(path, {}, route.dispatch.bind(route));
  layer.route = route;
  this.stack.push(layer);
  return route;
};
Route.prototype[method] = function(){
    // Convert parameters to array handles
    for (var i = 0; i < handles.length; i++) {
      var handle = handles[i];
      var layer = Layer('/', {}, handle);
      layer.method = method;
      this.methods[method] = true;
      this.stack.push(layer);
    }
    return this;
  };
Copy the code
  • From the layer. The route = the route; It can be concluded that routes are mounted on layer.
//Route data structure
{
    methods: {},path:path,
    stack:[
        Layer{
        handle:handle method:method ... }}]Copy the code

The middleware

  • Middleware is divided into application – level middleware, routing – level middleware, error – handling middleware, built-in middleware and third-party middleware.
  • The difference between error-handling middleware and other middleware is that the callback function takes four arguments, the first of which is the error object.
  • Middleware can be used in two ways: mounted on apps and mounted on express.Router().
  • Router. use, router.use, router.use
var layer = new Layer(path, {}, fn);
layer.route = undefined;
this.stack.push(layer); //app._router.stack.push(layer)
Copy the code
  • App. use and app.METHOD create middleware data structures that are different.
//app.use Creates the layer
Layer{
    route:undefined.handle:fn
}
//app.get creates the layer
Layer{
    route:route,
    handle:route.dispatch.bind(route)
}
Copy the code
  • Call with app.use, a route created with express.router (), i.e. app.use(Router), and the data structure becomes:
Layer{
    route:undefined.handle:router
}
Copy the code
  • If routing middleware calls routing middleware, router.use(router.use(router.get(path))) is eventually executed by app.use(router). The flow chart is as follows
  • Express middleware can be abstracted as follows
  • An example of a Router is a complete middleware and routing system, so it is often referred to as a “mini-app.” Many of the app’s use and definition routing methods are implemented through the Router.
  • The main data structures of APP, Router, Route and Layer can be shown in the following figure

conclusion

  • Routing and middleware are the core of Express. Learning routing and middleware, and then learning other aspects of Express will get twice the result with half the effort.
  • Read the source code, not only know the principle of Express, but also from the code to learn the advantages of using proxy mode, one implementation, multiple calls, single responsibility, small changes.
  • From the point of view of using API step by step “dig” source code, deduce the author’s ideas and data structure. If we derive the implementation of the source code from the author’s ideas and data structure and API design, we may be more efficient in reading the source code.

reference

  • Express document
  • Express source
  • github