Node.js has become a force that cannot be ignored in the Web background development circle. With its good asynchronous performance, rich NPM library and JavaScript language advantages, node.js has become one of the important technologies for many large companies to develop their background architecture. The Express framework is the best known and most popular of the backend development frameworks. In this tutorial, you’ll learn how Express encapsulates Node’s built-in HTTP module, grasp the key concepts of routing and middleware, learn and use the template engine, static file service, error handling, and JSON API, and ultimately develop a simple resume site.

This tutorial is part of the Node.js backend engineer learning path, welcome to Star wave, encourage us to continue to create better tutorials, update.

Back in the day: Implement a server with built-in HTTP modules

Since Ryan Dahl launched the Node.js platform in JSConf in 2009, the adoption of the technology has rocketed to become one of the most popular back-end development platforms, with Express being the most prominent Web framework. Before we begin this tutorial, we will outline the preparation, techniques, and learning objectives required for this tutorial.

Preliminary knowledge

This tutorial assumes that you already know:

  • JavaScript language basics (including some common ES6+ syntax)
  • Learn the basics of Node.js, especially asynchronous programming (this tutorial focuses on callback functions) and the Node module mechanism, as well as the basic use of NPM
  • HTTP protocol basics, how do browsers and servers interact

The technology used

  • Node.js: 8.x and above
  • NPM: 6.x and above
  • Express. Js: 4. X

Learning goals

After reading this tutorial, you will have learned

  • The Express framework has two core concepts: routing and middleware
  • Speed up development iterations with Nodemon
  • Render the page using a template engine and plug it into the Express framework
  • Use Express’s static file service
  • Write custom error handlers
  • Implement a simple JSON API port
  • Modularization is realized by splitting logic of subroutes

Pay attention to

While databases are an important part of back-end development, Express does not have built-in modules for handling databases and requires additional third-party libraries to support it. This tutorial focuses on express-related concepts and therefore does not cover database development. After learning this tutorial, you can browse the advanced Express tutorials.

Create a server with the built-in HTTP module

Before looking at Express, let’s look at how to implement a server using node.js’s built-in HTTP module to better understand how Express abstracts and encapsulates the underlying Node code. If you haven’t already installed Node.js, download and install it from the official website.

We will implement a resume website. Create a folder express_resume and enter it:

mkdir express_resume && cd express_resume
Copy the code

Create the server.js file as follows:

const http = require('http');

const hostname = 'localhost';
const port = 3000;

const server = http.createServer((req, res) = > {
  res.statusCode = 200;
  res.setHeader('Content-Type'.'text/html');
  res.end('Hello World\n');
});

server.listen(port, () => {
  console.log(`Server running at http://${hostname}:${port}/ `);
});
Copy the code

If you’re familiar with Node.js, the code above is clear:

  1. Importing HTTP Modules
  2. Specifies the hostname of the serverhostnameAnd port numberport
  3. withhttp.createServerCreate an HTTP server that takes a callback function and accepts a request objectreqAnd the response objectres, and writes the response content (status code 200, type HTML document, content isHello World)
  4. Starts the server on the specified port

Finally, run server.js:

node server.js
Copy the code

Open localhost:3000 in your browser and you will see the prompt “Hello World” :

It can be seen that directly using the built-in HTTP module to develop the server has the following obvious disadvantages:

  • There is a lot of low-level code to write — such as manually specifying HTTP status codes and header fields, and eventually returning content. If we need to develop more complex functions involving multiple status codes and header information (such as user authentication), this manual management mode is very inconvenient
  • There is no special routing mechanismRouting is one of the most important functions of the server. It can be used to return the corresponding content according to the different REQUEST URL and HTTP method of the client. But this code can only be used inhttp.createServerBy checking the requestreqThe content of the routing function can be realized, when building large applications

This leads to Express’s two major encapsulation and improvements to built-in HTTP:

  • More powerful Request and Response objects, with many utility methods added
  • Flexible and convenient route definition and parsing, can be very convenient for code splitting

Next, we’ll start developing Web servers with Express!

New age: Build servers with Express

In the first step, we put the server in a JS file, which is a Node module. From now on, we will turn this project into an NPM project. Enter the following command to create the NPM project:

npm init
Copy the code

You can then press enter (or fill it in carefully) and see that the package.json file has been created. Then add Express project dependencies:

npm install express
Copy the code

Before we start rewriting the above server with Express, let’s take a look at the two major encapsulation and improvements mentioned above.

More powerful Request and Response objects

The first is the Request object, which is usually represented by the REQ variable. Here are some of the most important members of reQ (it’s okay if you don’t know what they are) :

  • req.body: Data in the body of the client request, possibly form or JSON data
  • req.params: Path parameter in the request URI
  • req.query: Query parameters in the request URI
  • req.cookies: Cookies on the client

Then there is the Response Response object, usually represented by the RES variable, which can perform a series of Response operations, such as:

// Send a string of HTML code
res.send('HTML String');

// Send a file
res.sendFile('file.zip');

// Render a template engine and send
res.render('index');
Copy the code

The operations on the Response object are very rich and can also be chain-called:

// Set the status code to 404 and return the Page Not Found string
res.status(404).send('Page Not Found');
Copy the code

prompt

We didn’t enumerate all of the Request and Response apis here, because the idea of the Tuq community is to learn and deepen your understanding from the real world, and to avoid boring API memorization!

Routing mechanism

When the client (Web front-end, mobile, and so on) makes a request to the server, it contains two elements: the path (URI) and the HTTP request method (GET, POST, and so on). Together, the path and request methods are commonly referred to as API endpoints. The mechanism by which the server selects the appropriate processing logic based on the endpoint accessed by the client is called routing.

In Express, you can define a route simply as follows:

app.METHOD(PATH, HANDLER)
Copy the code

Among them:

  • appIs aexpressServer object
  • METHODIt could be anythinglowercaseHTTP request methods, includingget,post,put,delete, etc.
  • PATHIs the URI accessed by the client, for example//about
  • HANDLERIs the callback function when the route is triggered. The corresponding service logic can be executed in the function

Nodemon accelerates development

Nodemon is a popular development server that detects changes in workspace code and automatically restarts. Run the following command to install Nodemon:

npm install nodemon --save-dev
Copy the code

Here we install Nodemon as a development dependency on devDependencies because it is only needed for development. At the same time, we add the start command to package.json as follows:

{
  "name": "express_resume"."version": "1.0.0"."description": ""."main": "index.js"."scripts": {
    "start": "nodemon server.js"."test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": ""."license": "ISC"."dependencies": {
    "express": "^ 4.17.1"
  },
  "devDependencies": {
    "nodemon": "^ 2.0.2"}}Copy the code

Formal implementation

When it’s time to do it, let’s rewrite the above server with Express as follows:

const express = require('express');

const hostname = 'localhost';
const port = 3000;

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

app.listen(port, () => {
  console.log(`Server running at http://${hostname}:${port}/ `);
});
Copy the code

In the above code, we first create an Express server object with the Express () function, then define the home/route with the route definition method app.get mentioned above, and finally call listen to start the server as well.

From this step, we run the NPM start command to start the server and see the same Hello World content, but the code is much simpler.

prompt

After running NPM start, you can leave the server open, edit and save the code, and Nodemon will automatically restart the server and run the latest code.

Write your first middleware

Let’s move on to the second important concept of Express: Middleware.

Understanding middleware

Middleware is not a concept unique to Express. Rather, it is a widely used software engineering concept (even extending to other industries) that refers to components that decouple concrete business logic from underlying logic (see this discussion). In other words, middleware is reusable code that can be used in multiple application scenarios.

A simplified version of Express’s middleware flow is shown below:

First the client initiates a request to the server, then the server executes each middleware in turn, finally reaching the route and choosing the appropriate logic to execute.

prompt

This is a simplified version of the process description intended to give you an overview of the middleware, and we will refine this process in later chapters.

Two points in particular need to be noted:

  • Middleware executes sequentially, so that order is very important when configuring middleware
  • The middleware can either pass the request to the next middleware while executing the internal logic or return the user response directly

Definition of Express middleware

In Express, middleware is a function:

function someMiddleware(req, res, next) {
  // Custom logic
  next();
}
Copy the code

Of the three parameters, req and RES are the previously mentioned Request object and Response object. The next function is used to trigger the next middleware execution.

Pay attention to

If you forget to call the next function in the middleware and do not return a response directly, the server will get stuck in the middleware and not continue execution.

There are two ways to use middleware in Express: global middleware and routing middleware.

Global middleware

The app.use function is used to register the middleware, and the middleware will execute any request the user makes, for example:

app.use(someMiddleware);
Copy the code

Routing middleware

By registering middleware at route definition time, this middleware is executed only when the user accesses the URI corresponding to the route, for example:

app.get('/middleware', someMiddleware, (req, res) => {
  res.send('Hello World');
});
Copy the code

The someMiddleware middleware defined is triggered only when the user accesses/Middleware, not when the user accesses other paths.

Writing middleware

Let’s start implementing our first Express middleware. It simply prints the client’s access time, HTTP request methods, and URIs on a terminal called loggingMiddleware. The code is as follows:

// ...

const app = express();

function loggingMiddleware(req, res, next) {
  const time = new Date(a);console.log(` [${time.toLocaleString()}] ${req.method} ${req.url}`);
  next();
}

app.use(loggingMiddleware);

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

// ...
Copy the code

Pay attention to

It is bad practice to write console.log statements in middleware, because console.log (including other synchronized code) blocks the node.js asynchronous event loop, reducing server throughput. In actual production, it is recommended to use excellent third-party logging middleware, such as Morgan, Winston, and so on.

Run the server, then use your browser to try to access each path. Here I go to the home page (localhost:3000) and /hello (localhost:3000/hello, the browser should see 404) and see the console output:

[11/28/2019, 3:54:05 PM] GET /
[11/28/2019, 3:54:11 PM] GET /hello
Copy the code

To give you an initial understanding of the concept of middleware, we have only implemented a very simple middleware. In fact, the middleware can not only read individual properties on the REQ object, but also add new properties or modify existing properties (which can be acquired by later middleware and routing functions), making it easy to implement some complex business logic (such as user authentication).

Render the page with a template engine

Finally, our site will start showing some real content. Express provides excellent support for today’s major template engines (Pug, Handlebars, EJS, etc.) and allows for two lines of code access.

prompt

If you don’t know about templating engines, don’t worry, this tutorial hardly needs any of its advanced features, you just need to understand it as an “upgraded HTML document.”

This tutorial will use Handlebars as the template engine. First add the NPM package:

npm install hbs
Copy the code

Create the Views folder to hold all the templates. Then create the home page template index.hbs as follows:

<h1>Personal resume</h1>
<p>I am a little sparrow, eager to learn skills, practical skills.</p>
<a href="/contact">contact</a>
Copy the code

Create the contact page template contact.hbs as follows:

<h1>contact</h1>
<p>QQ: 1234567</p>
<p>Wechat: A map sparrow</p>
<p>Email address: [email protected]</p>
Copy the code

Finally, configure and use templates in server.js. The code to configure the template is very simple:

// Specify the directory to store the template
app.set('views'.'/path/to/templates');

// Specify the template engine as Handlebars
app.set('view engine'.'hbs');
Copy the code

To use a template, simply call the res.render method in the routing function:

// Render a template named hello.hbs
res.render('hello');
Copy the code

The modified server.js code is as follows:

// ...

const app = express();

app.set('views'.'views');
app.set('view engine'.'hbs');

// Define and use loggingMiddleware...

app.get('/', (req, res) => {
  res.render('index');
});

app.get('/contact', (req, res) => {
  res.render('contact');
})

// ...
Copy the code

Notice that in the code above, we added the route definition for GET/Contact.

Finally, we run the server again, visit our home page and see:

Click “Contact Information” to jump to the corresponding page:

Add a static file service

Generally, websites need to provide static file services, such as images, CSS files, JS files, etc. Express has its own static file service middleware express.static, which is very convenient to use.

For example, we add static file middleware as follows and specify static resource root as public:

// ...

app.use(express.static('public'));

app.get('/', (req, res) => {
  res.render('index');
});

// ...
Copy the code

Suppose the project’s public directory has these static files:

Public │ ├─ ├─ class.sci-tech │ ├─ sci-tech │ ├─ sci-tech │ ├─ sci-tech │ ├─ sci-tech │ ├─ sci-tech │ ├─ sci-tech │Copy the code

Can be accessed through the following paths:

http://localhost:3000/css/style.css
http://localhost:3000/img/tuture-logo.png
Copy the code

Style file public/ CSS /style.css code is as follows (directly copy and paste) :

body {
  text-align: center;
}

h1 {
  color: blue;
}

img {
  border: 1px dashed grey;
}

a {
  color: blueviolet;
}
Copy the code

The image file can be downloaded via the link on GitHub and then to the public/img directory. Of course, you can also use your own image, remember to replace the corresponding link in the template.

Add CSS stylesheets and images to the home page template views/index.hbs:

<link rel="stylesheet" href="/css/style.css" />

<h1>Personal resume</h1>
<img src="/img/tuture-logo.png" alt="Logo" />
<p>I am a little sparrow, eager to learn skills, practical skills.</p>
<a href="/contact">contact</a>
Copy the code

Add a style sheet to the contact template views/contact.hbs:

<link rel="stylesheet" href="/css/style.css" />

<h1>contact</h1>
<p>QQ: 1234567</p>
<p>Wechat: A map sparrow</p>
<p>Email address: [email protected]</p>
Copy the code

Run the server again and visit our website. The home page is as follows:

Contact us page below:

You can see that the stylesheet and image have loaded successfully!

Handle 404 and server errors

People have joys and sorrows, the moon waxes and wanes, the server also has errors. HTTP errors generally fall into two categories:

  • Errors on the client side (status code 4XX), such as accessing a page that does not exist (404), insufficient permissions (403), and so on
  • Server side error (status code 5XX), such as server internal error (500) or gateway error (503) and so on

If you open the server and go to a non-existent path, such as localhost:3000/what, this page will appear:

Obviously, the user experience is bad.

In this section, we’ll show you how to handle 404 (page does not exist) and 500 (server internal error) in the Express framework. Before that, we need to improve the operation flow of the Express middleware, as shown in the following figure:

There are two important differences between this diagram and the previous one:

  • Each route definition is essentially a middleware (or, more accurately, a middleware container that can contain multiple middleware) that returns a response when a URI match is successful and continues to execute the next route when the match fails
  • Each middleware (including routing) can not only be callednextThe function passes down, returns the response directly, and so onAn exception is thrown

From this figure, it is clear how to implement 404 and server error handling:

  • For 404, just add a middleware after all the routes to receive the request that all the routes failed to match
  • For error handling, all previous middleware exceptions go into error handling functions, which can be used with Express or customized.

Handle 404

In Express, access to non-existent paths can be handled through middleware:

app.use(The '*', (req, res) => {
  // ...
});
Copy the code

* matches any path. Placing this middleware behind all routes catches a request for which all access paths failed to match.

Handling internal errors

Express already has error handling, so let’s try it out. Add the following “broken” route to server.js (to simulate a real world error) :

app.get('/broken', (req, res) => {
  throw new Error('Broken! ');
});
Copy the code

Then start the server and go to localhost:3000/broken:

Danger!

The server simply returned the error call stack! Clearly, returning such a call stack to the user is not only a bad experience, but greatly increases the risk of attack.

In fact, the default error handling mechanism for Express can be toggled by setting NODE_ENV. We set it to production and start the server. If you are in a Git Bash environment on Linux, macOS, or Windows, you can run the following command:

NODE_ENV=production node server.js
Copy the code

If you are on the command line in Windows, run the following command:

set NODE_ENV=production
node server.js
Copy the code

A call to localhost:3000/broken returns an Internal Server Error with no Error message:

The experience is still poor, and ideally a friendly custom page can be returned. This can be addressed through Express’s custom error handlers, which take the form:

function (err, req, res, next) {
  // Handle error logic
}
Copy the code

Compared to normal middleware functions, the first parameter, err exception object, is added.

Implement custom processing logic

It’s easy to implement custom 404 and error handling logic. Add the following code to all routes in server.js:

// Middleware and other routing...

app.use(The '*', (req, res) => {
  res.status(404).render('404', { url: req.originalUrl });
});

app.use((err, req, res, next) = > {
  console.error(err.stack);
  res.status(500).render('500');
});

app.listen(port, () => {
  console.log(`Server running at http://${hostname}:${port}/ `);
});
Copy the code

prompt

When writing the logic to handle 404, we used variable interpolation in the template engine. In particular, the res.render method takes the data that needs to be passed to the template as a second argument (e.g. here {URL: req.originalURL} is passed to the user’s access path), and the data can be retrieved from the template with {{url}}.

The template code for 404 and 500 is as follows:

<link rel="stylesheet" href="/css/style.css" />

<h1>Can't find the page you want!</h1>
<p>The path you are accessing{{ url }}There is no</p>
Copy the code
<link rel="stylesheet" href="/css/style.css" />

<h1>The server seems to have wandered off</h1>
<p>Try again later! See your later~</p>
Copy the code

Run the server again to access a path that does not exist:

Access localhost: 3000 / broken:

The experience is very good!

Three lines of code implement the JSON API

At the end of this tutorial, we’ll implement a very simple JSON API. If you’ve had any experience with other back-end API development (especially Java), implementing a JSON API port in Express is incredibly simple. In the Response object mentioned earlier, Express wraps a JSON method for us to directly return a JavaScript object as JSON data, for example:

res.json({ name: 'Million dollar salary'.price: 996 });
Copy the code

Returns JSON data {“name”: “million annual salary “, “price”: 996}, and the default status code is 200. We can also specify a status code, for example:

res.status(502).json({ error: 'The company is closed.' });
Copy the code

Returns JSON data {“error”: “the company is closed “} with the status code 502.

To get started, let’s add a simple JSON API port/API to server.js that returns some data about the Tufinch community:

// ...

app.get('/api', (req, res) => {
  res.json({ name: 'The Tooquine Community'.website: 'https://tuture.co' });
});

app.get('/broken', (req, res) => {
  throw new Error('Broken! ');
});

// ...
Copy the code

We can use a browser to access the localhost:3000/ API port and see that the desired data is returned:

Or you can use Postman or Curl to access the data you want.

Use child routing split logic

As our site got bigger and bigger, it wasn’t a good idea to put all the code in server.js. “Split logic” (or “modularization”) is the most common approach, and in Express we can do this by subrouting the Router.

const express = require('express');
const router = express.Router();
Copy the code

Express. Router can be thought of as a mini app object, but it is fully functional and also supports registering middleware and routing:

// Register a middleware
router.use(someMiddleware);

// Add a route
router.get('/hello', helloHandler);
router.post('/world', worldHandler);
Copy the code

Finally, due to the idea of “everything is middleware” in Express, a Router is added to the app as middleware:

app.use('/say', router);
Copy the code

Add all routes to the router under /say.

app.get('/say/hello', helloHandler);
app.post('/say/world', worldHandler);
Copy the code

Formal implementation

To get started, first create the Routes directory to hold all the child routes. Create the routes/index.js file as follows:

const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
  res.render('index');
});

router.get('/contact', (req, res) => {
  res.render('contact');
});

module.exports = router;
Copy the code

Create routes/api.js with the following code:

const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
  res.json({ name: 'The Tooquine Community'.website: 'https://tuture.co' });
});

router.post('/new', (req, res) => {
  res.status(201).json({ msg: 'A new chapter is about to begin.' });
});

module.exports = router;
Copy the code

Finally, we delete all the old route definitions in server.js and replace them with the two routers we just implemented as follows:

const express = require('express');
const path = require('path');

const indexRouter = require('./routes/index');
const apiRouter = require('./routes/api');

const hostname = 'localhost';
const port = 3000;

const app = express();

// ...
app.use(express.static('public'));

app.use('/', indexRouter);
app.use('/api', apiRouter);

app.use(The '*', (req, res) => {
  res.status(404).render('404', { url: req.originalUrl });
});

// ...
Copy the code

Is not an instant refreshing a lot of it! If your server is still open, you can test to see if the previous route still works. Using Curl to test/API routing

$ curl localhost:3000/api
{"name":"The Tooquine Community"."website":"https://tuture.co"}
$ curl -X POST localhost:3000/api/new
{"msg":"A new chapter begins."}
Copy the code

This concludes the tutorial. The finished site is certainly simple, but hopefully you’ll learn two of the best things about Express: routing and middleware. After mastering these two concepts, the subsequent advanced tutorial will be much easier to learn!

Want to learn more exciting practical skills tutorial? Come and visit the Tooquine community.