1. Installation
Copy the code as follows: $ npm install express
Or use executable express(1) anywhere to install:
Copy the code as follows:/# Translator Note: This method is highly recommended
$ npm install -g express
2. Get started quickly
The fastest way to get started with express is to use executable express(1) to generate an application, as shown below:
Create an app:
The code copy is as follows:
$ npm install -g express
$ express /tmp/foo && cd /tmp/foo
Install dependency package:
The code copy is as follows:
$ npm install -d
Start the server:
The code copy is as follows:
$ node app.js
3. Create a server
To create an express.HTTPServer instance, simply call the createServer() method. For this application example, we can define a route based on HTTP actions (HTTP Verbs), taking app.get() as an example:
The code copy is as follows:
var app = require('express').createServer();
app.get('/', function(req, res){
res.send('hello world');
});
app.listen(3000);
4. Create an HTTPS server
Initialize an express.HTTPSServer instance as above. Then we pass it a configuration object, accepting key, cert, and other (properties/methods) mentioned in the https documentation.
The code copy is as follows:
var app = require('express').createServer({ key: ... });
V. Configuration
Express supports any environment, such as product stage and development stage. Developers can use the configure() method to set the current required environment. If the call to configure() does not contain any environment name, it will run on the callback specified in all environments.
Translator's note: Aliases like production/development/stage can be taken by themselves, as shown in app.configure in application.js. See the following example for actual usage.
The following example only dumpExceptions (throwing errors) in the development stage and returns a stack exception. However, in both environments we use methodOverride and bodyParser. Pay attention to the use of app.router, which can (optional) be used to load the route of the program. In addition, the route will also be loaded by calling app.get(), app.post(), etc. for the first time.
The code copy is as follows:
app.configure(function(){
app.use(express.methodOverride());
app.use(express.bodyParser());
app.use(app.router);
});
app.configure('development', function(){
app.use(express.static(__dirname + '/public'));
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
var oneYear = 31557600000;
app.use(express.static(__dirname + '/public', { maxAge: oneYear }));
app.use(express.errorHandler());
});
For similar environments you can pass multiple environment strings:
The code copy is as follows:
app.configure('stage', 'prod', function(){
// config
});
For any internal settings (#), Express provides set(key[, val]), enable(key) and disable(key) methods:
Translator note: For details, please refer to: application.js app.set.
The code copy is as follows:
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('views');
// => "/absolute/path/to/views"
app.enable('some feature');
// Equivalent to: app.set('some feature', true);
app.disable('some feature');
// Equivalent to: app.set('some feature', false);
app.enabled('some feature')
// => false
});
To change the environment, we can set NODE_ENV environment variables, such as:
The code copy is as follows:
$ NODE_ENV=production node app.js
This is very important because most caching mechanisms are turned on only during the product phase.
6. Settings
Express supports the following shortcut settings (out of the box):
1.basepath is used for the application base path of res.redirect(), which explicitly handles mounted apps.
2.view View The default root directory is CWD/views
3.view engine The default View engine processing (View files) does not require the use of suffixes
4.view cache enables View cache (enabled in the product stage)
5.charet changes encoding, default is utf-8
6.case sensitive routes case sensitive routes
7. After strit routing is enabled (in the route) ending / will not be ignored (translation note: that is, app.get('/sofish') and app.get('/sofish/') will be different)
8.json callback Enable res.send() / res.json() explicit jsonp support (transparent jsonp support)
7. Routing
Express uses HTTP actions to provide a set of prompt and expressive routing APIs. For example, if you want to deal with an account with a path of /user/12, we can define the route as follows. The value associated with named placeholders is accessible by req.params.
The code copy is as follows:
app.get('/user/:id', function(req, res){
res.send('user ' + req.params.id);
});
A route is a string that is compiled internally into regular. For example, when /user/:id is compiled, a simplified version of the regular expression is roughly as follows:
The code copy is as follows:
// Modify the official string
///user//([^//]+)//?/
Regular expressions can be passed in and applied to complex scenarios. Since content groups captured by literal regular expressions are anonymous, we may access them directly through req.params . Therefore, the first set of content we capture will be req.params[0], while the second set is immediately followed by req.params[1].
The code copy is as follows:
app.get(/^//users?(?://(/d+)(?:/./.(/d+))?)?/, function(req, res){
res.send(req.params);
});
Curl requests for the above defined route:
The code copy is as follows:
$ curl http://dev:3000/user
[null,null]
$ curl http://dev:3000/users
[null,null]
$ curl http://dev:3000/users/1
["1",null]
$ curl http://dev:3000/users/1..15
["1","15"]
Here are some examples of routes associated with paths they may use:
The code copy is as follows:
"/user/:id"
/user/12
"/users/:id?"
/users/5
/users
"/files/*"
/files/jquery.js
/files/javascripts/jquery.js
"/file/*.*"
/files/jquery.js
/files/javascripts/jquery.js
"/user/:id/:operation?"
/user/1
/user/1/edit
"/products.:format"
/products.json
/products.xml
"/products.:format?"
/products.json
/products.xml
/products
"/user/:id.:format?"
/user/12
/user/12.json
For example, we can use POST to send json data, and use bodyParser, a middleware that can parse json request content (or other content) to return the data, and store the return result in req.body:
The code copy is as follows:
var express = require('express')
, app = express.createServer();
app.use(express.bodyParser());
app.post('/', function(req, res){
res.send(req.body);
});
app.listen(3000);
Usually we can use a "fool"-style placeholder like user/:id, without (name) restrictions. However, for example, if we want to limit the user id to be only a number, then we might use /user/:id([0-9]+), which will only take effect if the placeholder contains at least one number (adaptation, match).
8. Passing Route Control
We can control the next adapted route by calling the third parameter, next() function. If the adaptation is not found, control will be passed back to Connect, and the middleware will be called in sequence in the order added in use(). The principle also applies to multiple routes defined to the same path, and they will be called in turn until one of them does not call next() and decides to make a request response.
The code copy is as follows:
app.get('/users/:id?', function(req, res, next){
var id = req.params.id;
if (id) {
// do something
} else {
next();
}
});
app.get('/users', function(req, res){
// do something else
});
The app.all() method can easily transfer the same logic to all HTTP actions by calling it once. Next we use it to extract a user from the pseudo-data and assign it to req.user.
The code copy is as follows:
var express = require('express')
, app = express.createServer();
var users = [{ name: 'tj' }];
app.all('/user/:id/:op?', function(req, res, next){
req.user = users[req.params.id];
if (req.user) {
next();
} else {
next(new Error('cannot find user ' + req.params.id));
}
});
app.get('/user/:id', function(req, res){
res.send('viewing ' + req.user.name);
});
app.get('/user/:id/edit', function(req, res){
res.send('editing ' + req.user.name);
});
app.put('/user/:id', function(req, res){
res.send('updating ' + req.user.name);
});
app.get('*', function(req, res){
res.send(404, 'what???');
});
app.listen(3000);
9. Middleware
The Connect middleware (properties) used are usually accompanied by a regular Connect server that is passed to express.createServer() . like:
The code copy is as follows:
var express = require('express');
var app = express.createServer(
express.logger()
, express.bodyParser()
);
In addition, within the configure() block - this progressive palace (translator's note: laugh ^^, in a progressive manner), we can also conveniently use use() to add middleware.
The code copy is as follows:
app.use(express.logger({ format: ':method :url' }));
Usually, using the connect middleware you might use require('connect'), like this:
The code copy is as follows:
var connect = require('connect');
app.use(connect.logger());
app.use(connect.bodyParser());
This is somewhat unpleasant to some extent, so express re-exports these middleware properties, although they are the same:
The code copy is as follows:
app.use(express.logger());
app.use(express.bodyParser());
The order of middleware is very important. When Connect receives a request, the first middleware we pass to createServer() or use() will be accompanied by three parameters, request, response, and a callback function (usually next). When next() is called, it will be the second middleware turn, and so on. This is worth noting because many middlewares depend on each other. For example, methodOverride() queryes the req.body method to detect HTTP method overloading. On the other hand, bodyParser() parses the requested content and stores it in req.body. Another example is cookie parsing and session support. We must first use() cookieParser() and then session().
Many Express applications include such a line of app.use(app.router), which may seem a bit strange. In fact, it is just a middleware function that contains all defined routing rules and performs routing searches based on existing URL requests and HTTP methods. Express allows you to decide its position, but by default it is placed at the bottom. By changing the location of the route, we can change the priority of the middleware, for example, we want to use the error report as the last middleware so that any exception passed to next() can be handled through it; or we want the static file service to be lower priority to allow our route to listen for downloads of a single static file request, etc. This looks like this:
The code copy is as follows:
app.use(express.logger(...));
app.use(express.bodyParser(...));
app.use(express.cookieParser(...));
app.use(express.session(...));
app.use(app.router);
app.use(express.static(...));
app.use(express.errorHandler(...));
First we add logger(), which may contain the node's req.end() method, providing data on our response time. The content of the request will be parsed (if there is data), followed by cookie resolution and session support. At the same time, req.session will be defined when the route in app.router is triggered. At this time, we do not call next(), so the static() middleware will not know about this request. If the following route has been defined, we can record various states, refuse downloads, and consume download points, etc.
The code copy is as follows:
var downloads = {};
app.use(app.router);
app.use(express.static(__dirname + '/public'));
app.get('/*', function(req, res, next){
var file = req.params[0];
downloads[file] = downloads[file] || 0;
downloads[file]++;
next();
});
10. Routing middleware
Routing can use router middleware to pass more than one callback function (or array) into its methods. This feature is very beneficial for restricting access, downloading data through routing, and so on.
Usually asynchronous data retrieval may look like the following example, we use the :id parameter and try to load a user:
The code copy is as follows:
app.get('/user/:id', function(req, res, next){
loadUser(req.params.id, function(err, user){
if (err) return next(err);
res.send('Viewing user ' + user.name);
});
});
To ensure DRY principles and improve readability, we can apply this logic to a middleware. As shown below, abstracting this logic into the middleware will allow you to reuse it while ensuring the simplicity of our routing.
The code copy is as follows:
function loadUser(req, res, next) {
// You would fetch your user from the db
var user = users[req.params.id];
if (user) {
req.user = user;
next();
} else {
next(new Error('Failed to load user ' + req.params.id));
}
}
app.get('/user/:id', loadUser, function(req, res){
res.send('Viewing user ' + req.user.name);
});
Multiple routing can be applied to a deeper layer of logic in order, such as restricting access to a user account. The following example only allows users who have passed the authentication to edit their (her) accounts.
The code copy is as follows:
function andRestrictToSelf(req, res, next) {
req.authenticatedUser.id == req.user.id
? next()
: next(new Error('Unauthorized'));
}
app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){
res.send('Editing user ' + req.user.name);
});
Always remember that routing is just a simple function, as shown below, we can define functions that return middleware to create a more expressive and flexible solution.
The code copy is as follows:
function andRestrictTo(role) {
return function(req, res, next) {
req.authenticatedUser.role == role
? next()
: next(new Error('Unauthorized'));
}
}
app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
res.send('Deleted user ' + req.user.name);
});
Commonly used middleware "stack" can be passed through an array (which will be applied recursively), which can be mixed and matched to any degree.
The code copy is as follows:
var a = [middleware1, middleware2]
, b = [middleware3, middleware4]
, all = [a, b];
app.get('/foo', a, function(){});
app.get('/bar', a, function(){});
app.get('/', a, middleware3, middleware4, function(){});
app.get('/', a, b, function(){});
app.get('/', all, function(){});
For the complete code of this instance, please see the route middleware example repository.
We may have multiple times to "skip" the remaining routing middleware and continue to match subsequent routes. To do this, we just need to call next() with the 'route' string - next('route'). If no remaining routes match the requested URL, Express will return 404 Not Found.
11. HTTP method
So far, I have been exposed to app.get() several times. In addition, Express also provides other common HTTP actions, such as app.post(), app.del(), etc.
A common example of POST usage is submitting a form. Next, we simply set the method attribute of the form to post in html, and control will be assigned to the route defined below.
The code copy is as follows:
<form method="post" action="/">
<input type="text" name="user[name]" />
<input type="text" name="user[email]" />
<input type="submit" value="Submit" />
</form>
By default, Express does not know how to handle the content of this request, so we must add the bodyParser middleware, which will parse the content of application/x-www-form-urlencoded and application/json requests and store the variables in req.body. We can use this middleware like the following example:
The code copy is as follows:
app.use(express.bodyParser());
As follows, our route will have access to the req.body.user object, and when name and email are defined it will contain these two properties (translation note: if the content sent by the form is not empty).
The code copy is as follows:
app.post('/', function(req, res){
console.log(req.body.user);
res.redirect('back');
});
When you want to use a method like PUT in a form, we can use a hidden input named _method, which can be used to modify HTTP methods. To do this, we first need the methodOverride middleware, which must appear after bodyParser in order to use the form value contained in its req.body.
The code copy is as follows:
app.use(express.bodyParser());
app.use(express.methodOverride());
Why these methods are not owned by default? Simply put, it is just because it is not necessary for the complete functionality required by Express. The use of methods depends on your application, you may not need them, and the client can still use methods like PUT and DELETE, and you can use them directly because methodOverride provides a very good solution for form. Here is a demonstration of how to use PUT, which may look like:
The code copy is as follows:
<form method="post" action="/">
<input type="hidden" name="_method" value="put" />
<input type="text" name="user[name]" />
<input type="text" name="user[email]" />
<input type="submit" value="Submit" />
</form>
app.put('/', function(){
console.log(req.body.user);
res.redirect('back');
});
12. Error handling
Express provides the app.error() method so that the received exception is thrown in a route or passed to next(err). The following example will handle different pages based on a specific NotFound exception:
The code copy is as follows:
function NotFound(msg){
this.name = 'NotFound';
Error.call(this, msg);
Error.captureStackTrace(this, arguments.callee);
}
NotFound.prototype.__proto__ = Error.prototype;
app.get('/404', function(req, res){
throw new NotFound;
});
app.get('/500', function(req, res){
throw new Error('keyboard cat!');
});
As described below, we can call app.error() multiple times. Here we detect the NotFound instance and display a 404 page, or pass to the next error handler. It is worth noting that these processors can be defined anywhere, because they will be placed under the routing processor when listening() is listened to. It allows definitions within the configure() block so that we can use different exception handling methods based on the environment.
The code copy is as follows:
app.error(function(err, req, res, next){
if (err instanceof NotFound) {
res.render('404.jade');
} else {
next(err);
}
});
For the simplicity, here we assume that all errors of this demo are 500, and of course you can choose what you like. When executing system calls to a file system like node, you may receive an error.code with ENOENT, meaning "there is no such file or directory", which we can use in the error handler, or display a specified page when needed.
The code copy is as follows:
app.error(function(err, req, res){
res.render('500.jade', {
error: err
});
});
Our app can also use Connect's errorHandler middleware to report exceptions. For example, when we want to output a stderr exception in the "development" environment, we can use:
The code copy is as follows:
app.use(express.errorHandler({ dumpExceptions: true }));
At the same time, during the development stage, we may need to display the exceptions we pass and throw in the fancy HTML page, for which we can set showStack to true.
The code copy is as follows:
app.use(express.errorHandler({ showStack: true, dumpExceptions: true }));
The errorHandler middleware can also return json when Accept: application/json exists, which is very useful for developing applications that rely heavily on client Javascript.
13. Route parameter preprocessing
Routing parameter preprocessing, through implicit data loading and request verification, can greatly improve the readability of your program. For example, you usually need to continuously obtain basic data from multiple routes. For example, loading a user with /user/:id, we might do this:
The code copy is as follows:
app.get('/user/:userId', function(req, res, next){
User.get(req.params.userId, function(err, user){
if (err) return next(err);
res.send('user ' + user.name);
});
});
Through preprocessing, our parameters can be mapped to callbacks that perform validation, control, and even load data from the database. As follows, we call app.param() with parameter names and hope to map it to some middleware. As you can see, we accept the id parameter representing the placeholder value. Using this, we load the user as usual and handle the error, and simply call next() to hand over control to the next preprocessor or routing processor.
The code copy is as follows:
app.param('userId', function(req, res, next, id){
User.get(id, function(err, user){
if (err) return next(err);
if (!user) return next(new Error('failed to find user'));
req.user = user;
next();
});
});
Once this is done, the above will greatly improve the readability of the route and allow us to easily share the logic throughout the program:
The code copy is as follows:
app.get('/user/:userId', function(req, res){
res.send('user ' + req.user.name);
});
14. View processing
The View file uses the format <name>.<engine>, where <engine> is the name of the module required. For example, layout.ejs will tell the view system to require('ejs'), and the loaded module must (export) the exports.compile(str, options) method and return a Function to adapt to Express. app.register() can be used to change this default behavior to map file extensions to specific engines. For example, "foo.html" can be processed by ejs.
The following example uses Jade to process index.html. Because we do not use layout: false, the content processed by index.jade will be passed into a local variable called body in layout.jade.
The code copy is as follows:
app.get('/', function(req, res){
res.render('index.jade', { title: 'My Site' });
});
The new view engine setting allows us to specify the default template engine, for example, when we use jade, you can set it like this:
The code copy is as follows:
app.set('view engine', 'jade');
Allow us to handle this:
The code copy is as follows:
res.render('index');
Corresponding to:
The code copy is as follows:
res.render('index.jade');
When view engine is set, the extension is optional, but we can still mix the matching template engine:
The code copy is as follows:
res.render('another-page.ejs');
Express also provides view options settings, which will be applied to a view every time it is rendered, for example, if you don't want to use layouts:
The code copy is as follows:
app.set('view options', {
layout: false
});
This can be overloaded inside the res.render() call when needed:
The code copy is as follows:
res.render('myview.ejs', { layout: true });
When there is a need to change a layout, we usually need to specify another path. For example, when we have set view engine to jade and the file is named ./views/mylayout.jade, we can simply pass the parameters:
The code copy is as follows:
res.render('page', { layout: 'mylayout' });
Otherwise (translator's note: when view engine is not set to jade or other engine), we must specify an extension:
The code copy is as follows:
res.render('page', { layout: 'mylayout.jade' });
They can also be absolute paths:
The code copy is as follows:
res.render('page', { layout: __dirname + '/../../mylayout.jade' });
There is a good example of this - customize the start and close tags of ejs:
The code copy is as follows:
app.set('view options', {
open: '{{',
close: '}}'
})
15. View parts
Express's view system has built-in support for parts and collections, which is equivalent to replacing a document fragment with a "mini" view. Example, rendering repeatedly in a view to display comments, we can use the part set:
The code copy is as follows:
partial('comment', { collection: comments });
If no other options or local variables are needed, we can omit the entire object and simply pass it into an array, which is equivalent to the above:
The code copy is as follows:
partial('comment', comments);
In use, the component set provides some "magic" support for local variables for free:
1.firstInCollection true, when it is the first object
2. IndexInCollection in the collection object
3.lastInCollection true, when it is the last object
4.collectionLength The length of the collection object
The transfer (generating) of local variables has higher priority. At the same time, local variables passed to the parent view are also suitable for children. For example, when we use partial('blog/post', post) to render a blog post, it will generate a post local variable. There is a local variable user in the view that calls this function, which will also be valid for blog/post. (Translator's note: Here partial is more like the include method in php).
Note: Use the Parts Collection carefully, rendering an array of parts collections of length 100 is equivalent to 100 views we need to process. For simple collections, it is best to repeat the built-in rather than using a component collector to avoid excessive overhead.
16. View search
View search is executed relative to the parent view (path). If we have a view page called views/user/list.jade, and partial('edit') is written inside it, it will try to load views/user/edit.jade, and partial('../messages') will load views/messages.jade.
The View system also supports template indexing, allowing you to use a directory with the same name as the view. For example, in a route, res.render('users') obtains non-views/users.jade, that is, views/users/index.jade. (Translator's note: First handle the situation of <path>.<engine>, then handle the situation of <path>/<index.<engine>. For details, see view.js.)
When using the above view index, we use partial('users') to refer to views/users/index.jade in the same directory as the view. At the same time, the view system will try to index../users/index without us calling partial('users').
17. Template Engines
The following are the most commonly used template engines for Express:
1.Haml: haml implementation
2.Jade: haml.js successor
3.EJS: Embedded JavaScript
4.CoffeeKup: CoffeeScript-based template
5.jQuery Templates
18. Session Support
Session support can be obtained by using Connect's session middleware. For this reason, we usually need to preface it with the cookieParser middleware, which will parse and store cookie data in req.cookies.
The code copy is as follows:
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat" }));
By default, the session middleware uses Connect's built-in memory storage, but there are many other implementations. For example, connect-redis provides a Redis session storage, which can be used like the following:
The code copy is as follows:
var RedisStore = require('connect-redis')(express);
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
At this point, the req.session and req.sessionStore properties will be used by all routes and subsequent middleware. All properties on req.session will be automatically saved in a response, for example when we want to add data to the shopping cart:
The code copy is as follows:
var RedisStore = require('connect-redis')(express);
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
app.post('/add-to-cart', function(req, res){
// We may POST multiple items through one form
// (In some cases, bodyParser() middleware is used)
var items = req.body.items;
req.session.items = items;
res.redirect('back');
});
app.get('/add-to-cart', function(req, res){
// When returning, page GET /add-to-cart
// We can check req.session.items && req.session.items.length
// Print the prompt
if (req.session.items && req.session.items.length) {
req.notify('info', 'You have %s items in your cart', req.session.items.length);
}
res.render('shopping-cart');
});
For req.session, it also has methods such as Session#touch(), Session#destroy(), Session#regenerate(), etc. to maintain and operate sessions. For more details, please refer to the Connect Session documentation.
19. Upgrade Guide
For students using Express 1.x, if you have important programs that need to be upgraded to 2.x for better support, please see the official very detailed migration guide: http://expressjs.com/guide.html#migration-guide