preface

In sections 1 and 2, we talked a lot about the use of Express, but didn’t go into details or how it works. So today we will implement our own Express to deepen our understanding of Express. Do not aim to implement all Express code, but to implement its core logic.

The preparatory work

So let’s create a my-Express folder, NPM init to add some basic information. Let’s take a look at what we need and implement it step by step.

  • Routes of different paths can be resolved
  • Middleware logic processing
  • Template file processing, handwritten template engine
  • Handles static file access logic

With these points, we can solve the problem step by step.

The routing process

Leaving Express out of the way, let’s take a look at how Node.js builds a server. After all, we’re doing some upper-level encapsulation on Top of Node.js.

http.createServer()

Creating a server in Node.js is simple, with just a few lines of code:

var http = require('http');
http.createServer(function (request, response) {
	response.end('Hello World');
}).listen(3000);
Copy the code

After creating a server, we should deal with the logic of the different routes. Create a new express.js file to write our source logic, and a new app.js file to write our business test logic.

  • Create a listener functionappThe listener takes two argumentsreqres, both of these parameterscreateServerThe logical parameters that are provided to us to process the request and the logical parameters that are returned
  • app.jsCall thelistenMethod, our server will run
//express.js
const http = require('http')
const url = require('url')

function express() {
    function app(req, res) {
    }
    app.listen = function () {
        letserver = http.createServer(app) server.listen(... arguments) }return app
}
module.exports = express

//app.js
var express = require('./express')
var app = express()

app.listen(3000)
Copy the code

Parse the request method and path

We can obtain the method and path of the request by using the following method

let _method = req.method.toLowerCase()
let {
    pathname
} = url.parse(req.url, true)
Copy the code

The middleware

Before routing partitions are available, Express can register a route using the middleware method use.

app.use('/user/get'.function(req,res,next){})Copy the code

Before implementing this approach, consider that at the time of development, all the routes are already written, and then the program runs to match the routes according to the method and path. So app.use should be a registration method that registers both routing and middleware, and routing in Express is a type of middleware in general.

app.routes = [];
app.use = function (path, handler) {
    if (typeofhandler ! = ='function') {
        // This is a middleware function, all routes should be matched to
        handler = path
        path = '/'
    }
    let layer = {
        // This is a generic routing middleware
        method: 'middleware'./ / method middleware
        path,
        handler,
    }
    app.routes.push(layer)
}
Copy the code

Once the route has been saved, there should be a scheduling method. This is the next parameter that we often see in Express development. This scheduling method was also written by the author during the interview bytes. Let’s take a look at its implementation:

  • traverseroutesAn array of
  • In the case of middleware, the system determines whether the path matches. If the path matches, the system executes. If the path does not match, the system continues to traverse
  • As you can see from this, middleware calls must be callednextMethod, otherwise the following logic will not continue
let index = 0
function next() {
    let index = 0
    function next() {
        if (index === app.routes.length) {
            // No match is found
            res.end(`Cannot ${_method} ${pathname}`)}let {
            method,
            path,
            handler
        } = app.routes[index++] // Each call to next should fetch the next layer
        // If it is middleware, decide whether to use the logic of this middleware
        if (method === 'middleware') {
            if (path === '/' || path === pathname || pathname.startsWith(path + '/')) {
                handler(req, res, next)
            } else {
                next() // There is no match for the current middleware to continue the iteration}}else {
            // Process routing
            if ((_method === method || method === 'all') && (path === pathname || path === The '*')) {
                handler(req, res)
            } else {
                next()
            }
        }
    }
    next()
}
next()
Copy the code

routing

After implementing the middleware access logic, it is time to use the routing method. Express uses a route as follows

//app.js
var userRouter = require('./routes/userRouter')
app.use('/users',userRouter)

// /routes/userRouter.js
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/'.function (req, res, next) {
  res.send('respond with a resource');
});
Copy the code

That is, we will implement a Router method to implement the routing partition

  • Collect all user-defined routesroutesAn array of
  • And then torouterObject joins allhttpThe request ofmethod
  • app.useMethod to register routes, put all routes are ultimately advancedapp.routesArray traversal
//express.js
express.Router = function () {
    let router = {
        routes: []
    }
    http.METHODS.forEach(method= > {
        method = method.toLowerCase()
        router[method] = function (path, handler) {
            let layer = {
                method,
                path,
                handler
            }
            router.routes.push(layer)
        }
    })
    router.all = function (path, handler) {
        let layer = {
            method: 'all'.//method is all
            path,
            handler
        }
        app.routes.push(layer)
    }
    return router
}
/ /...
app.use = function (path, handler) {
    if (path && Object.prototype.toString.call(handler) == '[object Object]') {
        let basePath = path,
            routes = handler.routes
        routes.forEach(item= > {
            let {
                method,
                path,
                handler
            } = item
            let layer = {
                method,
                path: basePath + path,
                handler
            }
            app.routes.push(layer)
        })
    } else {
        / /...}}Copy the code

Processing parameters

After writing the route, we have to deal with the parameters passed in by the user. Here is a unified encapsulation, added to the REQ object.

await getRequest(req)
function getRequest(req) {
    return new Promise((resolve, reject) = > {
        / / get parameters
        req.query = url.parse(req.url, true).query
        / / post parameters
        let data = ' '
        req.on('data'.function (chunk) {
            data += chunk;
        });

        req.on('end'.function () {
            data = decodeURI(data);
            vardataObject = querystring.parse(data); req.body = dataObject resolve() }); })}Copy the code

At this point, we have finished writing the middleware and routing.

Template file processing

Next let’s deal with the view layer of the MVC framework, the Views template file. Remember the page from section 1 when you first opened the Express sample code? That’s a view file for rendering. Create a new views folder under the root directory. Then create a new index. TPL file with our own hh suffix. Let’s start with some sample code:

// routes/users.js
router.get('/my'.(req, res, next) = > {
    res.render('index', {
        title: 'my'.user: {
            name: 'my-express'}})})Copy the code
<! DOCTYPEhtml>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title><%= title%></title>
</head>

<body>
    <% if (user) { %>
    <h2><%= user.name %></h2>The < %} % ></body>

</html>
Copy the code

We want the user to render the index.tpl template and display the parameters of the render method when using a browser to localhost:3000/users/my. It is also agreed that the expression is <% %>. Let’s implement the Render method first

render

The main idea of render method is as follows:

  • inresParameter injectionrendermethods
  • Call therenderMethod, reads the static file and finally spits out a page
    function render(filePath,title, options) {
        if (filePath[0] = ='/') filePath.splice(0.1)
        fs.readFile(`./views/${filePath}.tpl`.(err, data) = > {
            if (err) {
                throw new Error(err)
            } else {
                let buffer = compile(data, options)// Compile the template
                res.write(data)
                res.end()
            }
        })
    }
Copy the code

The resulting page is uncompiled, meaning we also need a way to compile the template and replace the values passed in with the HTML. Here directly take a well-known industry template engine to deal with -EJS. See the EJS documentation for EJS documentation.

Install EJS: NPM install EJS –save

We can then use EJS’s render method to convert the template string we wrote to an HTML string.

function compile(tpl, options) {
    tpl = tpl.toString();
    let str = ejs.render(tpl, options)
    return Buffer.from(str)
}
Copy the code

At this point, our module parsing is complete.

Static file processing

At last. Our framework still lacks a static resource access logic. Let’s deal with that

  • To avoid some conflicts, parsimonious coding, we’re not likeExpressSo internally, we’re going to route tolocalhost:3000/static/xxx
  • After obtaining the file, spit it out, like template reading
  • Special handlingstaticThe logic of the
function getStatic(pathname, res) {
    let _path = pathname.slice(1).split('/')
    if (_path[0] = ='static') {
        let filePath = '/' + _path.join('/')
        fs.readFile(filePath, (err, data) = > {
            res.write(data)
            res.end()
        })
    }
}
Copy the code

The last

At this point, the Express series is over everywhere. When you implement your own Express, you may still have some problems with code encapsulation, exception handling, and so on. But our initial goal may not be to build a wheel that works in an identical build environment, but rather to write some core logic and learn the core ideas of an MVC framework in a small way.

Thank you for reading, if you like, you can help to click a “like” yo ~