This article was originally published at: github.com/bigo-fronte… Welcome to follow and reprint.

First, install Express

npm install express
Copy the code

Express was installed for demonstration purposes.

Create 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 starts a server.

As shown in the figure below, we now decide to create an Express file of our own, importing express instead of importing our handwritten Express. .

Ok, now let’s implement our Express!

Have put the code on github: github.com/Sunny-lucki… .

Create the myExpress.js file

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

Express gets a method, and then the method gets app. App is actually a function, and we’ll explain why.

We can preliminarily implement Express as follows:

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

module.exports = createApplication;
Copy the code

In the code above, you can see that your 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

What app.listen does is create a server and run it by binding it to a port.

So you can refine the Listen method this way.

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

You might wonder why HTTP. CreateServer (app) is passed to the app.

In fact, we don’t pass in app, which means that app is not a method, but it’s ok.

We could do it like 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 shown in the code, we can change the app to an object, and there is no problem.

.

Implement the app.get() method

The app.get method takes two arguments, a path and a 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, and then when the get method executes, we wrap the two parameters, path and method, into an object and push them into routes.

As you can imagine, when we enter a path in the browser, we will execute the callback function in http.createServer.

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

Then loop over the routes to find the corresponding route and execute the callback method. As 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. Get the request method
            let m = req.method.toLocaleLowerCase();
            let { pathname } = url.parse(req.url, true);
            
            // 2. Find the corresponding route and execute 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

Let’s run the code.Run successfully:

Implement post and other methods.

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

This is possible, but there are other methods besides post and GET, so should we write every one of them like this? Of course not. There’s 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. An array as shown below

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

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

The test ran, and it was a success.

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 code below

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

Then you just need to change the router matching logic, as shown in the code below, and just change the judgment.

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

        // 2. Find the corresponding route and execute 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

Visible success.

Implementation of middleware app. Use

The implementation of this method is similar to the other methods, as shown in the code.

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

But the problem is, when we use middleware, we use the next method to keep the program going, and 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 next method.

In fact, you can guess that next is a method that calls itself like crazy. That’s recursion

And every time you recurse, the handler that was pushed into the routes is taken out and executed.

In fact, whether app.use or app.all or app.get. In fact, the layer is placed in routes, and then the routes are uniformly traversed to determine whether the handler method in the layer should be executed. Take a look at the implementation of the next method.

function next() {
    // We iterated through the entire array, but still did not find a matching path
    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') { // Process middleware
        if (path === '/' || path === pathname || pathname.starWidth(path + '/')) {
            handler(req, res, next)
        } else { // Continue traversingnext(); }}else { // Process routing
        if ((method === m || method === 'all') && (path === pathname || path === "*")) {
            handler(req, res);
        } else{ next(); }}}Copy the code

You can see that this is a traversal routes array of recursive methods.

And we can see that with middleware, the middleware will execute as long as path is a “/” or prefix match. Because handler uses the arguments req and res. So this next method is going to be 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. Get the request method
            let m = req.method.toLocaleLowerCase();
            let { pathname } = url.parse(req.url, true);

            // 2. Find the corresponding route and execute the callback method
            function next() {
                // We iterated through the entire array, but still did not find a matching path
                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') { // Process middleware
                    if (path === '/' || path === pathname || pathname.starWidth(path + '/')) {
                        handler(req, res, next)
                    } else { // Continue traversingnext(); }}else { // Process 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.

However, the implementation of the median price here is far from perfect.

Because when we use middleware, we don’t have to pass routes. Such as:

app.use((req,res) = > {
  console.log("I'm the middle price without routes.");
})
Copy the code

It is very simple to determine whether there is a path to pass, if not, give a default path “/”, the implementation code is as follows:

app.use = function (path, handler) { if(typeof path ! // Handler = path; // Handler = path; path = "/" } let layer = { method: "middle", path, handler } app.routes.push(layer) }Copy the code

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

We try to access path “/middle”

Yi? The first middleware didn’t execute, why?

By the way, with middleware, next() is executed at the end before it is passed to the next middleware or route.

When we request the “/middle” path, we can see that the request actually succeeds and the middleware executes successfully. That means our logic is fine.

Actually, the middleware is done, but don’t forget, there’s an error middleware?

What is error middleware?

Error-handling middleware functions are defined in much the same way as other middleware functions, except that error-handling functions have four arguments instead of three and have special 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 executing the next() method, if we throw an error, we will directly look for the error middleware to execute, not other middleware or routing.

Here’s an example:

As shown in the figure, when the first middleware passes parameters to next, an execution error occurs. It then skips the rest of the middleware and routing and executes the error middleware directly. Of course, after the execution of the faulty middleware, the execution of the later middleware continues.

Such as:

As shown, the latter of the faulty middleware is executed.

How does that work?

It’s easy to read the code directly, just add another layer of judgment in Next:


function next(err) {
    // We iterated through the entire array, but still did not find a matching path
    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 are errors, look for middleware execution.
        if(handler.length === 4) { // Find the wrong middleware
            handler(err,req,res,next)
        }else { // Continue to Xuzhou
            next(err) 
        }
    }else {
        if (method === 'middle') { // Process middleware
            if (path === '/' || path === pathname || pathname.starWidth(path + '/')) {
                handler(req, res, next)
            } else { // Continue traversingnext(); }}else { // Process routing
            if ((method === m || method === 'all') && (path === pathname || path === "*")) {
                handler(req, res);
            } else{ next(); }}}}Copy the code

Check whether err has a value in next by looking at the code visible to determine whether error-finding middleware is needed to execute.

As shown, the request /middle path is successfully executed.

At this point the Implementation of the Express framework is complete. Can you give me a STAR? Thank you guys.

Learn to summarize

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

  1. Middleware and routes are pushed into a Routes array.
  2. When middleware is executed, next is passed so that the next middleware or route can be executed
  3. Next is not passed when the route is executed, which causes the routes traversal to end prematurely
  4. After the error middleware has been executed, subsequent middleware or routing will still be executed.

Welcome everyone to leave a message to discuss, wish smooth work, happy life!

I’m bigO front. See you next time.