- Mastering Web Application Development with Express
- Alexandru Vl?du?u
- 1287字
- 2021-08-05 17:54:19
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);
- Java程序設計(慕課版)
- Learning C# by Developing Games with Unity 2020
- Git Version Control Cookbook
- Linux環境編程:從應用到內核
- 數據結構(C語言)
- MongoDB權威指南(第3版)
- MongoDB,Express,Angular,and Node.js Fundamentals
- Node.js:來一打 C++ 擴展
- Python機器學習:預測分析核心算法
- 圖數據庫實戰
- C# and .NET Core Test Driven Development
- Python 3 Object:oriented Programming(Second Edition)
- 高效使用Greenplum:入門、進階與數據中臺
- Java Web動態網站開發(第2版·微課版)
- Learning Redis