一、安装
$ npm install express
\# 译注:强烈建议这种方式 $ npm install -g express
二、快速上手
最快上手 express 的方法是利用可执行的 express(1) 来生成一个应用,如下所示:
创建一个 app:
$ npm install -g express $ express /tmp/foo && cd /tmp/foo
$ npm install -d
$ node app.js
三、创建一个服务器
要创建一个 express.HTTPServer 实例,只需调用 createServer() 方法。 通用这个应用实例,我们可以定义基于 HTTP 动作(HTTP Verbs)的路由,以 app.get() 为例:
var app = require('express').createServer();app.get('/', function(req, res){ res.send('hello world'); });
app.listen(3000);
四、创建一个 HTTPS 服务器
如上述初始化一个 express.HTTPSServer 实例。然后我们给它传一个配置对象,接受 key、cert 和其他在 https 文档 所提到的(属性/方法)。
var app = require('express').createServer({ key: ... });
五、配置
Express 支持任意环境,如产品阶段(production)和开发阶段(development)。开发者可以使用 configure() 方法来设置当前所需环境。如果 configure() 的调用不包含任何环境名,它将运行于所有环境中所指定的回调。
译注: 像 production / development / stage 这些别名都是可以自已取的,如 application.js 中的 app.configure 所示。实际用法看下面例子。
下面这个例子仅在开发阶段 dumpExceptions (抛错),并返回堆栈异常。不过在两个环境中我们都使用 methodOverride 和 bodyParser。注意一下 app.router 的使用,它可以(可选)用来加载(mount)程序的路由,另外首次调用 app.get()、app.post() 等也将会加载路由。
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()); });
app.configure('stage', 'prod', function(){ // config });
译注:设置详见:application.js 的 app.set。
app.configure(function(){ app.set('views', __dirname + '/views'); app.set('views'); // => "/absolute/path/to/views"app.enable('some feature'); // 等价于:app.set('some feature', true);
app.disable('some feature'); // 等价于:app.set('some feature', false);
app.enabled('some feature') // => false });
$ NODE_ENV=production node app.js
六、设置
Express 支持下列快捷(out of the box)设置:
1.basepath 用于 res.redirect() 的应用程序基本路径(base path),显式地处理绑定的应用程序(transparently handling mounted apps.)
2.view View 默认的根目录为 CWD/views
3.view engine 默认 View 引擎处理(View 文件)并不需要使用后缀
4.view cache 启用 View 缓存 (在产品阶段被启用)
5.charet 改变编码,默认为 utf-8
6.case sensitive routes 路由中区分大小写
7.strit routing 启用后(路由中的)结尾 / 将不会被忽略(译注:即 app.get('/sofish') 和 app.get('/sofish/') 将是不一样的)
8.json callback 启用 res.send() / res.json() 显式的 jsonp 支持(transparent jsonp support)
七、路由
Express 利用 HTTP 动作提供一套提示性强、有表现力的路由 API。打个比方,如果想要处理某个路径为 /user/12 的账号,我们能像下面这样来定义路由。关联到命名占位符(named placeholders)的值可用 req.params 来访问。
app.get('/user/:id', function(req, res){ res.send('user ' + req.params.id); });
// 修改一下官方的这个字符串 /\/user\/([^\/]+)\/?/
app.get(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/, function(req, res){ res.send(req.params); });
$ 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"]
"/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
var express = require('express') , app = express.createServer();app.use(express.bodyParser());
app.post('/', function(req, res){ res.send(req.body); });
app.listen(3000);
八、进路控制(Passing Route Control)
我们可以通过调用第三个参数,next() 函数,来控制下一个适配的路由。如果找不到适配,控制权将会传回给 Connect,同时中间件将会按在 use() 中添加的顺序被依次调用。道理同样适应于多个定义到同一路径的路由,他们将会依次被调用直到其中某个不调用 next() 而决定做出请求响应。
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 });
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);
九、中间件
使用的 Connect 中间件(属性)通常伴随着你的一个常规 Connect 服务器,被传到 express.createServer() 。如:
var express = require('express');var app = express.createServer( express.logger() , express.bodyParser() );
app.use(express.logger({ format: ':method :url' }));
var connect = require('connect'); app.use(connect.logger()); app.use(connect.bodyParser());
app.use(express.logger()); app.use(express.bodyParser());
很多 Express 应用都包含这样的一行 app.use(app.router),这看起来可能有点奇怪,其实它仅仅是一个包含所有定义路由规则,并执行基于现有 URL 请求和 HTTP 方法路由查找的一个中间件功能。Express 允许你决定其位置(to position),不过默认情况下它被放置于底部。通过改变路由的位置,我们可以改变中间件的优先级,譬如我们想把错误报告做为最后的中间件,以便任何传给 next() 的异常都可以通过它来处理;又或者我们希望静态文件服务优先级更低,以允许我们的路由可以监听单个静态文件请求的下载次数,等等。这看起来差不多是这样的:
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(...));
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(); });
十、路由中间件
路由可以利用路由器中间件,传递一个以上的回调函数(或者数组)到其方法中。这个特性非常有利于限制访问、通过路由下载数据,等等。
通常异步数据检索看起来可能像下例,我们使用 :id 参数,尝试加载一个用户:
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); }); });
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); });
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); });
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); });
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(){});
我们可能会有多次想要“跳过”剩余的路由中间件,继续匹配后续的路由。做到这点,我们只需调用 next() 时带上 'route' 字符串 —— next('route')。如果没有余下的路由匹配到请求的 URL,Express 将会返回 404 Not Found。
十一、HTTP 方法
至此已接触了好几次 app.get(),除此这外 Express 还提供了其他常见的 HTTP 动作,如 app.post() 、app.del() 等等。
POST 用法的一个常用例子是提交一个表单。下面我们简单地在 html 中把表单的 method 属性设置为 post,控制权将会指派给它下面所定义的路由。
<form method="post" action="/"> <input type="text" name="user[name]" /> <input type="text" name="user[email]" /> <input type="submit" value="Submit" /> </form>
app.use(express.bodyParser());
app.post('/', function(req, res){ console.log(req.body.user); res.redirect('back'); });
app.use(express.bodyParser()); app.use(express.methodOverride());
<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'); });
十二、错误处理
Express 提供了 app.error() 方法以便接收到的异常在一个路由里抛出,或者传到 next(err) 中。下面这个例子将基于特定的 NotFound 异常处理不同的页面:
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!'); });
app.error(function(err, req, res, next){ if (err instanceof NotFound) { res.render('404.jade'); } else { next(err); } });
app.error(function(err, req, res){ res.render('500.jade', { error: err }); });
app.use(express.errorHandler({ dumpExceptions: true }));
app.use(express.errorHandler({ showStack: true, dumpExceptions: true }));
十三、Route 参数预处理
路由参数预处理,通过隐式数据加载和请求验证,可以大大提升你程序的可读性。打个比方,你通常需要持续地从多个路由获取基本数据。像用 /user/:id 加载一个用户,通常来说我们可能会这样干:
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); }); });
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(); }); });
app.get('/user/:userId', function(req, res){ res.send('user ' + req.user.name); });
十四、View 处理
View 文件件使用 <name>.<engine> 这样的格式,其中 <engine> 是被 require 进来模块的名。例如 layout.ejs 将告诉 view 系统去 require('ejs'),被加载的模块必须(导出) exports.compile(str, options) 方法,并返回一个 Function 来适应 Express。app.register() 可用以改变这种默认行为,将文件扩展名映射到特定的引擎。譬如 “foo.html” 可以由 ejs 来处理。
下面这个例子使用 Jade 来处理 index.html。因为我们并未使用 layout: false,index.jade 处理后的内容将会被传入到 layout.jade 中一个名为 body 的本地变量。
app.get('/', function(req, res){ res.render('index.jade', { title: 'My Site' }); });
app.set('view engine', 'jade');
res.render('index');
res.render('index.jade');
res.render('another-page.ejs');
app.set('view options', { layout: false });
res.render('myview.ejs', { layout: true });
res.render('page', { layout: 'mylayout' });
res.render('page', { layout: 'mylayout.jade' });
res.render('page', { layout: __dirname + '/../../mylayout.jade' });
app.set('view options', { open: '{{', close: '}}' })
十五、View 部件
Express 的 view 系统内置了部件(partials) 和集合器(collections)的支持,相当于用一个 “迷你” 的 view 替换一个文档碎片(document fragment)。示例,在一个 view 中重复渲染来显示评论,我们可以使用部件集:
partial('comment', { collection: comments });
partial('comment', comments);
1.firstInCollection true,当它是第一个对象的时候
2.indexInCollection 在集合器对象中的索引
3.lastInCollection true,当它是最后一个对象的时候
4.collectionLength 集合器对象的长度
本地变量的传递(生成)具备更高的优先级,同时,传到父级 view 的本地变量对于子级 view 同样适应。例如当我们用 partial('blog/post', post) 来渲染一个博客文章,它将会生成一个 post 本地变量,在调用这个函数的 view 中存在本地变量 user,它将同样对 blog/post 有效。(译注:这里 partial 比较像 php 中的 include 方法)。
注意: 请谨慎使用部件集合器,渲染一个长度为 100 的部件集合数组相当于我们需要处理 100 个 view。对于简单的集合,最好重复内置,而非使用部件集合器以避免开销过大。
十六、View 查找
View 查找相对于父级 view (路径)执行,如我们有一个 view 页面叫作 views/user/list.jade,并且在其内部写有 partial('edit') 则它会尝试加载 views/user/edit.jade,同理 partial('../messages') 将会加载 views/messages.jade。
View 系统还支持模板索引,允许你使用一个与 view 同名的目录。例如在一个路由中,res.render('users') 得到的非 views/users.jade 即 views/users/index.jade。(译注:先处理 <path>.<engine> 的情况,再处理 <path>/<index.<engine> 的情况,详情可见 view.js。)
当使用上述 view 索引,我们在与 view 同一个目录下,使用 partial('users') 中引用 views/users/index.jade,与此同时 view 系统会尝试索引 ../users/index,而无须我们调用 partial('users')。
十七、Template Engines
下列为 Express 最常用的模板引擎:
1.Haml:haml 实现
2.Jade:haml.js 继位者
3.EJS:嵌入式 JavaScript
4.CoffeeKup:基于 CoffeeScript 的模板
5.jQuery Templates
十八、Session 支持
Session 支持可以通过使用 Connect 的 session 中间件来获得,为此通常我们同时需要在其前加上 cookieParser 中间件,它将解析和存储 cookie 数据于 req.cookies 中。
app.use(express.cookieParser()); app.use(express.session({ secret: "keyboard cat" }));
var RedisStore = require('connect-redis')(express); app.use(express.cookieParser()); app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
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){ // 我们可能通过一个表单 POST 出多个 item // (在些使用 bodyParser() 中间件) var items = req.body.items; req.session.items = items; res.redirect('back'); });
app.get('/add-to-cart', function(req, res){ // 当返回时,页面 GET /add-to-cart // 我们可以检查 req.session.items && req.session.items.length // 来打印出提示 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'); });
十九、升级指南
对于使用 Express 1.x 的同学,如果你有很重要的程序需要升级到 2.x 以获得更好的支持,请看官方非常详细的迁移指南:http://expressjs.com/guide.html#migration-guide