{"id":19030498,"url":"https://github.com/sofish/express-guide","last_synced_at":"2026-01-25T07:37:37.384Z","repository":{"id":3771860,"uuid":"4848761","full_name":"sofish/express-guide","owner":"sofish","description":"对英文版 ExpressJS 的一个翻译","archived":false,"fork":false,"pushed_at":"2012-09-02T15:15:48.000Z","size":283,"stargazers_count":362,"open_issues_count":1,"forks_count":71,"subscribers_count":34,"default_branch":"master","last_synced_at":"2025-03-04T21:02:18.293Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://sofish.github.com/express-guide/","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sofish.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-07-01T07:55:51.000Z","updated_at":"2024-05-19T10:14:44.000Z","dependencies_parsed_at":"2022-07-20T18:49:53.853Z","dependency_job_id":null,"html_url":"https://github.com/sofish/express-guide","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sofish/express-guide","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofish%2Fexpress-guide","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofish%2Fexpress-guide/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofish%2Fexpress-guide/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofish%2Fexpress-guide/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sofish","download_url":"https://codeload.github.com/sofish/express-guide/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofish%2Fexpress-guide/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28747756,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T05:12:38.112Z","status":"ssl_error","status_checked_at":"2026-01-25T05:04:50.338Z","response_time":113,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T21:18:13.872Z","updated_at":"2026-01-25T07:37:37.367Z","avatar_url":"https://github.com/sofish.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e 在页面上阅读：[http://sofish.github.com/express-guide/](http://sofish.github.com/express-guide/)\n\n# Express 指南\n\n这是对 [express.js guide](http://expressjs.com/guide.html) 的一个翻译。翻译人：[Sofish Lin](http://sofish.de)（[twitter](http://twitter.com/sofish)）。\n\n翻译此文档的目的是学习 Express，因为翻译意味着一种责任，因此边看 Express 源代码边翻。在容易混淆的地方都加上了译注。给自己做一个记录保存下来，也给有需要的人一份参考。当然，由于个人水平的原因，并不能最恰当地传达作者的幽默，也少不了错误，还请各位有看到的同学多多指正。\n\n如有任何问题，请直接给我提 issue、pull request 或者在 twitter at-at 我。\n\n----------------------------------------------------------\n\n### 安装\n\n```bash\n$ npm install express\n```\n\n或者在任何地方使用可执行的 `express(1)` 安装： \n\n```bash\n\\# 译注：强烈建议这种方式\n$ npm install -g express\n```\n\n## 快速上手\n\n最快上手 express 的方法是利用可执行的 `express(1)` 来生成一个应用，如下所示：\n\n创建一个 app：\n\n```bash\n$ npm install -g express\n$ express /tmp/foo \u0026\u0026 cd /tmp/foo\n```\n\n安装依赖包：\n\n```bash\n$ npm install -d\n```\n\n启动服务器:\n\n```bash\n$ node app.js\n```\n\n### 创建一个服务器\n\n要创建一个 `express.HTTPServer` 实例，只需调用 `createServer()` 方法。 通用这个应用实例，我们可以定义基于 HTTP 动作（HTTP Verbs）的路由，以 `app.get()` 为例：\n\n```js\nvar app = require('express').createServer();\n\napp.get('/', function(req, res){\n  res.send('hello world');\n});\n\napp.listen(3000);\n```\n\n### 创建一个 HTTPS 服务器\n\n如上述初始化一个 `express.HTTPSServer` 实例。然后我们给它传一个配置对象，接受 `key`、`cert` 和其他在 [https 文档](http://nodejs.org/docs/v0.3.7/api/https.html#https.createServer) 所提到的（属性/方法）。\n\n```js\n var app = require('express').createServer({ key: ... });\n```\n\n### 配置\n\nExpress 支持任意环境，如产品阶段（production）和开发阶段（development）。开发者可以使用 `configure()` 方法来设置当前所需环境。如果 `configure()` 的调用不包含任何环境名，它将运行于所有环境中所指定的回调。\n\n\u003cmark\u003e译注：\u003c/mark\u003e 像 _production_ / _development_ / _stage_ 这些别名都是可以自已取的，如 [application.js](https://github.com/visionmedia/express/blob/master/lib/application.js) 中的 `app.configure` 所示。实际用法看下面例子。\n\n下面这个例子仅在开发阶段 `dumpExceptions` (抛错)，并返回堆栈异常。不过在两个环境中我们都使用 `methodOverride ` 和 `bodyParser`。注意一下 `app.router` 的使用，它可以（可选）用来加载（mount）程序的路由，另外首次调用 `app.get()`、`app.post()` 等也将会加载路由。\n\n```js\napp.configure(function(){\n\tapp.use(express.methodOverride());\n\tapp.use(express.bodyParser());\n\tapp.use(app.router);\n});\n\napp.configure('development', function(){\n\tapp.use(express.static(__dirname + '/public'));\n\tapp.use(express.errorHandler({ dumpExceptions: true, showStack: true }));\n});\n\napp.configure('production', function(){\n  var oneYear = 31557600000;\n  app.use(express.static(__dirname + '/public', { maxAge: oneYear }));\n  app.use(express.errorHandler());\n});\n```\n\n对于相似的环境你可以传递多个环境字符串：\n\n```js\napp.configure('stage', 'prod', function(){\n  // config\n});\n```\n\n对于任何内部设置（[#](https://twitter.com/sofish/status/219430499725217794))，Express 提供了 `set(key[, val])`、 `enable(key)` 和 `disable(key)` 方法:\n\n\u003cmark\u003e译注：\u003c/mark\u003e设置详见：[application.js](https://github.com/visionmedia/express/blob/master/lib/application.js) 的 `app.set`。\n\n```js\n app.configure(function(){\n    app.set('views', __dirname + '/views');\n    app.set('views');\n    // =\u003e \"/absolute/path/to/views\"\n    \n    app.enable('some feature');\n    // 等价于：app.set('some feature', true);\n    \n    app.disable('some feature');\n    // 等价于：app.set('some feature', false);\n \n    app.enabled('some feature')\n    // =\u003e false\n });\n```\n变更环境我们可以设置 `NODE_ENV` 环境变量，如：\n\n```bash\n$ NODE_ENV=production node app.js\n```\n\n这非常重要，因为多数缓存机制只在产品阶段是被打开的。\n\n\n### 设置\n\nExpress 支持下列快捷（out of the box）设置:\n\n* `basepath` 用于 `res.redirect()` 的应用程序基本路径（base path），显式地处理绑定的应用程序（transparently handling mounted apps.）\n* `view` View 默认的根目录为 **CWD/views**\n* `view engine` 默认 View 引擎处理（View 文件）并不需要使用后缀\n* `view cache` 启用 View 缓存 (在产品阶段被启用)\n* `charet` 改变编码，默认为 utf-8\n* `case sensitive routes` 路由中区分大小写\n* `strit routing` 启用后（路由中的）结尾 `/` 将不会被忽略（译注：即 `app.get('/sofish')` 和 `app.get('/sofish/')` 将是不一样的）\n* `json callback` 启用 `res.send()` / `res.json()` 显式的 jsonp 支持（transparent jsonp support）\n\n### 路由\n\nExpress 利用 HTTP 动作提供一套提示性强、有表现力的路由 API。打个比方，如果想要处理某个路径为 _/user/12_ 的账号，我们能像下面这样来定义路由。关联到命名占位符（named placeholders）的值可用 `req.params` 来访问。\n\n```js\napp.get('/user/:id', function(req, res){\n    res.send('user ' + req.params.id);\n});\n```\n\n路由是一个在内部被编译为正则的字符串。譬如，当 _/user/:id_ 被编译，一个简化版本的正则表达弄大概如下：\n\n```js\n// 修改一下官方的这个字符串\n/\\/user\\/([^\\/]+)\\/?/\n```\n\n正则表达式可以传入应用于复杂的场景。由于通过字面量正则表达式捕获的内容组是匿名的，我们可能直接通过 `req.params` 来访问它们。因此，我们捕获的第一组内容将是 `req.params[0]`，同时第二组是紧接着的 `req.params[1]`。 \n\n```js\napp.get(/^\\/users?(?:\\/(\\d+)(?:\\.\\.(\\d+))?)?/, function(req, res){\n    res.send(req.params);\n});\n```\n\nCurl 针对上述定义路由的请求：\n\n```bash\n$ curl http://dev:3000/user\n[null,null]\n$ curl http://dev:3000/users\n[null,null]\n$ curl http://dev:3000/users/1\n[\"1\",null]\n$ curl http://dev:3000/users/1..15\n[\"1\",\"15\"]\n```\n下面是一些路由的实例，关联到他们可能使用到的路径：\n\n```bash\n\"/user/:id\"\n/user/12\n\n\"/users/:id?\"\n/users/5\n/users\n\n\"/files/*\"\n/files/jquery.js\n/files/javascripts/jquery.js\n\n\"/file/*.*\"\n/files/jquery.js\n/files/javascripts/jquery.js\n\n\"/user/:id/:operation?\"\n/user/1\n/user/1/edit\n\n\"/products.:format\"\n/products.json\n/products.xml\n\n\"/products.:format?\"\n/products.json\n/products.xml\n/products\n\n\"/user/:id.:format?\"\n/user/12\n/user/12.json\n```\n\n举个例子，我们可以使用 `POST` 发送 json 数据，通过 `bodyParser` 这个可以解析 json 请求内容（或者其他内容）的中间件来返回数据，并将返回结果存于 `req.body` 中：\n\n```js\nvar express = require('express')\n  , app = express.createServer();\n\napp.use(express.bodyParser());\n\napp.post('/', function(req, res){\n  res.send(req.body);\n});\n\napp.listen(3000);\n```\n\n通常我们可以使用一个像 `user/:id` 这样，没有（命名）限制的“傻瓜”式的占位符。然而比方说，我们要限制用户 id 只能是数字，那么我们可能使用 `/user/:id([0-9]+)`，这个将仅当占位符是包含至少一位数字时才生效（适配，match）。\n\n### 进路控制（Passing Route Control）\n\n我们可以通过调用第三个参数，`next()` 函数，来控制下一个适配的路由。如果找不到适配，控制权将会传回给 Connect，同时中间件将会按在 `use()` 中添加的顺序被依次调用。道理同样适应于多个定义到同一路径的路由，他们将会依次被调用直到其中某个不调用 `next()` 而决定做出请求响应。\n\n```js\napp.get('/users/:id?', function(req, res, next){\n\tvar id = req.params.id;\n\tif (id) {\n\t\t// do something\n\t} else {\n\t\tnext();\n\t}\n});\n\napp.get('/users', function(req, res){\n\t// do something else\n});\n```\n\n`app.all()` 方法只调用一次就可以方便地把同样的逻辑到所有 HTTP 动作。下面我们使用它来从伪数据中提取一个用户，将其赋给 `req.user`。\n\n```js\nvar express = require('express')\n  , app = express.createServer();\n\nvar users = [{ name: 'tj' }];\n\napp.all('/user/:id/:op?', function(req, res, next){\n  req.user = users[req.params.id];\n  if (req.user) {\n    next();\n  } else {\n    next(new Error('cannot find user ' + req.params.id));\n  }\n});\n\napp.get('/user/:id', function(req, res){\n  res.send('viewing ' + req.user.name);\n});\n\napp.get('/user/:id/edit', function(req, res){\n  res.send('editing ' + req.user.name);\n});\n\napp.put('/user/:id', function(req, res){\n  res.send('updating ' + req.user.name);\n});\n\napp.get('*', function(req, res){\n  res.send(404, 'what???');\n});\n\napp.listen(3000); \n```\n\n### 中间件\n\n使用的 [Connect](http://github.com/senchalabs/connect) 中间件（属性）通常伴随着你的一个常规 Connect 服务器，被传到 `express.createServer()` 。如：\n\n```js\nvar express = require('express');\n\nvar app = express.createServer(\n  \t  express.logger()\n  \t, express.bodyParser()\n  );\n```\n\n另外，在 `configure()` 块内 —— 这个渐进式的宫殿（译注：笑^^，in a progressive manner），我们还可以方便地使用 `use()` 来添加中间件。\n\n```js\napp.use(express.logger({ format: ':method :url' }));\n```\n\n通常，使用 connect 中间件你可能会用到 `require('connect')`，像这样：\n\n```js\nvar connect = require('connect');\napp.use(connect.logger());\napp.use(connect.bodyParser());\n```\n\n这在某种程度上来说有点不爽，所以 express 重导出（re-exports）了这些中间件属性，尽管他们是一样的:\n\n```js\napp.use(express.logger());\napp.use(express.bodyParser());\n```\n\n中间件的顺序非常重要，当 Connect 收到一个请求，我们传到 `createServer()` 或者 `use()` 执行的第一个中间件将附带三个参数，request、response，以及一个回调函数（通常是 `next`）。当 `next()` 被调用，将轮到第二个中间件，依此类推。之所以说这是值得注意的，是因为很多中间件彼此依赖，例如 `methodOverride()` 查询 `req.body` 方法来检测 HTTP 方法重载，另一方面 `bodyParser()` 解析请求内容并将其于寄存于 `req.body`。另一个例子是 cookie 解析和 session 支持，我们必须先 `use()` `cookieParser()` 紧接着 `session()`。\n\n很多 Express 应用都包含这样的一行 `app.use(app.router)`，这看起来可能有点奇怪，其实它仅仅是一个包含所有定义路由规则，并执行基于现有 URL 请求和 HTTP 方法路由查找的一个中间件功能。Express 允许你决定其位置（to position），不过默认情况下它被放置于底部。通过改变路由的位置，我们可以改变中间件的优先级，譬如我们想把错误报告做为最后的中间件，以便任何传给 `next()` 的异常都可以通过它来处理；又或者我们希望静态文件服务优先级更低，以允许我们的路由可以监听单个静态文件请求的下载次数，等等。这看起来差不多是这样的：\n\n```js\napp.use(express.logger(...));\napp.use(express.bodyParser(...));\napp.use(express.cookieParser(...));\napp.use(express.session(...));\napp.use(app.router);\napp.use(express.static(...));\napp.use(express.errorHandler(...));\n```\n\n首先我们添加 `logger()`，它可能包含 node 的 `req.end()` 方法，提供我们响应时间的数据。接下来请求的内容将会被解析（如果有数据的话），紧接着的是 cookie 解析和 session 支持，同时 `req.session` 将会在触发 `app.router` 中的路由时被定义，这时我们并不调用 `next()`，因此 `static()` 中间件将不会知道这个请求，如若已经定义了如下一个路由，我们则可以记录各种状态、拒绝下载和消耗下载点数等。\n\n```js\nvar downloads = {};\n\napp.use(app.router);\napp.use(express.static(__dirname + '/public'));\n\napp.get('/*', function(req, res, next){\n  var file = req.params[0];\n  downloads[file] = downloads[file] || 0;\n  downloads[file]++;\n  next();\n});\n```\n\n### 路由中间件\n\n路由可以利用路由器中间件，传递一个以上的回调函数（或者数组）到其方法中。这个特性非常有利于限制访问、通过路由下载数据，等等。\n\n通常异步数据检索看起来可能像下例，我们使用 `:id` 参数，尝试加载一个用户： \n\n```js\napp.get('/user/:id', function(req, res, next){\n  loadUser(req.params.id, function(err, user){\n    if (err) return next(err);\n    res.send('Viewing user ' + user.name);\n  });\n});\n```\n\n为保证 DRY 原则和提升可读，我们可以把这个逻辑应用于一个中间件内。如下所示，抽象这个逻辑到中间件内将允许你重用它，同时保证了我们路由的简洁。\n\n```js\nfunction loadUser(req, res, next) {\n  // You would fetch your user from the db\n  var user = users[req.params.id];\n  if (user) {\n    req.user = user;\n    next();\n  } else {\n    next(new Error('Failed to load user ' + req.params.id));\n  }\n}\n\napp.get('/user/:id', loadUser, function(req, res){\n  res.send('Viewing user ' + req.user.name);\n});\n```\n\n多重路由可以，并按顺序应用到更深一层的逻辑，如限制一个用户账号的访问。下面的例子只允许通过鉴定的用户才可以编辑他（她）的账号。\n\n```js\nfunction andRestrictToSelf(req, res, next) {\n  req.authenticatedUser.id == req.user.id\n    ? next()\n    : next(new Error('Unauthorized'));\n}\n\napp.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){\n  res.send('Editing user ' + req.user.name);\n});\n```\n\n时刻铭记路由只是简单的函数，如下所示，我们可以定义返回中间件的函数以创建一个更具表现力，更灵活的方案。\n\n```js\nfunction andRestrictTo(role) {\n  return function(req, res, next) {\n    req.authenticatedUser.role == role\n      ? next()\n      : next(new Error('Unauthorized'));\n  }\n}\n\napp.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){\n  res.send('Deleted user ' + req.user.name);\n});\n```\n\n常用的中间件“堆栈”可以通过一个数组来传递（会被递归应用），这些中间件可以混着、匹配到任何层次（which can be mixed and matched to any degree）。\n\n```js\nvar a = [middleware1, middleware2]\n  , b = [middleware3, middleware4]\n  , all = [a, b];\n\napp.get('/foo', a, function(){});\napp.get('/bar', a, function(){});\n\napp.get('/', a, middleware3, middleware4, function(){});\napp.get('/', a, b, function(){});\napp.get('/', all, function(){});\n```\n\n对于这个实例的完整代码，请看 [route middleware example](http://github.com/visionmedia/express/blob/master/examples/route-middleware/app.js) 这个仓库。\n\n我们可能会有多次想要“跳过”剩余的路由中间件，继续匹配后续的路由。做到这点，我们只需调用 `next()` 时带上 `'route'` 字符串 —— `next('route')`。如果没有余下的路由匹配到请求的 URL，Express 将会返回 `404 Not Found`。\n\n\n### HTTP 方法\n\n至此已接触了好几次 `app.get()`，除此这外 Express 还提供了其他常见的 HTTP 动作，如 `app.post()` 、`app.del()` 等等。\n\nPOST 用法的一个常用例子是提交一个表单。下面我们简单地在 html 中把表单的 method 属性设置为 post，控制权将会指派给它下面所定义的路由。\n\n```html\n\u003cform method=\"post\" action=\"/\"\u003e\n     \u003cinput type=\"text\" name=\"user[name]\" /\u003e\n     \u003cinput type=\"text\" name=\"user[email]\" /\u003e\n     \u003cinput type=\"submit\" value=\"Submit\" /\u003e\n\u003c/form\u003e\n```\n\n默认上 Express 并不知道如何处理这个请求的内容，因此我们必须添加 `bodyParser` 中间件，它将解析 `application/x-www-form-urlencoded` 和 `application/json` 请求的内容，并把变量存放于 `req.body` 中。我们可以像下述示例一样来使用这个中间件：\n\n```js\napp.use(express.bodyParser());\n```\n\n如下，我们的路由将有权访问 `req.body.user` 对象，当有 name 和 email 被定义时它将包含这两个属性（译注：如果表单发送的内容不为空的话）。\n\n```js\napp.post('/', function(req, res){\n  console.log(req.body.user);\n  res.redirect('back');\n});\n```\n\n当想在一个表单中使用像 PUT 这样的方法，我们可以使用一个命名为 `_method` 的 hidden `input`，它可以用以修改 HTTP 方法。为了做这个，我们首先需要 `methodOverride` 中间件，它必须出现于 `bodyParser` 后面，以便使用它的 `req.body`中所包含的表单值。\n\n```js\napp.use(express.bodyParser());\napp.use(express.methodOverride());\n```\n\n对于这些方法为何不是默认拥有，简单来说只是因为它并不是 Express 所要求完整功能所必须。方法的使用依赖于你的应用，你可能并不需要它们，客户端依然能使用像 PUT 和 DELETE 这样的方法，你可以直接使用它们，因为 `methodOverride` 为 form 提供了一个非常不错的解决方案。下面将示范如何使用 PUT 这个方法，看起来可能像：\n\n```html\n\u003cform method=\"post\" action=\"/\"\u003e\n  \u003cinput type=\"hidden\" name=\"_method\" value=\"put\" /\u003e\n  \u003cinput type=\"text\" name=\"user[name]\" /\u003e\n  \u003cinput type=\"text\" name=\"user[email]\" /\u003e\n  \u003cinput type=\"submit\" value=\"Submit\" /\u003e    \n\u003c/form\u003e\n\napp.put('/', function(){\n    console.log(req.body.user);\n    res.redirect('back');\n});\n```\n\n### 错误处理\n\nExpress 提供了 `app.error()` 方法以便接收到的异常在一个路由里抛出，或者传到 `next(err)` 中。下面这个例子将基于特定的 NotFound 异常处理不同的页面：\n\n```js\nfunction NotFound(msg){\n  this.name = 'NotFound';\n  Error.call(this, msg);\n  Error.captureStackTrace(this, arguments.callee);\n}\n\nNotFound.prototype.__proto__ = Error.prototype;\n\napp.get('/404', function(req, res){\n  throw new NotFound;\n});\n\napp.get('/500', function(req, res){\n  throw new Error('keyboard cat!');\n});\n```\n\n如下述，我们可以多次调用 `app.error()`。这里我们检测 NotFound 的实例，并显示 404 页面，或者传到 `next` 错误处理器。值得注意的是这些处理器可以在任何地方定义，因为他们将会在 `listen()` 的时候被放置于路由处理器下面。它允许在 `configure()` 块内有定义，以便我们能基于环境用不同的异常处理方式。\n\n```js\napp.error(function(err, req, res, next){\n    if (err instanceof NotFound) {\n        res.render('404.jade');\n    } else {\n        next(err);\n    }\n});\n```\n\n为求简洁（for the simplicity），这里我们假定这个 demo 的所有错误为 500，当然你可以可以选择自己喜欢的。像 node 执行文件系统的系统调用时，你可能会接收到一个带有 ENOENT 的 `error.code`，意思为 “不存在这样的文件或目录” 的错误，我们可以在错误处理器中使用，或者当有需要时可显示一个指定的页面。\n\n```js\napp.error(function(err, req, res){\n  res.render('500.jade', {\n     error: err\n  });\n});\n```\n\n我们的 app 同样可以利用 Connect 的 `errorHandler` 中间件来汇报异常。譬如当我们希望在 “开发” 环境输出 `stderr` 异常时，我们可以使用：\n\n```js\napp.use(express.errorHandler({ dumpExceptions: true }));\n```\n\n同时在开发阶段我们可能需要在花哨的 HTML 页面显示我们传递和抛出的异常，对此我们可以把 `showStack` 设置为 `true`。\n\n```js\napp.use(express.errorHandler({ showStack: true, dumpExceptions: true }));\n```\n\n`errorHandler` 中间件还可以在 `Accept: application/json` 存在的时候返回 json，这对于开发重度依赖客户端 Javascript 的应用非常有用。\n\n### Route 参数预处理\n\n路由参数预处理，通过隐式数据加载和请求验证，可以大大提升你程序的可读性。打个比方，你通常需要持续地从多个路由获取基本数据。像用 `/user/:id` 加载一个用户，通常来说我们可能会这样干：\n\n```js\napp.get('/user/:userId', function(req, res, next){\n  User.get(req.params.userId, function(err, user){\n    if (err) return next(err);\n    res.send('user ' + user.name);\n  });\n}); \n```\n\n通过预处理，我们的参数可以映射到执行验证、控制(coercion)，甚至从数据库加载数据的回调。如下我们带着参数名调用 `app.param()` 希望将其映射于某些中间件。如你所见，我们接受代表占位符值的 `id` 参数。使用这个，我们如常加载用户并处理错误，以及简单地调用 `next()` 来把控制权交由下一个预处理或者路由处理器。\n\n```js\napp.param('userId', function(req, res, next, id){\n  User.get(id, function(err, user){\n    if (err) return next(err);\n    if (!user) return next(new Error('failed to find user'));\n    req.user = user;\n    next();\n  });\n});\n```\n\n一旦这样做，上所述将会大大地提升路由的可读性，并且允许我们轻松地在整个程序中共享逻辑：\n\n```js\napp.get('/user/:userId', function(req, res){\n  res.send('user ' + req.user.name);\n});\n```\n\n### View 处理\n\nView 文件件使用 \u0026lt;name\u0026gt;.\u0026lt;engine\u0026gt; 这样的格式，其中 \u0026lt;engine\u0026gt; 是被 `require` 进来模块的名。例如 `layout.ejs` 将告诉 view 系统去 `require('ejs')`，被加载的模块必须(导出) `exports.compile(str, options)` 方法，并返回一个 Function 来适应 Express。`app.register()` 可用以改变这种默认行为，将文件扩展名映射到特定的引擎。譬如 “foo.html” 可以由 ejs 来处理。\n\n下面这个例子使用 [Jade](http://github.com/visionmedia/jade) 来处理 index.html。因为我们并未使用 `layout: false`，index.jade 处理后的内容将会被传入到 layout.jade 中一个名为 body 的本地变量。\n\n```js\napp.get('/', function(req, res){\n\tres.render('index.jade', { title: 'My Site' });\n});\n```\n\n新的 `view engine` 设置允许我们指定默认的模板引擎，例如当我们使用 jade 时可以这样设置：\n\n```js\napp.set('view engine', 'jade');\n```\n\n允许我们这样处理：\n\n```\nres.render('index');\n```\n\n对应于：\n\n```js\nres.render('index.jade');\n```\n\n当 `view engine` 被设定，扩展名实属可选，但我们依然可以混着匹配模板引擎：\n\n```js\nres.render('another-page.ejs');\n```\n\nExpress 同时还提供了 `view options` 设置，这将应用于一个 view 每次被渲染的时候，譬如你不希望使用 layouts  的时候可能会这样做：\n\n```js\napp.set('view options', {\n\tlayout: false\n});\n```\n\n在需要的时候，这可以在 `res.render()` 调用的内部进行重载：\n\n```js\nres.render('myview.ejs', { layout: true });\n```\n\n当有需要变更一个 layout，我们通常需要再指定一个路径。譬如当我们已经把 `view engine` 设置为 jade，并且这个文件命名为 ./views/mylayout.jade，我们可以这样简单地进行传参：\n\n```js\nres.render('page', { layout: 'mylayout' });\n```\n\n否则（译注：没有把 `view engine` 设置为 jade 或者其他的引擎时），我们必须指定一个扩展名：\n\n```js\nres.render('page', { layout: 'mylayout.jade' });\n```\n\n它们同样可以是绝对路径：\n\n```js\nres.render('page', { layout: __dirname + '/../../mylayout.jade' });\n```\n\n对于这点有一个不错的例子 —— 自定义 ejs 的起始和闭合标签：\n\n```js\napp.set('view options', {\n\topen: '{{',\n\tclose: '}}'\n})\n```\n\n### View 部件\n\nExpress 的 view 系统内置了部件（partials） 和集合器（collections）的支持，相当于用一个 “迷你” 的 view 替换一个文档碎片（document fragment）。示例，在一个 view 中重复渲染来显示评论，我们可以使用部件集：\n\n```js\npartial('comment', { collection: comments });\n```\n\n如果并不需要其他选项或者本地变量，我们可以省略整个对象，简单地传进一个数组，这与上述是等价的：\n\n```js\npartial('comment', comments);\n```\n\n在使用中，部件集无偿地提供了一些 “神奇” 本地变量的支持：\n\n* _firstInCollection_  `true`，当它是第一个对象的时候\n* _indexInCollection_  在集合器对象中的索引\n* _lastInCollection_  `true`，当它是最后一个对象的时候\n* _collectionLength_  集合器对象的长度\n\n本地变量的传递（生成）具备更高的优先级，同时，传到父级 view 的本地变量对于子级 view 同样适应。例如当我们用 `partial('blog/post', post)` 来渲染一个博客文章，它将会生成一个 `post` 本地变量，在调用这个函数的 view 中存在本地变量 `user`，它将同样对 `blog/post` 有效。（译注：这里 partial 比较像 php 中的 include 方法）。\n\n__注意:__ 请谨慎使用部件集合器，渲染一个长度为 100 的部件集合数组相当于我们需要处理 100 个 view。对于简单的集合，最好重复内置，而非使用部件集合器以避免开销过大。\n\n### View 查找\n\nView 查找相对于父级 view （路径）执行，如我们有一个 view 页面叫作 views/user/list.jade，并且在其内部写有 `partial('edit')` 则它会尝试加载 views/user/edit.jade，同理 `partial('../messages')` 将会加载 views/messages.jade。\n\nView 系统还支持模板索引，允许你使用一个与 view 同名的目录。例如在一个路由中，`res.render('users')` 得到的非 views/users.jade 即 views/users/index.jade。（译注：先处理 \u0026lt;path\u0026gt;.\u0026lt;engine\u0026gt; 的情况，再处理 \u0026lt;path\u0026gt;/\u0026lt;index.\u0026lt;engine\u0026gt; 的情况，详情可见 [view.js](https://github.com/visionmedia/express/blob/master/lib/view.js)。）\n\n当使用上述 view 索引，我们在与 view 同一个目录下，使用 `partial('users')` 中引用 views/users/index.jade，与此同时 view 系统会尝试索引 `../users/index`，而无须我们调用 `partial('users')`。\n\n### Template Engines\n\n下列为 Express 最常用的模板引擎:\n\n* [Haml](http://github.com/visionmedia/haml.js) haml 实现\n* [Jade](http://jade-lang.com) haml.js 继位者\n* [EJS](http://github.com/visionmedia/ejs) 嵌入式 JavaScript\n* [CoffeeKup](http://github.com/mauricemach/coffeekup) 基于 CoffeeScript 的模板\n* [jQuery Templates](https://github.com/kof/node-jqtpl) \n\n### Session 支持\n\nSession 支持可以通过使用 Connect 的 session 中间件来获得，为此通常我们同时需要在其前加上 `cookieParser` 中间件，它将解析和存储 cookie 数据于 `req.cookies` 中。\n\n```js\napp.use(express.cookieParser());\napp.use(express.session({ secret: \"keyboard cat\" }));\n```\n\n默认情况下 session 中间件使用 Connect 内置的内存存储，然而还有其他多种实现方式。如 [connect-redis](http://github.com/visionmedia/connect-redis) 提供了一种 [Redis](http://code.google.com/p/redis/) 的 session 存储，它这可像下面这样被使用：\n\n```js\nvar RedisStore = require('connect-redis')(express);\napp.use(express.cookieParser());\napp.use(express.session({ secret: \"keyboard cat\", store: new RedisStore }));\n```\n\n至此，`req.session` 和 `req.sessionStore` 属性将可以被所有路由和后继的中间件使用。在 `req.session` 上的所有属性都会在一个响应中被自动保存下来，譬如当我们想要添加数据到购物车： \n\n```js\nvar RedisStore = require('connect-redis')(express);\napp.use(express.bodyParser());\napp.use(express.cookieParser());\napp.use(express.session({ secret: \"keyboard cat\", store: new RedisStore }));\n\napp.post('/add-to-cart', function(req, res){\n  // 我们可能通过一个表单 POST 出多个 item\n  // (在些使用 bodyParser() 中间件)\n  var items = req.body.items;\n  req.session.items = items;\n  res.redirect('back');\n});\n\napp.get('/add-to-cart', function(req, res){\n  // 当返回时，页面 GET /add-to-cart\n  // 我们可以检查 req.session.items \u0026\u0026 req.session.items.length\n  // 来打印出提示\n  if (req.session.items \u0026\u0026 req.session.items.length) {\n    req.notify('info', 'You have %s items in your cart', req.session.items.length);\n  }\n  res.render('shopping-cart');\n});\n```\n\n对于 `req.session` 对旬，它还有像 `Session#touch()`、`Session#destroy()`、 `Session#regenerate()` 等用以维护和操作 session 的方法。更多的详情请看 [Connect Session](http://senchalabs.github.com/connect/middleware-session.html) 的文档。\n\n### 升级指南\n\n对于使用 Express 1.x 的同学，如果你有很重要的程序需要升级到 2.x 以获得更好的支持，请看官方非常详细的 [迁移指南](http://expressjs.com/guide.html#migration-guide)\n\n----------------------------------------------------------\n\n最后，感谢一下自己，又长进了。读到这里的你，如果感觉它对你有帮助，请同时分享给你有需要的朋友吧～\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsofish%2Fexpress-guide","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsofish%2Fexpress-guide","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsofish%2Fexpress-guide/lists"}