安装
创建一个目录,然后进入此目录并将其作为当前工作目录。然后通过npm init
命令为你的应用创建一个package.json
文件。接下来安装Express并将其保存到依赖列表中。
mkdir myapp
cd myapp
npm init
npm install express --save
Express应用生成器
通过应用生成器工具express可以快速创建一个应用的骨架。
通过如下命令安装:
$ npm install express-generator -g
-h
选项可以列出所有可用的命令行选项:
$ express -h
Usage: express [options] [dir]
Options:
--version output the version number
-e, --ejs add ejs engine support
--pug add pug engine support
--hbs add handlebars engine support
-H, --hogan add hogan.js engine support
-v, --view <engine> add view <engine> support (dust|ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade)
--no-view use static html instead of view engine
-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
-h, --help output usage information
例如,下面的示例就是在当前工作目录下创建一个命名为myapp
的应用。
$ express myapp
create : myapp
create : myapp/package.json
create : myapp/app.js
create : myapp/public
create : myapp/public/javascripts
create : myapp/public/images
create : myapp/routes
create : myapp/routes/index.js
create : myapp/routes/users.js
create : myapp/public/stylesheets
create : myapp/public/stylesheets/style.css
create : myapp/views
create : myapp/views/index.jade
create : myapp/views/layout.jade
create : myapp/views/error.jade
create : myapp/bin
create : myapp/bin/www
然后安装所有依赖包:
$ cd myapp
$ npm install
启动这个应用(MacOS或Linux平台):
$ DEBUG=myapp npm start
Windows平台使用如下命令:
> set DEBUG=myapp & npm start
然后在浏览器中打开http://localhost:3000/
网址就可以看到这个应用了。
通过Express应用生成器创建的应用一般都有如下目录结构:
.
├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.jade
├── index.jade
└── layout.jade
7 directories, 9 files
路由
路由是指如何定义应用的端点以及如何响应客户端的请求。
路由是由一个URI、HTTP请求和若干个句柄组成,它的结构如下:app.METHOD(path, [callback...], callback)
,app
是express
对象的一个实例,METHOD
是一个HTTP请求方法,path
是服务器上的路径,callback
是当路由匹配时要执行的函数。
下面是一个基本的路由示例:
var express = require('express');
var app = express();
app.get('/', function(req, res) {
res.send('hello world');
});
路由方法
路由方法源于HTTP请求方法,和express实例相关联。
下面这个例子展示了为应用跟路径定义的GET和POST请求:
// GET method route
app.get('/', function (req, res) {
res.send('GET request to the homepage');
});
// POST method route
app.post('/', function (req, res) {
res.send('POST request to the homepage');
});
Express定义了如下和HTTP请求对应的路由方法:get
、post
、put
、head
、delete
、options
、trace
、copy
、lock
、mkcol
、move
、purge
、propfind
、proppatch
、unlock
、report
、mkactivity
、checkout
、merge
、m-search
、notify
、subscribe
、unsubscribe
、patch
、search
、和connect
。
有些路由方法名不是合规的 JavaScript 变量名,此时使用括号记法,比如: app['m-search']('/', function() {})
app.all()
是一个特殊的路由方法,没有任何HTTP方法与其对应,它的作用是对于一个路径上的所有请求加载中间件。
在下面的例子中,来自“/secret”
的请求,不管使用GET、POST、PUT、DELETE或其他任何http模块支持的HTTP请求,句柄都会得到执行。
app.all('/secret', function (req, res, next) {
console.log('Accessing the secret section ...');
next(); // pass control to the next handler
});
路由路径
路由路径和请求方法一起定义了请求的端点,它可以是字符串、字符串模式或者正则表达式。
查询字符串不是路由路径的一部分。
// 使用字符串的路由路径
// 匹配根路径的请求
app.get('/', function (req, res) {
res.send('root');
});
// 匹配 /about 路径的请求
app.get('/about', function (req, res) {
res.send('about');
});
// 匹配 /random.text 路径的请求
app.get('/random.text', function (req, res) {
res.send('random.text');
});
// 使用字符串模式的路由路径
// 匹配 acd 和 abcd
app.get('/ab?cd', function(req, res) {
res.send('ab?cd');
});
// 匹配 abcd、abbcd、abbbcd等
app.get('/ab+cd', function(req, res) {
res.send('ab+cd');
});
// 匹配 abcd、abxcd、abRABDOMcd、ab123cd等
app.get('/ab*cd', function(req, res) {
res.send('ab*cd');
});
// 匹配 /abe 和 /abcde
app.get('/ab(cd)?e', function(req, res) {
res.send('ab(cd)?e');
});
字符?、+、*
和()
是正则表达式的子集,-
和.
在基于字符串的路径中按照字面值解释。
// 使用正则表达式的路由路径
// 匹配任何路径中含有a的路径
app.get(/a/, function(req, res) {
res.send('/a/');
});
// 匹配 butterfly、dragonfly,不匹配butterflyman、dragonfly man等
app.get(/.*fly$/, function(req, res) {
res.send('/.*fly$/');
});
路由句柄
可以为请求处理提供多个回调函数,其行为类似中间件。唯一的区别是这些回调函数有可能调用next('route')
方法而略过其他路由回调函数。可以利用该机制为路由定义前提条件,如果在现有路径上继续执行没有意义,则可将控制权交给剩下的路径。
路由句柄有多种形式,可以是一个函数、一个函数数组,或者是两者混合。
// 使用一个回调函数处理路由
app.get('/example/a', function (req, res) {
res.send('Hello from A!');
});
// 使用多个回调函数处理路由(记得指定`next`对象)
app.get('/example/b', function (req, res, next) {
console.log('response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from B!');
});
// 使用回调函数数组处理路由
var cb0 = function (req, res, next) {
console.log('CB0');
next();
}
var cb1 = function (req, res, next) {
console.log('CB1');
next();
}
var cb2 = function (req, res) {
res.send('Hello from C!');
}
app.get('/example/c', [cb0, cb1, cb2]);
// 混合使用函数和函数数组处理路由
var cb0 = function (req, res, next) {
console.log('CB0');
next();
}
var cb1 = function (req, res, next) {
console.log('CB1');
next();
}
app.get('/example/d', [cb0, cb1], function (req, res, next) {
console.log('response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from D!');
});
响应方法
下表中响应对象(res
)的方法向客户端返回响应,终结请求响应的循环。如果在路由句柄中一个方法也不调用,来自客户端的请求会一直挂起。
方法 | 描述 |
---|---|
res.download() |
提示下载文件。 |
res.end() |
终结响应处理流程。 |
res.json() |
发送一个JSON格式的响应。 |
res.jsonp() |
发送一个支持JSONP的JSON格式的响应。 |
res.redirect() |
重定向请求。 |
res.render() |
渲染视图模板。 |
res.send() |
发送各种类型的响应。 |
res.sendFile |
以八位字节流的形式发送文件。 |
res.sendStatus() |
设置响应状态代码,并将其以字符串形式作为响应体的一部分发送。 |
app.route()
可使用app.route()
创建路由路径的链式路由句柄。由于路径在一个地方指定,这样做有助于创建模块化的路由。
使用app.route()定义链式路由句柄
app.route('/book')
.get(function(req, res) {
res.send('Get a random book');
})
.post(function(req, res) {
res.send('Add a book');
})
.put(function(req, res) {
res.send('Update the book');
});
express.Router
可使用express.Router
类创建模块化、可挂载的路由句柄。Router
实例是一个完整的中间件和路由系统,因此常称其为一个mini-app
。
下面的实例程序创建了一个路由模块,并加载了一个中间件,定义了一些路由,并且将它们挂载至应用的路径上。
在app
目录下创建名为birds.js
的文件,内容如下:
var express = require('express');
var router = express.Router();
// 该路由使用的中间件
router.use(function timeLog(req, res, next) {
console.log('Time: ', Date.now());
next();
});
// 定义网站主页的路由
router.get('/', function(req, res) {
res.send('Birds home page');
});
// 定义 about 页面的路由
router.get('/about', function(req, res) {
res.send('About birds');
});
module.exports = router;
然后在应用中加载路由模块。
var birds = require('./birds');
app.use('/birds', birds);
应用即可处理发自/birds
和/birds/about
的请求,并且调用为该路由指定的timeLog
中间件。
使用中间件
Express是一个自身功能极简,完全是由路由和中间件构成一个的web开发框架:从本质上来说,一个Express应用就是在调用各种中间件。
中间件(Middleware
)是一个函数,它可以访问请求对象(request object(req)
), 响应对象(response object(res)
),和web应用中处于请求—响应循环流程中的中间件,一般被命名为next
的变量。
中间件的功能包括:
- 执行任何代码
- 修改请求和响应对象
- 终结请求-响应循环
- 调用堆栈中的下一个中间件
如果当前中间件没有终结请求—响应循环,则必须调用next()
方法将控制权交给下一个中间件,否则请求就会挂起。
Express应用可使用如下几种中间件:
- 应用级中间件
- 路由级中间件
- 错误处理中间件
- 内置中间件
- 第三方中间件
使用可选则挂载路径,可在应用级别或路由级别装载中间件。另外,还可以同时装在一系列中间件函数,从而在一个挂载点上创建一个子中间件栈。
应用级中间件
应用级中间件绑定到app
对象使用app.use()
和app.METHOD()
,其中,METHOD
是需要处理的HTTP请求的方法,例如GET,PUT,POST等等,全部小写。
var app = express();
// 没有挂载路径的中间件,应用的每个请求都会执行该中间件
app.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 挂载至/user/:id的中间件,任何指向/user/:id的请求都会执行它
app.use('/user/:id', function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 路由和句柄函数(中间件系统),处理指向/user/:id的GET请求
app.get('/user/:id', function (req, res, next) {
res.send('USER');
});
下面这个例子展示了在一个挂载点装载一组中间件。
// 一个中间件栈,对任何指向/user/:id的HTTP请求打印出相关信息
app.use('/user/:id', function(req, res, next) {
console.log('Request URL:', req.originalUrl);
next();
}, function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
作为中间件系统的路由句柄,使得为路径定义多个路由成为可能。在下面的例子中,为指向/user/:id
的GET请求定义了两个路由。第二个路由虽然不会带来任何问题,但却永远不会被调用,因为第一个路由已经终止了请求—响应循环。
// 一个中间件栈,处理指向/user/:id的GET请求
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id);
next();
}, function (req, res, next) {
res.send('User Info');
});
// 处理 /user/:id,打印出用户id
app.get('/user/:id', function (req, res, next) {
res.end(req.params.id);
});
如果需要在中间件栈中跳过剩余中间件,调用next('route')
方法将控制权交给下一个路由。 注意:next('route')
只对使用app.VERB()
或router.VERB()
加载的中间件有效。
// 一个中间件栈,处理指向/user/:id的GET请求
app.get('/user/:id', function (req, res, next) {
// 如果user id为0, 跳到下一个路由
if (req.params.id == 0) next('route');
// 否则将控制权交给栈中下一个中间件
else next();
}, function (req, res, next) {
res.render('regular'); // 渲染常规页面
});
// 处理/user/:id,渲染一个特殊页面
app.get('/user/:id', function (req, res, next) {
res.render('special');
});
路由级中间件
路由级中间件和应用级中间件一样,只是它绑定的对象为express.Router()
。
var router = express.Router();
路由级使用router.use()
或router.VERB()
加载。
上述在应用级创建的中间件系统,可通过如下代码改写为路由级。
var app = express();
var router = express.Router();
// 没有挂载路径的中间件,通过该路由的每个请求都会执行该中间件
router.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 一个中间件栈,显示任何指向/user/:id的HTTP请求的信息
router.use('/user/:id', function(req, res, next) {
console.log('Request URL:', req.originalUrl);
next();
}, function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 一个中间件栈,处理指向/user/:id的GET请求
router.get('/user/:id', function (req, res, next) {
// 如果 user id 为 0, 跳到下一个路由
if (req.params.id == 0) next('route');
// 负责将控制权交给栈中下一个中间件
else next();
}, function (req, res, next) {
res.render('regular'); // 渲染常规页面
});
// 处理 /user/:id, 渲染一个特殊页面
router.get('/user/:id', function (req, res, next) {
console.log(req.params.id);
res.render('special');
});
// 将路由挂载至应用
app.use('/', router);
错误处理中间件
错误处理中间件有4个参数,定义错误处理中间件时必须使用这4个参数。即使不需要next
对象,也必须在签名中声明它,否则中间件会被识别为一个常规中间件,不能处理错误。
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
内置中间件
从4.x版本开始,Express已经不再依赖Connect
了。除了express.static
,Express以前内置的中间件现在已经全部单独作为模块安装使用了。
express.static(root, [options])
express.static
是Express唯一内置的中间件。它基于serve-static
,负责在Express应用中提供管静态资源。
参数root
指提供静态资源的根目录。可选的options
参数拥有如下属性。
属性 | 描述 | 类型 | 缺省值 |
---|---|---|---|
dotfiles |
是否对外输出文件名以点(. )开头的文件。可选值为 allow 、deny 和ignore
|
String |
ignore |
etag |
是否启用etag 生成 |
Boolean |
true |
extensions |
设置文件扩展名备份选项 | Array |
[] |
index |
发送目录索引文件,设置为false 禁用目录索引。 |
Mixed |
index.html |
lastModified |
设置Last-Modified 头为文件在操作系统上的最后修改日期。可能值为true 或false 。 |
Boolean |
true |
maxAge |
以毫秒或者其字符串格式设置Cache-Control 头的max-age 属性。 |
Number |
0 |
redirect |
当路径为目录时,重定向至/ 。 |
Boolean |
true |
setHeaders |
设置HTTP头以提供文件的函数。 | Function |
var options = {
dotfiles: 'ignore',
etag: false,
extensions: ['htm', 'html'],
index: false,
maxAge: '1d',
redirect: false,
setHeaders: function (res, path, stat) {
res.set('x-timestamp', Date.now());
}
}
app.use(express.static('public', options));
每个应用可有多个静态目录。
app.use(express.static('public'));
app.use(express.static('uploads'));
app.use(express.static('files'));
第三方中间件
通过使用第三方中间件从而为Express应用增加更多功能。
安装所需功能的node模块,并在应用中加载,可以在应用级加载,也可以在路由级加载。
// 安装并加载解析cookie的中间件:cookie-parser
$ npm install cookie-parser
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');
// 加载用于解析cookie的中间件
app.use(cookieParser());
在Express中使用模板引擎
需要在应用中进行如下设置才能让Express渲染模板文件:
-
views
:放模板文件的目录,比如:app.set('views', './views')
-
view engine
:模板引擎,比如:app.set('view engine', 'jade')
然后安装相应的模板引擎npm软件包。
$ npm install jade --save
和Express兼容的模板引擎,比如Jade
,通过res.render()
调用其导出方法__express(filePath, options, callback)
渲染模板。
有一些模板引擎不遵循这种约定,Consolidate.js
能将Node中所有流行的模板引擎映射为这种约定,这样就可以和Express无缝衔接。
一旦view engine
设置成功,就不需要显式指定引擎,或者在应用中加载模板引擎模块,Express已经在内部加载,如下所示。
app.set('view engine', 'jade');
在views
目录下生成名为index.jade
的Jade
模板文件,内容如下:
html
head
title!= title
body
h1!= message
然后创建一个路由渲染index.jade
文件。如果没有设置view engine
,您需要指明视图文件的后缀,否则就会遗漏它。
app.get('/', function (req, res) {
res.render('index', { title: 'Hey', message: 'Hello there!'});
});
此时向主页发送请求,index.jade
会被渲染为HTML。
错误处理
定义错误处理中间件和定义其他中间件一样,除了需要4个参数,而不是3个,其格式如下(err, req, res, next)
。例如:
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
在其他app.use()
和路由调用后,最后定义错误处理中间件。
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
app.use(bodyParser());
app.use(methodOverride());
app.use(function(err, req, res, next) {
// 业务逻辑
});
中间件返回的响应是随意的,可以响应一个HTML错误页面、一句简单的话、一个JSON字符串,或者其他任何东西。
为了便于组织(更高级的框架),可能会像定义常规中间件一样,定义多个错误处理中间件。比如您想为使用XHR的请求定义一个,还想为没有使用的定义一个,那么:
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
app.use(bodyParser());
app.use(methodOverride());
app.use(logErrors);
app.use(clientErrorHandler);
app.use(errorHandler);
logErrors
将请求和错误信息写入标准错误输出、日志或类似服务。
function logErrors(err, req, res, next) {
console.error(err.stack);
next(err);
}
clientErrorHandler
的定义如下(注意这里将错误直接传给了next
):
function clientErrorHandler(err, req, res, next) {
if (req.xhr) {
res.status(500).send({ error: 'Something blew up!' });
} else {
next(err);
}
}
errorHandler
能捕获所有错误,其定义如下:
function errorHandler(err, req, res, next) {
res.status(500);
res.render('error', { error: err });
}
如果向next()
传入参数(除了'route'
字符串),Express会认为当前请求有错误的输出,因此跳过后续其他非错误处理和路由/中间件函数。如果需做特殊处理,需要创建新的错误处理路由。
如果路由句柄有多个回调函数,可使用'route'
参数跳到下一个路由句柄。比如:
app.get('/a_route_behind_paywall',
function checkIfPaidSubscriber(req, res, next) {
if(!req.user.hasPaid) {
// 继续处理该请求
next('route');
}
}, function getPaidContent(req, res, next) {
PaidContent.find(function(err, doc) {
if(err) return next(err);
res.json(doc);
});
});
在这个例子中,句柄getPaidContent
会被跳过,但app
中为/a_route_behind_paywall
定义的其他句柄则会继续执行。
next()
和next(err)
类似于Promise.resolve()
和Promise.reject()
。它们让您可以向Express发信号,告诉它当前句柄执行结束并且处于什么状态。next(err)
会跳过后续句柄,除了那些用来处理错误的句柄。
缺省错误处理句柄
Express内置了一个错误处理句柄,它可以捕获应用中可能出现的任意错误。这个缺省的错误处理中间件将被添加到中间件堆栈的底部。
如果你向next()
传递了一个error
,而你并没有在错误处理句柄中处理这个error
,Express内置的缺省错误处理句柄就是最后兜底的。最后错误将被连同堆栈追踪信息一同反馈到客户端。堆栈追踪信息并不会在生产环境中反馈到客户端。
设置环境变量NODE_ENV
为“production”
就可以让应用运行在生产环境模式下。
如果你已经开始向response
输出数据了,这时才调用next()
并传递了一个error
,比如你在将向客户端输出数据流时遇到一个错误,Express内置的缺省错误处理句柄将帮你关闭连接并告知request
请求失败。
因此,当你添加了一个自定义的错误处理句柄后,如果已经向客户端发送包头信息了,你还可以将错误处理交给Express内置的错误处理机制。
function errorHandler(err, req, res, next) {
if (res.headersSent) {
return next(err);
}
res.status(500);
res.render('error', { error: err });
}