First, install Express

npm install express
Copy the code

Express is installed for demonstration purposes.

Already put the code on Github: github.com/Sunny-lucki… . Can I get you a star? Thank you guys.

Create the example.js file

// example.js
const express = require('express')
const app = express()
const port = 3000

app.get('/'.(req, res) = > {
  res.send('Hello World! ')
})

app.listen(port, () = > {
  console.log(`Example app listening at http://localhost:${port}`)})Copy the code

As the code shows, executing node example.js runs a server.

As shown in the figure below, now we decide to create an Express file belonging to us, and change the express introduced to our handwritten express. .

Ok, now let’s implement our Express!

Create the myExpress.js file

const express = require('express')
const app = express()
Copy the code

From these two lines of code, we know that express gets a method, and that method executes to get app. And app is actually a function, and why it is a function, we’ll explain.

We can preliminarily realize the express as follows:

// myExpress.js
function createApplication() {
    let app = function (req,res) {}return app;
}

module.exports = createApplication;
Copy the code

In the above code, we find that the APP has a LISTEN method.

Therefore, we can further add the LISTEN method to the app:

// myExpress.js
function createApplication() {
    let app = function (req,res) {

    }
    app.listen = function () {}return app;
}

module.exports = createApplication;
Copy the code

App.listen creates a server and binds it to a port to run.

So listen can be perfected in this way.

// myExpress.js
let http = require('http');
function createApplication() {
    let app = function (req,res) {
        res.end('hahha');
    }
    app.listen = function () {
        letserver = http.createServer(app) server.listen(... arguments); }return app;
}

module.exports = createApplication;
Copy the code

Now, some of you might be wondering, why is http.createserver (app) being passed in here.

We don’t actually pass in the app, in other words, it’s okay to make the app not a method.

We could change it to this.

// myExpress.js
let http = require('http');
function createApplication() {
    let app = {};

    app.listen = function () {
        let server = http.createServer(function (req, res) {
            res.end('hahha') }) server.listen(... arguments); }return app;
}

module.exports = createApplication;
Copy the code

As the code shows, we can change the app to an object, and that’s fine.

.

Implement the app.get() method

The app.get method takes two arguments, the path and the callback function.

// myExpress.js
let http = require('http');
function createApplication() {
    let app = {};
    app.routes = []
    app.get = function (path, handler) {
        let layer = {
            method: 'get',
            path,
            handler
        }
        app.routes.push(layer)
    }
    app.listen = function () {
        let server = http.createServer(function (req, res) {
            
            res.end('hahha') }) server.listen(... arguments); }return app;
}

module.exports = createApplication;


Copy the code

As shown in the code above, we add a route object to the app. When the get method executes, we wrap the two parameters we receive, the path and the method, into an object and push them into routes.

As you can imagine, when we enter the path in the browser, the callback function in http.createserver is executed.

So, we need to get the browser’s request path here. Parse the path.

The loop routes are then traversed to find the corresponding route and the callback method is executed. This is shown in the code below.

// myExpress.js
let http = require('http');
const url  = require('url');
function createApplication() {
    let app = {};
    app.routes = []
    app.get = function (path, handler) {
        let layer = {
            method: 'get',
            path,
            handler
        }
        app.routes.push(layer)
    }
    app.listen = function () {
        let server = http.createServer(function (req, res) {
            / / remove the layer
            // 1. Obtain the method of the request
            let m = req.method.toLocaleLowerCase();
            let { pathname } = url.parse(req.url, true);
            
            // 2. Find the corresponding route and perform the callback method
            for (let i = 0 ; i< app.routes.length; i++){
                let {method,path,handler} = app.routes[i]
                if (method === m && path === pathname ) {
                    handler(req,res);
                }
            }
            res.end('hahha') }) server.listen(... arguments); }return app;
}

module.exports = createApplication;
Copy the code

Run the code.Run successfully:

Implement other methods such as POST.

Simply copy the app.get method and change the value of method to post.

// myExpress.js
let http = require('http');
const url  = require('url');
function createApplication() {... app.get =function (path, handler) {
        let layer = {
            method: 'get',
            path,
            handler
        }
        app.routes.push(layer)
    }
    app.post = function (path, handler) {
        let layer = {
            method: 'post', path, handler} app.routes. Push (layer)}...return app;
}

module.exports = createApplication;

Copy the code

We can do that, but there are other ways to do it besides post and get, so should we write every one of them like this? Of course not. There is an easy way.

// myExpress.js

function createApplication() {... http.METHODS.forEach(method= > {
        method = method.toLocaleLowerCase()
        app[method] = function (path, handler) {
            letlayer = { method, path, handler } app.routes.push(layer) } }); . }module.exports = createApplication;
Copy the code

As the code shows, http.methods is an array of METHODS. The array is shown below

[” GET “, “POST”, “DELETE”, “PUT”].

By iterating through the array of methods, you can implement all the methods.

The test run was successful.

Implement the app.all method

All means match all methods,

App.all (‘/user’) matches all routes whose path is /user

App.all (‘*’) indicates a route that matches any path and any method

Implementing the ALL method is also very simple, as shown in the following code

app.all = function (path, handler){
        let layer = {
            method: "all",
            path,
            handler
        }
        app.routes.push(layer)
    }
Copy the code

Then you only need to modify the matching logic of the router, as shown in the following code. You only need to modify the judgment.

app.listen = function () {
    let server = http.createServer(function (req, res) {
        / / remove the layer
        // 1. Obtain the method of the request
        let m = req.method.toLocaleLowerCase();
        let { pathname } = url.parse(req.url, true);

        // 2. Find the corresponding route and perform the callback method
        for (let i = 0 ; i< app.routes.length; i++){
            let {method,path,handler} = app.routes[i]
            if ((method === m || method === 'all') && (path === pathname || path === "*")) { handler(req,res); }}console.log(app.routes);
        res.end('hahha') }) server.listen(... arguments); }Copy the code

Success is visible.

Implementation of middleware app.use

The implementation of this method, just like the other methods, is shown in the code.

app.use = function (path, handler) {
    let layer = {
        method: "middle",
        path,
        handler
    }
    app.routes.push(layer)
}
Copy the code

But the question is, when we use middleware, we use the next method, to keep the program going, how does it execute.

app.use(function (req, res, next) {
  console.log('Time:'.Date.now());
  next();
});
Copy the code

So we have to implement the method next.

In fact, you can guess that next should be a crazy call to their own methods. That’s recursion

And every time we recurse, we take the handler that was pushed into the routes and execute it.

In fact, whether it’s app.use or app.all or app.get. In fact, we put the layer into the routes, and then we walk through the routes to determine whether the handler method in the layer should be executed. See the implementation of the next method.

function next() {
    // We have iterated through the array, but no matching path was found
    if (index === app.routes.length) return res.end('Cannot find ')
    let { method, path, handler } = app.routes[index++] // Each call to next goes to the next layer
    if (method === 'middle') { // Processing middleware
        if (path === '/' || path === pathname || pathname.starWidth(path + '/')) {
            handler(req, res, next)
        } else { // Continue the traversalnext(); }}else { // Handle routing
        if ((method === m || method === 'all') && (path === pathname || path === "*")) {
            handler(req, res);
        } else{ next(); }}}Copy the code

You can see that it’s a recursive method traversing the Routes array.

And we can see that if we are using middleware, the middleware will execute if the path is “/” or if the prefix matches. Because handler uses the req and res arguments. So the next method is defined in LISTEN.

The following code looks like this:

// myExpress.js
let http = require('http');
const url = require('url');
function createApplication() {
    let app = {};
    app.routes = [];
    let index = 0;

    app.use = function (path, handler) {
        let layer = {
            method: "middle",
            path,
            handler
        }
        app.routes.push(layer)
    }
    app.all = function (path, handler) {
        let layer = {
            method: "all",
            path,
            handler
        }
        app.routes.push(layer)
    }
    http.METHODS.forEach(method= > {
        method = method.toLocaleLowerCase()
        app[method] = function (path, handler) {
            let layer = {
                method,
                path,
                handler
            }
            app.routes.push(layer)
        }
    });
    app.listen = function () {
        let server = http.createServer(function (req, res) {
            / / remove the layer
            // 1. Obtain the method of the request
            let m = req.method.toLocaleLowerCase();
            let { pathname } = url.parse(req.url, true);

            // 2. Find the corresponding route and perform the callback method
            function next() {
                // We have iterated through the array, but no matching path was found
                if (index === app.routes.length) return res.end('Cannot find ')
                let { method, path, handler } = app.routes[index++] // Each call to next goes to the next layer
                if (method === 'middle') { // Processing middleware
                    if (path === '/' || path === pathname || pathname.starWidth(path + '/')) {
                        handler(req, res, next)
                    } else { // Continue the traversalnext(); }}else { // Handle routing
                    if ((method === m || method === 'all') && (path === pathname || path === "*")) {
                        handler(req, res);
                    } else {
                        next();
                    }
                }
            }

            next()
            res.end('hahha') }) server.listen(... arguments); }return app;
}

module.exports = createApplication;
Copy the code

When we request the path, we find that the middleware actually executed successfully.

But the mid-point is far from perfect.

Because, when we use middleware, it is possible to not pass routing. Such as:

app.use((req,res) = > {
  console.log("I'm the midpoint with no route.");
})
Copy the code

If not, give the default path “/”. The implementation code is as follows:

app.use = function (path, handler) {
    if(typeofpath ! = ="string") { // The first argument is not a string, indicating that it is not a path, but a method
        handler = path;
        path = "/"
    }
    let layer = {
        method: "middle",
        path,
        handler
    }
    app.routes.push(layer)
}
Copy the code

See, isn’t that clever? It’s easy.

We try to access the path “/middle”

Yi? The first middleware was not implemented, why?

By the way, when using middleware, next() is executed at the end before it is handed over to the next middleware or route.

When we request the “/middle” path, we can see that the request succeeds and the middleware executes successfully. To show that our logic is correct.

Actually, the middleware is done, but don’t forget, there’s a bug middleware, right?

What is error middleware?

The error-handling middleware function is defined in the same way as other middleware functions. The difference is that the error-handling middleware function has four independent variables instead of three, and has specific characteristics (Err, req, res, next) :

app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke! ');
});
Copy the code

When we execute the next() method, if an error is thrown, we will directly look for the faulty middleware to execute, and no other middleware or route will be executed.

Here’s an example:

As shown in the figure, when the first middleware passes a parameter to next, it indicates an error in the execution. It then bypasses the rest of the middleware and routing and directly executes the faulty middleware. Of course, after the faulty middleware is executed, the subsequent middleware is executed.

Such as:

As shown in the figure, the one after the fault middleware will execute.

So how does that work?

It’s very simple, just look at the code explanation, just add another layer of judgment in next:


function next(err) {
    // We have iterated through the array, but no matching path was found
    if (index === app.routes.length) return res.end('Cannot find ')
    let { method, path, handler } = app.routes[index++] // Each call to next goes to the next layer
    if( err ){ // If there is an error, the middleware execution should be sought.
        if(handler.length === 4) { // Find the wrong middleware
            handler(err,req,res,next)
        }else { // Continue to Xuzhou
            next(err) 
        }
    }else {
        if (method === 'middle') { // Processing middleware
            if (path === '/' || path === pathname || pathname.starWidth(path + '/')) {
                handler(req, res, next)
            } else { // Continue the traversalnext(); }}else { // Handle routing
            if ((method === m || method === 'all') && (path === pathname || path === "*")) {
                handler(req, res);
            } else{ next(); }}}}Copy the code

Look at the code visible in the next to determine whether err value, you can determine whether you need to find error middleware to execute.

As shown in the figure, request /middle path, successful execution.

At this point, the implementation of the Express framework is complete.

Learn to summarize

Through the implementation of the principle of Express handwriting, I have a deeper understanding of the use of Express and found that:

  1. Middleware and routes are pushed into a routes array.
  2. When the middleware is executed, the next is passed so that the next middleware or route can be executed
  3. When the route is executed, the next is not passed, and the traversal of the routes ends prematurely
  4. After the faulty middleware is executed, the subsequent middleware or routing will be executed.

The last

Welcome to pay attention to the public number “front sunshine”, there are more handwritten principle articles, you can also join the technical exchange group and the internal push group, the public number collected the factory’s internal push code, quick to get it!