The Express framework was originally designed to extend the functionality of Node’s built-in modules and improve development efficiency. When you delve deeper, You’ll see that Express builds a layer of abstraction on top of Node’s built-in HTTP module. In theory, all Express implementations can be implemented using pure Node.

In this article, we will explore the relationship between Express and Node based on the previous Node content, including concepts such as middleware and routing. Of course, this is just an overview and the details will come later.

Overall, Express offers four main features:

  1. Unlike pure Node code, which uses a single function to process all requests, Express uses a “middleware stack” to process the flow.
  2. Routing is similar to middleware in that a handler is invoked only when you access a specific URL through a specific HTTP method.
  3. The request and Response object methods are extended.
  4. The view module allows you to dynamically render and change HTML content, and to write HTML in other languages.

The middleware

Middleware is one of the biggest features of Express. Middleware is very similar to the native Node handler functions (accepting a request and responding to it), but unlike native, middleware divides the processing and uses multiple functions to form a complete processing flow.

We’ll see a variety of applications of middleware in our code. For example, use one middleware to log all requests, then set HTTP headers in the other middleware, and then continue with the process. While request processing can also be done in a “big function,” middleware that splits tasks into explicitly independent functions is clearly more consistent with SRP rules in software development.

Middleware is not Express specific. The same concept exists with Django for Python or Laravel for PHP. Similarly, Ruby’s Web framework has a concept called Rack middleware.

Now let’s re-implement the Hello World application using Express middleware. You’ll find that you can develop in just a few lines of code, improving efficiency while eliminating hidden bugs.

Express edition Hello World

Start with a new Express project: create a new folder and create a package.json file in it. Recall how package.json works, which lists the project’s dependencies, project name, author, and so on. The package.json in our new project looks like this:

{
  "name": "hello-world"."author": "Your Name Here!"."private": true."dependencies": {}}Copy the code

Next, execute the command to install the latest Express and save it to package.json:

npm install express -save

After the command is executed, Express is automatically installed in node_modules and the dependencies are explicitly listed in package.json. At this point, package.json looks like this:

{
  "name": "hello-world"."author": "Your Name Here!"."private": true."dependencies": {
        "express": "^ 5.0.0"}}Copy the code

Next, copy the following code into app.js:

var express = require("express");  
var http = require("http");
var app = express();   
 
app.use(function(request, response) {  
    response.writeHead(200, { "Content-Type": "text/plain" });      
    response.end("Hello, World!");  
}); 
 
http.createServer(app).listen(3000);  

Copy the code

First, we introduced the Express and HTTP modules in turn.

The variable app is then created using the Express () method, which returns a request handler closure. This is important because it means I can pass it to the http.createserver method as before.

Remember the native Node request handling mentioned in the previous chapter? It goes something like this:

var app = http.createServer(function(request, response) {
    response.writeHead(200, { "Content-Type": "text/plain" });
    response.end("Hello, world!");
});
Copy the code

The two pieces of code are very similar in that the callback closure contains two parameters and the response is the same.

Finally, we created a service and started it. Http.createserver takes a function, so it’s reasonable to assume that app is just a function that represents a complete middleware process in Express.

How does middleware work at a high level

In native Node code, all HTTP request processing is done in a single function:

function requestHandler(request, response) {
    console.log("In comes a request to: " + request.url);
    response.end("Hello, world!");
}
Copy the code

If abstracted as a flowchart, it would look something like this:

This is not to say that other functions cannot be called during processing, but that all request responses are sent by this function.

The middleware uses a set of middleware stack functions to handle these requests, as shown below:

Then, it’s important to understand why Express uses a set of middleware functions and what they do.

Now let’s review the previous user authentication example: the user’s private information is displayed only if the authentication is successful, and each access request is logged.

There are three middleware functions in this application: request logging, user authentication, and information presentation. The workflow of middleware is: each request is recorded first, and then user authentication is carried out. Verification is carried out through information presentation, and finally the response to the request is made. So, there are two possible scenarios for the entire workflow:

In addition, some of these middleware functions need to respond to the response. If there is no response, the server suspends the request and the browser waits.

The advantage of this is that we can split the application. The split components are not only convenient for later maintenance, but also can be combined differently.

Middleware that does not modify anything

Middleware functions can modify request and response, but they are not required. For example, the previous logging middleware code: all it needs to do is log. The code for a purely functional intermediate function without any modifications is roughly as follows:

function myFunMiddleware(request, response, next) {... nest(); }Copy the code

Because middleware functions are executed from the top down. So, with a purely functional request logging middleware, the code looks like this:

var express = require("express");
var http = require("http");
var app = express();
// Logging middleware
app.use(function(request, response, next) {
  console.log("In comes a " + request.method + " to " + request.url);
  next();
});

// Send the actual response
app.use(function(request, response) {
  response.writeHead(200, { "Content-Type": "text/plain" });
  response.end("Hello, world!");
});
http.createServer(app).listen(3000);
Copy the code

Modify the middleware of Request and Response

Not all middleware is the same as above. Some middleware functions need to process request and response, especially the latter.

Let’s implement the validation middleware functions mentioned earlier. For the sake of simplicity, only cases where the current number of minutes is even are allowed to pass. So, the middleware function code is roughly as follows:

app.use(function(request, response, next) {
  console.log("In comes a " + request.method + " to " + request.url);
  next();
});
app.use(function(request, response, next) {
  var minute = (new Date()).getMinutes();
  // If the call is made in the first minute of the hour, call next() to continue
  if ((minute % 2) = = =0) {
    next();
  } else {
    // If the verification fails, send a status code of 403 and respond
    response.statusCode = 403;
    response.end("Not authorized."); }}); app.use(function(request, response) {
  response.end('Secret info: the password is "swordfish"! '); // Send password information
});
Copy the code

Third party middleware class library

In most cases, what you’re trying to do has probably already been done. That said, mature solutions may already exist in the community for some commonly used functions. Let’s take a look at some of the third-party modules commonly used in Express.

MORGAN: Logging middleware

Morgan is a very powerful logging middleware. It can record the user’s behavior and request time. This is useful for analyzing abnormal behavior and possible site crashes. Morgan is also the preferred logging middleware in Express most of the time.

Use the command NPM install Morgan –save to install the middleware and modify the code in app.js:

var express = require("express");
var logger = require("morgan");
var http = require("http");
var app = express();
app.use(logger("short")); 
app.use(function(request, response){
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.end("Hello, world!");
});
http.createServer(app).listen(3000);
Copy the code

Visit http://localhost:3000 again and you’ll see Morgan’s log.

Static file middleware for Express

Sending static files over the network is a common requirement scenario for Web applications. These resources typically include image resources, CSS files, and static HTML files. But a simple file sending behavior is a lot of code because of the large number of boundary cases and performance concerns that need to be checked. The express.static module built into Express simplifies things to the greatest extent.

Assuming that we now need to provide file services to the public folder, we can greatly reduce the amount of code simply by using static file middleware:

var express = require("express");
var path = require("path");
var http = require("http");
var app = express();
var publicPath = path.resolve(__dirname, "public"); 
app.use(express.static(publicPath)); 
app.use(function(request, response) {
    response.writeHead(200, { "Content-Type": "text/plain" });
    response.end("Looks like you didn't find a static file.");
});
http.createServer(app).listen(3000);
Copy the code

Now, any static file in the public directory can be requested directly, so you can put all the files you need in that directory. If no matching files exist in the public folder, it will proceed to the next middleware and respond with a message of unmatched files.

Why path.resolve? The reason you don’t use /public directly is because Mac and Linux use /public while Windows uses the evil backslash \public. Path. resolve is used to resolve multiplatform directory paths.

More Middleware

In addition to the Morgan middleware and Express static middleware described above, there are many other powerful middleware, such as:

  • Connect-ratelimit: Allows you to control the number of connections per hour. If someone makes a large number of requests to the service, you can simply return an error to stop processing those requests.
  • Helmet: HTTP headers can be added to protect against some cyber attacks. This will be covered in a later section on security.
  • Cookie-parses: parses the cookie information in the browser.
  • Response-time: You can better debug application performance by sending x-Response-time information.

routing

Routing is a technique for mapping URL and HTTP methods to specific processing callback functions. Assuming the project has a home page, an about page, and a 404 page, let’s see how the routes are mapped:


var express = require("express");
var path = require("path");
var http = require("http");
var app = express();

// Set up static file middleware as before.
// All requests pass through this middleware and proceed if no file is found
var publicPath = path.resolve(__dirname, "public");
app.use(express.static(publicPath));

// this is called when the root directory is requested
app.get("/".function(request, response) {
    response.end("Welcome to my homepage!");
});

// called when requesting /about
app.get("/about".function(request, response) {
    response.end("Welcome to the about page!");
});

// is called when /weather is requested
app.get("/weather".function(request, response) {
    response.end("The current weather is NICE.");
});

// If the preceding parameters are not matched, the route is incorrect. Return to 404 page
app.use(function(request, response) {
    response.statusCode = 404;
    response.end("404");
});
http.createServer(app).listen(3000);
Copy the code

In addition to adding the middleware mentioned above, the next three app.get functions are a powerful routing system in Express. They use app.post to respond to all network requests such as a POST or PUT. The first argument to the function is a path, such as /about or /weather or simply the root directory /, and the second argument is a request handler. This handler works just like the previous middleware, the only difference being the timing of the call.

In addition to the fixed routing form, it can also match more complex routes (using re, etc.) :

// Specify "hello" as the fixed part of the route
app.get("/hello/:who".function(request, response) {
    // who is not fixed, it represents the name passed in the URL
    response.end("Hello, " + request.params.who + ".");
   
});
Copy the code

Restart services and access localhost: 3000 / hello/earth until the response information is:

Hello, earth

Notice if you are behind the URL insert multiple /, for example: localhost: 3000 / hello / – / earth will return a 404 error.

You’ve probably seen this kind of URL link in your daily life, where a particular user can access a particular URL. For example, if a user is ExpressSuperHero, his profile page URL might be:

Mywebsite.com/users/Expre…

In Express you can simplify route definitions in this way without having to list all users’ specific routes.

The official documentation also shows an example of using regular expressions for complex matching, and you can do a lot more with routing. However, this chapter only needs to know routing concepts, and more will be covered in chapter 5.

Extend request and Response

Express extends the functionality of request and Response objects. You can find all the details in the official documentation, but here’s a look at some of them:

Redirect is a great feature of Express and can be used as follows:

response.redirect("/hello/world");
response.redirect("http://expressjs.com");
Copy the code

There are no redirect methods in native Nodes. Although we could also use native code to implement redirection, it would obviously be more code intensive.

In addition, sending files in Express is much easier with just one line of code:

response.sendFile("path/to/cool_song.mp3")
Copy the code

As before, the native implementation code for this feature is complex.

In addition to extending the response object, Express also extends the request object. For example, you can get the IP address of the machine that sent the request from Request. IP or the HTTP header from request.get.

Here we use it to achieve the IP blacklist function, the code is as follows:

var express = require("express");
var app = express();

var EVIL_IP = "123.45.67.89";

app.use(function(request, response, next) {
    if (request.ip === EVIL_IP) {
        response.status(401).send("Not allowed!");
    } else{ next(); }}); .Copy the code

Req.ip is used as well as res.status() and res.send(), all of which are extensions of Express.

In theory, all we need to know is that Express extends request and Response and how to use it, but we don’t need to know the details.

The examples above are just the tip of the iceberg for all Express extensions, and you can see more examples in the documentation.

view

Almost all web content is presented in HTML, and most of the HTML content is dynamically generated. You may need to provide a specific welcome page for the current logged-in user or you may need to dynamically generate tables within the page. To cope with dynamic content rendering, there are a number of Express template engines in the community, such as EJS, Handlebars, Pug.

Here is an example of using the EJS template engine:

var express = require("express");
var path = require("path");
var app = express();

// Tell Express that your view exists in a folder called views
app.set("views", path.resolve(__dirname, "views"));

// Tell Express that you will use the EJS template engine
app.set("view engine"."ejs");
Copy the code

In the code, first we import the necessary modules. Then set the path to the view file. Next, we set the template engine to EJS (Documents). Of course, in EJS, we also need to install by NPM install EJS –save command.

Once the EJS engine is installed and set up, it’s a matter of how to use it.

First, we create an index.ejs file under the Views folder and copy the following contents:


      
<html>
<head>
    <meta charset="utf-8">
    <title>Hello, world!</title>
</head>
<body>
    <% = message% >
</body>
</html>
Copy the code

EJS is essentially a superset of HTML, and all HTML syntax is directly usable and fully compatible. But EJS extends the syntax partially. For example, you can use the <%= message %> syntax to insert the passed argument message into the tag.

app.get("/".function(request, response) {
    response.render("index", {
        message: "Hey everyone! This is my webpage."
    });
});
Copy the code

Express adds a method called Render to the Response object. This method looks for the template view file corresponding to the first parameter in the view directory and passes the second parameter to the template file.

The following is the content of the HTML file dynamically generated by engine rendering:


      
<html>
<head>
    <meta charset="utf-8">
    <title>Hello, world!</title>
</head>
<body>
    Hey everyone! This is my webpage. 
</body>
</html>
Copy the code

Example: an implementation of a message board

In the final section, we will use the previous techniques to build a complete message board Web application. To illustrate the above, the application consists of two main pages:

  1. A home page: mainly used to list all previous comments
  2. An edit page: for editing new messages

The preparatory work

First, we create a new folder, create a new project, and copy the following into the new package.json file:

{
    "name": "express-guestbook"."private": true."scripts": {
        "start": "node app"}}Copy the code

You can add additional field information (such as author or version) to the file, but this is not necessary in this case. Next, we install the dependency files by typing:

npm install express morgan body-parser ejs –save

POST requests need to be parsed using body-parser because the message creation action is required.

The core code

With that done, create the app.js file and copy the following code:


var http = require("http");
var path = require("path");
var express = require("express");
var logger = require('morgan');
var bodyParser = require("body-parser");

var app = express();

// Set the engine
app.set("views", path.resolve(__dirname, "views"));
app.set("view engine"."ejs");

// Set the global variable of the message
var entries = [];
app.locals.entries = entries;

// Use Morgan for logging
app.use(logger("dev"));

// The middleware that sets the user's form submission action information. All information is stored in req.body
app.use(bodyParser.urlencoded({ extended: false }));

// Render the main page (in views/index.ejs) when visiting the root directory of the website.
app.get("/".function(request, response) {
    response.render("index");
});

// Render the "New Message" page (at views/index.ejs) when get accesses this URL
app.get("/new-entry".function(request, response) {
    response.render("new-entry");
});

// POST action To process routes for creating messages
app.post("/new-entry".function(request, response) {
    // If the user submits a form with no title or content, an error of 400 is returned
    if(! request.body.title || ! request.body.body) { response.status(400).send("Entries must have a title and a body.");
        return;
    }
    
    // Add new messages to entries
    entries.push({
        title: request.body.title,
        content: request.body.body,
        published: new Date()});// Redirect to the home page to view your new entry
    response.redirect("/");
});

Render a 404 page because you requested an unknown resource
app.use(function(request, response) {
    response.status(404).render("404");
});

// Start the server on port 3000
http.createServer(app).listen(3000.function() {
    console.log("Guestbook app started on port 3000.");
});
Copy the code

The new view

Finally, we need to complete the view file of the page, create a new views folder, and copy the following contents into the new header.ejs file:


      
<html>
<head>
<meta charset="utf-8">
<title>Express Guestbook</title>
<link rel="stylesheet" href="/ / maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
</head>
<body class="container">
    <h1>
        Express Guestbook
        <a href="/new-entry" class="btn btn-primary pull-right">
            Write in the guestbook
        </a>
    </h1>
    
Copy the code

Twitter’s Bootstrap framework is used here, but you can make any substitution you want. Most importantly, this file serves as a common header for all pages.

Next, create footer.ejs as a generic footer in the same directory:

</body>
</html>
Copy the code

With the general section complete, the index, new-entry, and 404 page files are next. Copy the following code into the file views/index.ejs:

<% include header% >
<% if (entries.length) { %>
    <% entries.forEach(function(entry) { %>
        <div class="panel panel-default">
            <div class="panel-heading">
                <div class="text-muted pull-right">
                    <% = entry.published% >
                </div>
                <% = entry.title% >
             </div>
             <div class="panel-body">
                <% = entry.content% >
             </div>
         </div>
     <%}) % >
<% } else { %>
    No entries! <a href="/new-entry">Add one!</a>
<%} % >
<% include footer% >
Copy the code

Also copy the following code into views/new-entry.ejs

<% include header% >
<h2>Write a new entry</h2>
<form method="post" role="form">
    <div class="form-group">
        <label for="title">Title</label>
        <input type="text" class="form-control" id="title" name="title" placeholder="Entry title" required>
    </div>
    <div class="form-group">
        <label for="content">Entry text</label>
        <textarea class="form-control" id="body" name="body" placeholder="Love Express! It's a great tool for building websites." rows="3" required></textarea>
    </div>
    <div class="form-group">
        <input type="submit" value="Post entry" class="btn btn-primary">
    </div>
</form>
<% include footer% >
Copy the code

Finally, there is the views/404.ejs file:

<% include header% >
<h2>404! Page not found.</h2>
<% include footer% >
Copy the code

With all the view files created, it’s time to run the service.

Run the service

If you use NPM start to pull up the service now and access the corresponding URL, you should see the scenario shown in the following figure.

Finally, let’s review a few key points of this small project:

  • A middleware was used to log all requests and 404 page responses were made to urls that did not match.
  • After creating a message, we redirect the page to the home page.
  • In this project, EJS is used as the template engine of Express. And use it to achieve dynamic rendering of HTML files.

conclusion

  • Express is based on Node for engineering expansion, making the development process more smooth and efficient.
  • Express consists of four main parts.
  • Express request processing flows can be built from multiple middleware.
  • The popular template engine in Express is EJS, which enables dynamic rendering of HTML and is syntactically friendlier.

The original address