Getting started with Express

Express is perhaps the most popular Node.js web app framework. It's so popular that it's part of the MEAN Stack acronym. MEAN refers to MongoDB, ExpressJS, AngularJS, and Node.js. Express is described as being Sinatra-like, referring to a popular Ruby application framework, and that it isn't very opinionated. This means Express is not at all strict about how your code is structured; you just write it the way you think is best.

You can visit the home page for Express at http://expressjs.com/.

Shortly, we'll implement a simple application to calculate Fibonacci numbers using Express, and in later chapters, we'll do quite a bit more with Express. We'll also explore how to mitigate the performance problems from computationally intensive code we discussed earlier.

Let's start by installing the Express Generator. While we can just start writing some code, the Express Generator gives a blank starting application. We'll take that and modify it.

Install it using following commands:

$ mkdir fibonacci
$ cd fibonacci
$ npm install [email protected]

This is different from the suggested installation method on the Express website, which was to use the -g tag for a global install. We're also using an explicit version number to ensure compatibility.

Earlier, we discussed how many now recommend against installing modules globally. In the Twelve-Factor model, it's strongly recommended to not install global dependencies, and that's what we're doing.

The result is that an express command is installed in the ./node_modules/.bin directory:

$ ls node_modules/.bin/
express 

To run the command:

$ ./node_modules/.bin/express --help

  Usage: express [options] [dir]

  Options:

    -h, --help          output usage information
    -V, --version       output the version number
    -e, --ejs           add ejs engine support (defaults to jade)
        --hbs           add handlebars engine support
    -H, --hogan         add hogan.js engine support
    -c, --css <engine>  add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
        --git           add .gitignore
    -f, --force         force on non-empty directory

We probably don't want to type ./node_modules/.bin/express every time we run the Express Generator application, or, for that matter, any of the other applications that provide command-line utilities. Fortunately, modifying the PATH environment variable is an easy solution:

$ export PATH=node_modules/.bin:${PATH}

This works for a bash shell. But, for csh users, try this:

$ setenv PATH node_modules/.bin:${PATH}

With the PATH environment variable set like this, the express command can be directly executed. Let's now use it to generate a blank starter application.

Now that you've installed express-generator in the fibonacci directory, use it to set up the blank framework application:

Getting started with Express

This created for us a bunch of files which we'll walk through in a minute. The node_modules directory still has the express-generator module, which is now not useful. We can just leave it there and ignore it, or we can add it to the devDependencies of the package.json it generated. Alternatively, we can uninstall it as shown in the preceding screenshot.

The next thing to do is run the blank application in the way we're told. The command shown, npm start, relies on a section of the supplied package.json file:

"scripts": {
    "start": "node ./bin/www"
},

The npm tool supports scripts that are ways to automate various tasks. We'll use this capability throughout the book to do various things. Most npm scripts are run with the npm run scriptName command, but the start command is explicitly recognized by npm and can be run as shown previously.

The first step is to install the dependencies (npm install), then to start the application:

Getting started with Express

We can modify the supplied npm start script to print debugging information on the screen. Change the scripts section to the following:

"scripts": {
    "start": "DEBUG=fibonacci:* node ./bin/www"
},

Adding DEBUG=fibonacci:* this way sets an environment variable, DEBUG, as shown in the screenshot. In Express applications, the DEBUG variable turns on debugging code, which prints an execution trace that's useful for debugging.

Since the output says it is listening on port 3000, we direct our browser to http://localhost:3000/ and see the following output:

Getting started with Express

Walking through the default Express application

We have a working blank Express application; let's look at what was generated for us. We're doing this to familiarize ourselves with Express before diving in to start coding our Fibonacci application.

Because we used the --ejs option, this application is set up to use the EJS template engine. The template engine chosen assists with generating HTML. EJS is documented at http://ejs.co/.

The views directory contains two files, error.ejs and index.ejs. These are both EJS templates that we'll modify later.

The routes directory contains the initial routing setup, that is, the code to handle specific URLs. We'll modify these later.

The public directory will contain assets that the application doesn't generate, but are simply sent to the browser. What's initially installed is a CSS file, public/stylesheets/style.css.

The package.json file contains our dependencies and other metadata.

The bin directory contains the www script that we saw earlier. That's a Node.js script, which initializes the HTTPServer objects, starts it listening on a TCP port, and calls the last file we'll discuss, app.js. These scripts initialize Express, hook up the routing modules, and do other things.

There's a lot going on in the www and app.js scripts, so let's start with the application initialization. Let's first take a look at a couple of lines in app.js:

var express = require('express');
…
var app = express();
…
module.exports = app;

This means that app.js is a module that exports the object returned by the express module.

Now, let's turn to the www script. The first thing to see is it starts with this line:

#!/usr/bin/env node

This is a Unix/Linux technique to make a command script. It says to run the following as a script using the node command.

It calls the app.js module as follows:

var app = require('../app');
…
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
…
var server = http.createServer(app);
…
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

We see where port 3000 comes from; it's a parameter to the normalizePort function. We also see that setting the PORT environment variable will override the default port 3000. Try running the following command:

$ PORT=4242 DEBUG=fibonacci:* npm start

The application now tells you that it's listening on port 4242, where you can ponder the meaning of life.

The app object is next passed to http.createServer(). A look in the Node.js documentation tells us this function takes a requestListener, which is simply a function that takes the request and response objects we've seen previously. This tells us the app object is such a function.

Finally, the www script starts the server listening on the port we specified.

Let's now walk through app.js in more detail:

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

This tells Express to look for templates in the views directory and to use the EJS templating engine.

The app.set function is used for setting application properties. It'll be useful to browse the API documentation as we go through (http://expressjs.com/en/4x/api.html).

Next is a series of app.use calls:

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);

The app.use function mounts middleware functions. This is an important piece of Express jargon we will discuss shortly. At the moment, let's say that middleware functions are executed during the processing of routes. This means all the features named here are enabled in app.js:

The Express middleware

Let's round out the walkthrough of app.js by discussing what middleware functions do for our application. We have an example at the end of the script:

app.use(function(req, res, next) {
  var err = new Error('Not found');
  err.status = 404;
  next(err);
});

The comment says catch 404 and forward to error handler. As you probably know, an HTTP 404 status means the requested resource was not found. We need to tell the user their request wasn't satisfied, and this is the first step in doing so. Before getting to the last step of reporting this error, you must learn how middleware works.

We do have a middleware function right in front of us. Refer to its documentation at http://expressjs.com/en/guide/writing-middleware.html.

Middleware functions take three arguments. The first two, request and response, are equivalent to the request and response of the Node.js HTTP request object. However, Express expands the objects with additional data and capabilities. The last, next, is a callback function controlling when the request-response cycle ends, and it can be used to send errors down the middleware pipeline.

The incoming request gets handled by the first middleware function, then the next, then the next, and so on. Each time the request is to be passed down the chain of middleware functions, the next function is called. If next is called with an error object, as shown here, an error is being signaled. Otherwise, the control simply passes to the next middleware function in the chain.

What happens if next is not called? The HTTP request will hang because no response has been given. A middleware function gives a response when it calls functions on the response object, such as res.send or res.render.

For example, consider the inclusion of app.js:

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

This does not call next, but instead calls res.send. This is the correct method of ending the request-response cycle, by sending a response (res.send) to the request. If neither next nor res.send is called, the request never gets a response.

Hence, a middleware function does one of the following four things:

  • Executes its own business logic. The request logger middleware shown earlier is an example.
  • Modifies the request or response objects. Both the body-parser and cookie-parser do so, looking for data to add to the request object.
  • Calls next to proceed to the next middleware function or else signals an error.
  • Sends a response, ending the cycle.

The ordering of middleware execution depends on the order they're added to the app object. The first added is executed first, and so on.

Middleware and request paths

We've seen two kinds of middleware functions so far. In one, the first argument is the handler function. In the other, the first argument is a string containing a URL snippet, and the second argument is the handler function.

What's actually going on is app.use has an optional first argument the path the middleware is mounted on. The path is a pattern match against the request URL, and the given function is triggered if the URL matches the pattern. There's even a method to supply named parameters in the URL:

app.use('/user/profile/:id', function(req, res, next) {
    userProfiles.lookup(req.params.id, (err, profile) => {
          if (err) return next(err);
          // do something with the profile
          // Such as display it to the user
          res.send(profile.display());
    });
});

This path specification has a pattern, :id, and the value will land in req.params.id. In this example, we're suggesting a user profiles service, and that for this URL we want to display information about the named user.

Another way to use a middleware function is on a specific HTTP request method. With app.use, any request will be matched, but in truth, GET requests are supposed to behave differently than POST requests. You call app.METHOD where METHOD matches one of the HTTP request verbs. That is, app.get matches the GET method, app.post matches POST, and so on.

Finally, we get to the router object. This is a kind of middleware used explicitly for routing requests based on their URL. Take a look at routes/users.js:

var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});
module.exports = router;

We have a module whose exports object is a router. This router has only one route, but it can have any number of routes you think is appropriate.

Back in app.js, this is added as follows:

app.use('/users', users);

All the functions we discussed for the app object apply to the router object. If the request matches, the router is given the request for its own chain of processing functions. An important detail is that the request URL prefix is stripped when the request is passed to the router instance.

You'll notice that the router.get in users.js matches '/' and that this router is mounted on '/users'. In effect that router.get matches /users as well, but because the prefix was stripped, it specifies '/' instead. This means a router can be mounted on different path prefixes, without having to change the router implementation.

Error handling

Now, we can finally get back to the generated app.js, the 404 error (page not found), and any other errors the application might want to show to the user.

A middleware function indicates an error by passing a value to the next function call. Once Express sees an error, it will skip any remaining non-error routing, and it will only pass it to error handlers instead. An error handler function has a different signature than what we saw earlier.

In app.js that we're examining, this is our error handler:

app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

Error handler functions take four parameters, with err added to the familiar req, res, and next. For this handler, we use res.status to set the HTTP response status code, and we use res.render to format an HTML response using the views/error.ejs template. The res.render function takes data, rendering it with a template to produce HTML.

This means any error in our application will land here, bypassing any remaining middleware functions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset