What is express?

Fast, Unopinionated, minimalist Web Framework for Node. Express is a Web server based on Node, which is characterized by: Fast, simple, and small.

Features:

  • Robust Routing Robust routing system
  • Focus on high performance
  • Super high test coverage
  • HTTP helpers (redirection, caching, etc) HTTPVarious enablers such as redirection, caching, etc
  • View System Supporting 14+ Template Engines supports more than 14 View engines
  • Content negotiation?
  • Executable for generating applications quickly

When I first started working with Node, the backend framework I used was Express, and I could only write the simplest interfaces based on the official documentation API. Koa compared (see the reference comparison koa | analysis), may use in the internal inherited development package module, can be introduced directly, without having to download separately. Existing Express-Generator scaffolding, global download installation can be used

Let’s cut to the chase.

Init project Initializes the project

mkdir express_practice
cd express_practice
npm init -y
Copy the code

The Installation is installed

This is a Node.js module available through the NPM Registry. Express is a Node module that can be downloaded through the NPM Registry repository. Before installing, download and install Node.js.node.js 0.10 or higher is required. Install node > 0.10 Installation is done using the NPM install command.

npm i express --save
or
npm i express
or
npm i express -S
Copy the code

Use the Usage

The new app. Js

Create a file for the Web application.

touch app.js
Copy the code

Introducing dependency modules

  • Express Core Module
  • App. Router is a class
  • User is a secondary routing instance
const express = require('express');
const app = express();
const Router = app.Router;
const user = new Router();
Copy the code

User Indicates the request under the route

  • Three parameters
  • Next call does not pass the parameter, is to give way by the system in no matching path and method, to the next middleware; Passing in parameters is throwing an error
user.get('/remove', (req, res, next) => {
    res.send(`/user/remove`)
    next();
})
user.get("/add", (req, res, next) => {
    res.end('/user/add');
    next();
})
Copy the code

App application request

app.get('/'.function (req, res) {
  res.send('Hello World')
})

app.get('/'.function (req, res) {
  res.send('Hello World')})Copy the code

Register the user secondary routing system

  • Registered middleware
  • Interface 1: /user/remove
  • Interface 2: /user/add
app.use('/user', user);
Copy the code

Error capture processing

  • Error middleware
  • Four parameters, the first parameter is the caught error
  • The error was caused by calling next in the previous request
app.use(function(err, req, res, next) {
    if (err) res.send(err);
})
Copy the code

Listen on port

app.listen(3000);
Copy the code

Express Core Concepts

The Express core uses the CommonJS specification, which is basically callback based middleware and requests.

Outlet Structure Directory structure

- express
    - index.js
    - lib
        - express.js
        - application.js
        - router
            - index.js
            - layer.js
            - route.js
Copy the code

Fulfill implementation

Create a package file and its subfiles

mkdir express && cd express
touch index.js
mkdir lib && cd lib
touch express.js
touch application.js
mkdir router && cd router
touch index.js
touch layer.js
touch route.js
Copy the code

express/index.js

Express is essentially a function that returns an instance of an Application class

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

Express uses the CommonJS specification. Exports and imports of files are: require(), module.exports respectively.

express/lib/express.js

The introduction ofApplicationApplication systems andRouterRouting system

  • Application and routing systems have their own roles to play.
  • createApplicationIs a factory function, instantiated inside the functionApplicationInstance object and return it for each introductionexpressThe package module generates a single application instance.
  • createApplication.RouterYou mount a Router class that uses the routing system independently.
const Application = require('./application');
const Router = require('./router');
Copy the code

createApplicationA function that instantiates an application instance

function createApplication() {
    return new Application();
}
Copy the code

Add the Router attribute to the function

createApplication.Router = Router;
Copy the code

Note: A function is also an object, and attributes can be added.

The export function

module.exports = createApplication;
Copy the code

express/lib/application.js

Download third-party tool libraries

npm i methods --save
Copy the code

Importing dependency packages

  • http nodeCore module, does not need to download the installation, used to callhttp.createServer()Create a Web service.
  • RouterRouting system class, see detailsrouter.js
  • methodsThird-party librariesdeclare const methods: string[];
const http = require('http');
const Router = require('./router');
const methods = require('methods');
Copy the code

createApplication

  • Instance attributesconfig
  • Instance attributes_router, memory optimization will be followed *
function Application(){ 
    this.config = {};
    this._router = new Router;
}
Copy the code

Set instance method

  • If the parameter is set to 2, the operation is performed
  • If the parameter is set to 1, the value operation is performed
Application.prototype.set = function (key, value) {
    if (arguments.length === 2) {
        this.config[key] = value;
    } else {
        return this.config[key]; }}Copy the code

Layze_route instance method

Performance optimization:

  • Modify the instance properties in Constructor.
  • Replace the Application once instantiated, initial _router attribute, the client if not call any method | requests param method, just call the use method, can cause performance of waste.
  • There is only one _router, which means you only need to instantiate it once for assignment.
function Application(){ 
     this.config = {};
-    this._router = new Router;
}
Copy the code
Application.prototype.lazy_route = function() {
    if (!this._router) return this._router = new Router();
}
Copy the code

Param instance method

The implementation is left to the _router routing system

Application.prototype.param = function (key, handler) {
    this.lazy_route();
    this._router.param(key, handler);
}
Copy the code

Use instance method

The implementation is left to the _router routing system

Application.prototype.use = function(path, handler) {
    this.lazy_route();
    this._router.use(path, handler);
}
Copy the code

Method Instance method

Handlers are passed in to the _Router routing system

methods.forEach(method= > {
    Application.prototype[method] = function(path, ... handlers) {
        if (method === 'get' && arguments.length === 1) {
            return this.set(path);
        }
        this.lazy_route();
        this._router[method](path, ...handlers);
    }
})
Copy the code

Listen instance method

  • callhttp.createServer(function(req, res)), create a server service;
  • Prepare adoneMethod, used when the routing system does not have a matching route (method and path)nextMethods;
  • call_router.handleAnd the incomingreq.res.doneThree parameters make way for the system to process the request and respond to it;
  • Specify the port number to listen on, optionally passing in the callback function.

Application.prototype.listen = function() {
    let server = new http.createServer((req, res) = > {
        function done() {
            res.end(`Cannot ${req.method} ${req.url}`);
        }
        this.lazy_route();
        res.send = res.end;
        this._router.handle(req, res, done); }); server.listen(... arguments); }Copy the code

exportApplication

module.exports = Application;
Copy the code

express/lib/router/index.js

Introducing dependency modules

const url = require('url');
const methods = require('methods');
const Route = require('./route');
const Layer = require('./layer');
Copy the code

Declare aprotoobject

  • forRouterInside the constructor of the classrouterFunction inheritance
const proto = {};
Copy the code

createRouter

  • constructorFunction returns one insiderouterFunction, which contains multiple properties/methods
  • Why override the constructor?
    • Used for level-2 routing
    // User is a middleware,
    const user = new app.Router();
    // Req, res, and next are passed in when the application instance calls use
    app.use('user', user);
    // The user function call is the router call, which then calls router.handle
    Copy the code
function Router() {
    const router = function (req, res, next) {
        router.handle(req, res, next);
    }
    router.stack = [];
    router.__proto__ = proto;
    router.paramsCallback = {}; // { key: [fn, fn] }
    return router;
}
Copy the code

Note:

  • The router mechanism is a classic publish/subscribe model.

  • Subscription phase:

    Call use/ get/ post/… Add a layer to the stack array

  • Release phase:

    • callapp.listen()When seeApplication.prototype.listen
  • There are two ways to instantiate a Router:

    • callapp[method](path, handlers)when
    • callapp.RouterExample of secondary routing

Param method

proto.param = function (key, handler) {
    if (this.paramsCallback[key]) {
        this.paramsCallback[key].push(handler);
    } else {
        this.paramsCallback[key] = [handler]; }}Copy the code

The route method

  • Instantiate arouterouting
  • Instantiate alayer
  • Complete a subscription
  • Return to theroute
proto.route = function (path) {
    const route = new Route();
    const layer = new Layer(path, route.dispatch.bind(route));
    layer.route = route;
    this.stack.push(layer);
    return route;
}
Copy the code

Method of use

  • Calling the route method
  • Instantiate alayer
  • layer.routeSet toundefinedValue, which is easy to identify as common middleware at the time of releaserouterouting
proto.use = function (path, handler) {
    if (typeof path === 'function') {
       handler = path;
       path = "/";
    }
    const layer = new Layer(path, handler);
    layer.route = undefined;
    this.stack.push(layer);
}
Copy the code

[method] method

  • Each time it is called, a new layer is addedlayer
  • The triggerroute[method]
methods.forEach(method= > {
    proto[method] = function (path, ... handlers) {
        let route = this.route(path); route[method](handlers); }})Copy the code

Process_params parameter method

proto.process_params = function (layer, req, res, done) {
    if(! layer.keys || layer.keys.length ===0) {
        return done();
    }
    // key [id, name]
    let keys = layer.keys.map(item= > item.name);
    let params = this.paramsCallback;
    let idx = 0;
    function next () {
        if (keys.length === idx) return done();
        let key = keys[idx++];
        processCallback(key, next);
    }
    next();
    function processCallback(key, out) {
        let fns = params[key];
        if(! fns) {return out();
        }
        let idx = 0;
        let value = req.params[key];
        function next() {
            if (fns.length === idx) return out();
            letfn = fns[idx++]; fn(req, res, next, value, key); } next(); }}Copy the code

Handle method

  • Before release subscription methods | middleware;
  • outFunction isApplication.prototype.listenInternally incomingdoneMethod, indicating the end;
  • dispatchIs the core method, using recursive method;
  • removedIs the subpath of the secondary route. If the request path is /user/add, the first match is made/userAt this time, the path is /add. When /add fails to match, it will be recursively calleddispatchCall the next layer and append /user back.
proto.handle = function (req, res, out) {
    let { pathname } = url.parse(req.url);

    // Start processing from the first layer of the stack
    let idx = 0;
    let removed = "";

    // The core method err is passed from the next function call to indicate error capture
    const dispatch = err= > {
        // Stack is empty, directly out
        if (idx === this.stack.length) return out();

        // If removed does not indicate that the last round failed to be matched, the current round is re-matched and the removed round is added again
        / / empty removed
        if (removed) {
            req.url = removed + req.url;
            removed = "";
        }

        // Get the current layer and pointer down
        let layer = this.stack[idx++];

        // If an error is caught
        if (err) {
            // The current layer is not error middleware
            if(! layer.route) { layer.handle_err(err, req, res, dispatch); }else {
                Use ((err, req, res, next) => {})
                // Leave it to it
                returndispatch(err); }}else {
            // No errors and match
            if (layer.match(pathname)) {
                if(! layer.rout) {// If it is middleware, execute the corresponding method directly
                    // Delete the middleware path here
                    // /user/add /
                    if(layer.handler.length ! = =4) {
                        if(layer.path ! = ='/') { // If middleware is /
                            removed = layer.path;
                            req.url = req.url.slice(removed.length)
                        }
                        layer.handle_request(req, res, dispatch);
                    } else{ dispatch(); }}else {
                    / / routing
                    if (layer.route.methods[req.method.toLowerCase()]) {
                        req.params = layer.params;
                       this.process_params(layer, req, res, () => { layer.handle_request(req, res, dispatch); })}else{ dispatch(); }}}else {
                dispatch();
            }
        }

    }
    dispatch();
}
Copy the code

exportRouter

module.exports = Router;
Copy the code

express/lib/router/layer.js

Downloading dependency packages

npm i path-to-regexp --save
Copy the code

Importing dependency packages

  • path-to-regexp
const { pathToRegexp } = require('path-to-regexp');
Copy the code

Create a Layer class

Layers are used in two scenarios

  • inRouter.stackFor each array elementlayer;
  • inRoute.stackIn, ditto.
function Layer(path, handler) {
    this.path = path;
    this.handler = handler;
    this.reg = pathToRegexp(this.path, this.keys=[]);
}
Copy the code

The match method

Layer.prototype.match = function(pathname) {
    let match = pathname.match(this.reg);
    if (match) {
        this.params = this.keys.reduce((memo, current, index) = > (memo[current.name] = match[index+1], memo), {});
        return true;
    }
    if (this.path === pathname) return true;
    if (!this.route) {
         if(this.path === '/') {return true;
        }
        return pathname.startsWith(this.path+'/')}}Copy the code

Handle_request method

The core

Layer.prototype.handle_request = function(req, res, next) {
    this.handler(req, res, next);
}
Copy the code

Handle_err method

Layer.prototype.handle_err = function(err, req, res, next) {
    // It could be the wrong middleware
    if (this.handler.length === 4) {
        return this.handler(err, req, res, next);
    }
    next(err);
}
Copy the code

Layer was derived class

module.exports = Layer;
Copy the code

express/lib/router/route.js

Importing dependency packages

const methods = require('methods');
const Layer = require('./layer');
Copy the code

createRoute

function Route() {
    this.stack = [];
    this.methods = {};
}
Copy the code

The methods instance method is used to quickly determine whether the current matching method is correct, saving performance

Dispatch instance method

The core

Route.prototype.dispatch = function (req, res, out) {
    let index = 0;
    let method = req.method.toLowerCase();
    const dispatch = err= > {
        if (err) return out(err);
        if (index === this.stack.length) return out();
        let layer = this.stack[index++];
        if (layer.method === method) {
            layer.handle_request(req, res, dispatch);
        } else dispatch();
    }
    dispatch();
}
Copy the code

[method] Instance method

methods.forEach(method= > {
    Route.prototype[method] = function (handlers) {
        handlers.forEach(handler= > {
            let layer = new Layer('/', handler);
            layer.method = method; // What method does the user call
            this.methods[method] = true; // If the user binds the method, I will record it
            this.stack.push(layer); }); }});Copy the code

Export the Route class

module.exports = Route;
Copy the code

DONE

After writing for a long time, I will finish – a simple diagram will be added for better understanding and learning.

If the article is wrong, welcome correction.

There was an article about turning the clock fast, sitting for hours and still having a little head on it.

Good news~~~

Today I heard the good news that all the makeshift hospitals in Wuhan have been closed. Our motherland is great. Now it is time to start counting down to resume work