官术网_书友最值得收藏!

Express routes

Express makes use of HTTP verbs and path patterns to provide a meaningful API for describing routes:

app.verb(path, [callback...], callback)

The route handlers take the same arguments (request, response, and next), and the path can be a string (that will be transformed into a regular expression) or a regular expression.

If we were to define a route handler to update an article, then the request method (verb) would be PUT, and the URL could look like /articles/211 (where the number represents the article ID). Here's how to do this with Express:

app.put('/articles/:id', function(req, res, next) {
  var id = req.params.id;

  console.log('Updating article ' + id);
  console.log('Attributes: ', req.body);
  // save to database
  // send message or render template
});

Specifying the path

The path parameter can be either a string (transformed into a regular expression internally by Express) or a regular expression, in case we need to match very specific patterns (such as an e-mail address or a number). Needless to say, the route handlers only get invoked when the path matches the regular expression.

When using the string version, we can specify placeholders using colons, and they get mapped to the req.params variable. If a question mark is added after a placeholder, it makes it optional.

Let's suppose that we want to create a blog application and we would like to display a post in different formats (HTML, JSON, RSS, or XML) based on the path ending. If the format is missing, the post should still be accessible and the default format would be HTML. The route should catch the following URLs:

  • /blog/routing-with-express.html
  • /blog/routing-with-express.json
  • /blog/command-line-node-apps.json
  • /blog/application-security

The path always starts with /blog, followed by a slug (a URL-friendly version of the post title usually) and an optional format at the end. The slug and format are dynamic, so we need to specify them using placeholders. Additionally, we'll have to add the question mark at the end, since the format is optional, resulting in the following path:

/blog/:slug.:format?

The dynamic variables (the slug and format, in this case) are accessible from within the route handler, so here's how the route handler should look:

app.get('/blog/:slug.:format?', function(req, res, next) {
  var format = req.params.format || 'html';
  var slug = req.params.slug;

  // query the database to find the blog post based on the slug
  database.findBySlug(slug, function(err, post) {
    if (err) { return next(err); }

    switch (format) {
      case 'html':
        /* render html */
        break;
      case 'json':
        res.json(post);
        break;
      default:
        // bad format
        next();
        break;
    }
  });
});

Note

The preceding example does not use the res.format() function provided by Express since the format is already included in the URL, and there's no need to perform an extra check for content-negotiation based on the Accept header.

The regular expressions converted from path strings can be inspected either by checking the app._router.stack variable (inside our application) or by creating a small script that uses the NPM module path-to-regexp to output the result.

In the following screenshot, we can see the regexp version of the path variable used in our previous example (for the sake of simplicity, the route handler only sends a basic message):

Reusable route handlers

There are a lot of situations where we need to plug in the same middleware for multiple routes to eliminate code duplication, such as requiring user authentication or loading database items based on path placeholders.

Let's think for a moment what routes we would have to make for a web application that publishes articles related to software. There will be two types of users that will interact with the application: guests and admins. The guests will read the articles and the admins will be able to do CRUD actions.

For both types of users, we will need to query the database for the article that's being read or updated, so our first middleware component will populate the req.article property with the result returned from the database. In case there's no such article found, the response status will be 404:

// simulate a database using an object
// the keys represent the ids of the articles
var articles = {
  'express-tutorial' : {
    title: 'Practical web apps with Express',
    content: 'Lean how to create web apps with Express'
  },
  'node-videos': {
    title: 'Node.js video tutorials',
    content: 'Practical Node tips!'
  }
};

var loadArticle = function(req, res, next) {
  // we assume that the /:article placeholder
  // is present in the path
  if (!articles[req.params.article]) {
    return res.status(404).send('No such article!');
  }

  req.article = articles[req.params.article];

  next();
};

For each route that requires authentication, we need to check whether the user is an admin, so a second middleware comes to life. A user has admin rights in this example if the server is hosted on their local workstation, and if not, the server will respond with a 403 Forbidden status message:

var requireAdmin = function(req, res, next) {
  if (req.ip !== '127.0.0.1') {
    return res.status(403).send('Forbidden');
  }

  next();
};

The thing left to do is to create the routes and integrate these two functions in the picture. The first approach is to load the middleware for each separate route, so the code would look like the following:

app.get('/articles/:article', loadArticle, function(req, res, next) {
  res.send(req.article.content);
});

app.get('/articles/:article/edit', loadArticle, requireAdmin, function(req, res, next) {
  res.send('Editing article ' + req.article.title);
});

Since the route ordering matters and we want to avoid including the loadArticle middleware for each route, a better way to handle the situation would be to create another route just before the previous two that includes the article-loading component:

// this path will match /articles/title as well as
// /articles/title/edit
app.get('/articles/:article/:action?', loadArticle);

app.get('/articles/:article', function(req, res, next) {
  res.send(req.article.content);
});

app.get('/articles/:article/edit', requireAdmin, function(req, res, next) {
  res.send('Editing article ' + req.article.title);
});

This looks better than our previous example, but we can still make a small adjustment for the future. At the moment, we only have the route for editing articles, which requires admin access, but we will probably have another one for creating new articles, which will look similar to /articles/:article/new. We can create a route that matches them and includes the admin middleware component, and place it at the top, as shown in the following line of code:

app.get('/articles/:article/:action', requireAdmin);

Although this technique works, it has its caveats. If we add another route, and the article placeholder wouldn't be the second parameter in that path, we would have to modify our code.

A better solution would be to use the native app.param() function from Express, which can take two parameters: a placeholder and a route handler. By using it, we wouldn't care anymore if the paths would change, as long as the placeholder still exists. There's only a one-line change to the first route of our sample application:

app.param('article', loadArticle);

Note

The app.param() function will match all the verbs and not only the GET requests. This means that if we add a PUT route to /articles/:article (for updating an existing item), the middleware will be executed for that path as well.

Route wildcards

Apart from the regular HTTP verbs, there is a special method in Express named all. It functions in the same way as the other app.VERB() methods, but it matches the request no matter what the HTTP verb is.

One of the path characters (the first argument to the app.VERB() method) that has a special meaning is *, which is replaced with (.*) when creating the regular expression and will thus match everything.

The combination of the method wildcard and the star wildcard is particularly helpful when loading the same middleware for sections of our main application, such as an admin dashboard or an API. Since we would require authentication for every route prefixed with /api or /admin, we can easily add the following route:

app.all('/admin/*', requireAdmin);
主站蜘蛛池模板: 张北县| 裕民县| 龙门县| 永泰县| 岐山县| 昌吉市| 荃湾区| 乡宁县| 太谷县| 建阳市| 莲花县| 郓城县| 静海县| 乐东| 道孚县| 浦县| 定南县| 浙江省| 义乌市| 敖汉旗| 若尔盖县| 清远市| 临澧县| 蓝山县| 太仓市| 石泉县| 宜春市| 宝山区| 荣昌县| 浦东新区| 资阳市| 敖汉旗| 新密市| 梁河县| 大石桥市| 江津市| 浪卡子县| 宝山区| 吴江市| 嵊州市| 平果县|