当前位置:   article > 正文

Express项目结构_express 文件结构

express 文件结构

使用 Express 应用生成器创建一个应用骨架

需要在命令行运行“生成器 + 项目名称”即可,此外还可以指定站点的模板引擎和 CSS 生成器。

$ express --help

  用法:express [选项] [目录]

  选项:

        --version        打印版本号
    -e, --ejs            添加 ejs 引擎支持
        --pug            添加 pug 引擎支持
        --hbs            添加 handlebars 引擎支持
    -H, --hogan          添加 hogan.js 引擎支持
    -v, --view <engine>  添加 <engine> 视图引擎支持 (ejs|hbs|hjs|jade|pug|twig|vash) (默认为 jade)
    -c, --css <engine>   添加 <engine> 样式表引擎支持 (less|stylus|compass|sass) (默认为纯 css)
        --git            添加 .gitignore
    -f, --force          对非空文件夹强制执行
    -h, --help           打印帮助信息
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

可以直接运行 express 命令,将使用 Jade 视图引擎和纯 CSS 在当前目录中创建项目。(如果指定目录名,则在子目录中创建项目)。

还可以使用 --view 选择视图(模板)引擎,并且/或者使用 --css 选择 CSS 生成引擎。

不推荐用 --hogan、–ejs、–hbs 等参数选用模板引擎。请使用 --view(或 -v)。

Express 应用生成器支持多款流行的视图/模板引擎,包括 EJS、Hbs、Pug (Jade)、Twig 和 Vash,缺省选项是 Jade。Express 本身也支持大量其他模板语言,开箱即用。

本项目选用 Pug 模板引擎(Jade 是它不久前的曾用名),它是最流行的 Express / JavaScript 模板语言之一,且对 Express 生成器 开箱即用。

Express 应用生成器支持最常见的 CSS 引擎:LESS, SASS, Compass, Stylus。

生成器生成的代码不使用、也不包含任何数据库。Express 应用可以使用 Node 支持的所有数据库(Express 本身不提供数据库管理机制)。

创建一个名为 express-tutorial 的项目,使用 Pug 模板库,不使用 CSS 引擎。

首先,进入准备放置项目的目录,然后在命令提示符运行 Express 应用生成器,生成器将创建(并列出)项目的文件:

jwdmac2@QideiMac first-project % mkdir express-tutorial
jwdmac2@QideiMac first-project % cd express-tutorial
jwdmac2@QideiMac express-tutorial % express --view=pug

   create : public/
   create : public/javascripts/
   create : public/images/
   create : public/stylesheets/
   create : public/stylesheets/style.css
   create : routes/
   create : routes/index.js
   create : routes/users.js
   create : views/
   create : views/error.pug
   create : views/index.pug
   create : views/layout.pug
   create : app.js
   create : package.json
   create : bin/
   create : bin/www

   install dependencies:
     $ npm install

   run the app:
     $ DEBUG=express-tutorial:* npm start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

生成器在最后还告诉你如何安装(package.json 中所列的)依赖,以及如何运行该应用。

安装依赖项(install 命令将获取项目的 package.json 文件中列出的所有依赖项包)。

npm install
  • 1

然后运行该应用。

DEBUG=express-tutorial:* npm start
  • 1

最后在浏览器中导航至 http://localhost:3000/ ,就可以访问该应用。你应该可以看到:

在这里插入图片描述
一个 Express 应用就配置成功了,它托管于 localhost:3000。

目录结构

安装好依赖项的生成项目具有如下文件结构(不带“/”前缀的是文件):

/express-tutorial
    app.js
    /bin
        www
    package.json
    /node_modules
        [4,500 个子文件夹和文件]
    /public
        /images
        /javascripts
        /stylesheets
            style.css
    /routes
        index.js
        users.js
    /views
        error.pug
        index.pug
        layout.pug
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

如图

在这里插入图片描述
package.json 文件定义依赖项和其他信息,以及一个调用应用入口(/bin/www,一个 JavaScript 文件)的启动脚本,脚本中还设置了一些应用的错误处理,加载 app.js 来完成其余工作。/routes 目录中用不同模块保存应用路由。/views 目录保存模板。

下面来详细介绍这些文件。

package.json

package.json 文件中定义了应用依赖和其他信息:

{
  "name": "express-tutorial",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "express": "~4.16.1",
    "http-errors": "~1.6.3",
    "morgan": "~1.9.1",
    "pug": "2.0.0-beta11"
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
依赖包括 express 包,和选用的视图引擎包(pug)。还有以下一些实用的包:

cookie-parser:用于解析 cookie 头来填充 req.cookies(提供了访问 cookie 信息的便捷方法)。

debug:一个小型 node 调试程序,仿照 node 核心的调试技术建立。

http-errors:处理错误中间件。

morgan:node 专用 HTTP 请求记录器中间件。

“scripts” 部分

定义了一个 “start” 脚本,当运行 npm start 时会调用它来启动服务器。在脚本定义中可以看到 start 实际上运行了 “node ./bin/www”。

还有一个 “devstart” 脚本,可以通过运行 npm run devstart 来运行 “nodemon ./bin/www”。

  "scripts": {
    "start": "node ./bin/www",
    "devstart": "nodemon ./bin/www"
  },
  • 1
  • 2
  • 3
  • 4

www 文件

文件 /bin/www 是应用入口!它做的第一件事是 require() “真实”的应用入口(即项目根目录中的 app.js ),app.js 会设置并返回 express()应用对象。

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('express-tutorial:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

注: require() 是一个全局的 node 函数,可将模块导入当前文件。这里使用相对路径指定 app.js 模块,并省略了 .js 扩展名(可选)。

文件的其余部分先为 app 设置端口(环境变量中的预定义值或默认值 3000),再创建一个 HTTP 服务器,然后开始监听请求,报告服务器错误和连接信息。其他内容可暂时忽略(这里所有内容都是机器生成的模板)。

app.js

此文件创建一个 express 应用对象(依照惯例命名为 app),通过各种设置选项和中间件来设置这个应用,然后从该模块中导出。它可以进行路由 HTTP 请求、配置中间件、渲染 HTML 视图、注册模板引擎以及修改 应用程序设置 等操作,从而控制应用的行为(例如 环境模式,路由定义是否为区分大小写等)。

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

上文的 www 入口文件中 require() 的 app 就是这里导出的。

我们来详细了解一下 app.js 文件。首先,它使用 require() 导入了一些实用 node 库,其中包括之前用 NPM 下载的 express、http-errors、morgan 和 cookie-parser,还有一个 path 库,它是用于解析文件和目录的核心 node 库。

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
  • 1
  • 2
  • 3
  • 4
  • 5

然后 require() 的是用户路由目录中的模块。这些模块/文件用于处理特定的“路由”(URL 路径)。可以通过添加新文件来扩展骨架应用。

app.use('/', indexRouter);
app.use('/users', usersRouter);
  • 1
  • 2

此时我们刚刚导入了模块;还没有真正使用过其中的路由(稍后会使用)。

下面我们用导入的 express 模块来创建 app 对象,然后使用它来设置视图(模板)引擎。设置引擎分两步:首先设置 ‘views’ 以指定模板的存储文件夹(此处设为子文件夹 /views)。然后设置 ‘view engine’ 以指定模板库(本例中设为“pug” )。

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
  • 1
  • 2
  • 3
  • 4
  • 5

下一组 app.use() 调用将中间件库添加进请求处理链。除了之前导入的第三方库之外,我们还使用 express.static 中间件将项目 /public 目录下所有静态文件托管至根目录。

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
  • 1
  • 2
  • 3
  • 4
  • 5

所有中间件都已设置完毕,现在把(之前导入的)路由处理器添加到请求处理链中。从而为网站的不同部分定义具体的路由:

app.use('/', indexRouter);
app.use('/users', usersRouter);
  • 1
  • 2

这些路径(‘/’ 和 ‘/users’)将作为导入路由的前缀。如果导入的模块 users 在 /profile 定义了路由,则可以在 /users/profile 访问该路由。

最后一个中间件为错误和 HTTP 404 响应添加处理方法。

// catch 404 and forward to error handler
// 捕获 404 并抛给错误处理器
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
// 错误处理器
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  // 设置 locals,只在开发环境提供错误信息
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  // 渲染出错页面
  res.status(err.status || 500);
  res.render('error');
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

Express 应用对象(app)现已完成配置。最后一步是将其添加到 exports 模块(使它可以通过 /bin/www 导入)。

module.exports = app;
  • 1

routes

路由

路由文档 /routes/users.js 如下所示(由于路由文件均使用类似结构,所以 index.js 略过)。

var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});

module.exports = router;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

首先加载 express 模块 并获取 express.Router 对象(命名为 router)。然后为 router 指定路由,最后导出 router(就可以导入 app.js 了)。

该路由定义了一个回调,在检测到正确模式的 HTTP GET 请求时将调用该回调。正确模式即导入模块时指定的路由(‘/users’)加该模块(‘/’)中定义的任何内容。换句话说,在收到 /users/ URL 时使用此路由。

用 node 启动该应用并访问 http://localhost:3000/users/,浏览器会返回一条消息:‘respond with a resource’。

值得注意的是,上述回调函数有第三个参数 ‘next’,因此它是一个中间件函数,而不是简单的路由回调。next 参数暂时还用不到,在 ‘/’ 路径中添加多个路由处理器时才会涉及。

views

视图(模板)

视图(模板)存保存在 /views 目录中( app.js 中指定),使用 .pug 扩展名。 Response.render() 方法用某对象的某个变量值一同来渲染一个特定的模板,然后将结果作为响应发送。在 /routes/index.js 中可以看到,该路由使用 ‘index’ 模板和一个模板变量 title 来渲染响应。

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

module.exports = router;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

以下是上文代码中涉及到的模板(index.pug)。pug 语法稍后再详细讨论。现在只需要知道:title 变量将以 ‘Express’ 作为值插入模板的指定位置。

extends layout

block content
  h1= title
  p Welcome to #{title}
  • 1
  • 2
  • 3
  • 4
  • 5

语法结构

导入和创建模块

模块是 JavaScript 库或文件,可以用 Node 的 require() 函数将它们导入其他代码。Express 本身就是一个模块,Express 应用中使用的中间件和数据库也是。

下面的代码以 Express 框架为例展示了如何通过名字来导入模块。首先,调用 require() 函数,用字符串(‘express’)指定模块的名字,然后调用返回的对象来创建 Express 应用。然后就可以访问应用对象的属性和函数了。

const express = require('express');
const app = express();
  • 1
  • 2

还可以创建自定义模块,并用相同的方法导入。

为了让对象暴露于模块之外,只需把它们设置为 exports 对象的附加属性即可。例如,下面的 square.js 模块就是一个导出了 area() 和 perimeter() 方法的文件:

exports.area = width => { return width * width; };
exports.perimeter = width => { return 4 * width; };
  • 1
  • 2

可以用 require() 导入这个模块,然后调用导出的方法,用法如下:

const square = require('./square');
// 这里 require() 了文件名,省略了 .js 扩展名(可选)
console.log('边长为 4 的正方形面积为 ' + square.area(4));
  • 1
  • 2
  • 3

为模块指定绝对路径(或模块的名字)也是可行的。

一次赋值不仅能构建一个单一的属性,还能构建一个完整的对象,可以像下面这样把对象赋值给 module.exports(也可以让 exports 对象直接作为一个构造器或另一个函数):

module.exports = {
  area: width => { return width * width; },
  perimeter: width => { return 4 * width; }
};
  • 1
  • 2
  • 3
  • 4

在一个既定的模块内,可以把 exports 想象成 module.exports 的 快捷方式。exports 本质上就是在模块初始化前为 module.exports 的值进行初始化的一个变量。这个值是对一个对象(这里是空对象)的引用。这意味着 exports 与 module.exports 引用了同一个对象,也意味着如果为 exports 赋其他值不会影响到 module.exports。

使用异步 API

JavaScript 代码在完成那些需要一段时间才能完成的操作时,经常会用异步 API 来取代同步 API。同步 API 下,每个操作完成后才可以进行下一个操作。例如,下列日志函数是同步的,将按顺序将文本打印到控制台(第一、第二)。

console.log('第一');
console.log('第二');
  • 1
  • 2

而异步 API 下,一个操作开始后(在其完成之前)会立即返回。一旦操作完成,API 将使用某种机制来执行附加操作。例如,下面的代码将打印“第二、第一”。这是因为虽然先调用了 setTimeout() 方法并立即返回,但它的操作到 3 秒后才完成。

setTimeout(() => {
  console.log('第一');
}, 3000);
console.log('第二');
  • 1
  • 2
  • 3
  • 4

在 Node 中使用无阻塞异步 API 甚至比在浏览器中更为重要,这是因为 Node 是一个单线程事件驱动的执行环境。“单线程”意味着对服务器的所有请求运行在同一个线程上,而不是分布在不同的进程上。这个模式在速度和管理服务器资源方面效率很高,但也意味着如果以同步方式调用的函数占用了很长时间,不仅会阻塞当前请求,还会阻塞当前 web 应用其他所有请求。

有多种方法可以让一个异步 API 通知当前应用它已执行完毕。最常用的是在调用异步 API 时注册一个回调函数,在 API 操作结束后将“回调”之。这也是上面的代码所使用的方法。

如果有一系列独立的异步操作必须按顺序执行,那么使用回调可能会非常“混乱”,因为这会导致多级嵌套回调。人们通常把这个问题叫做“回调地狱”。缓解这个问题有以下办法:良好的编码实践使用 async 等模块迁移至 ES6 并使用 Promise 等特性

Node 和 Express 有一个一般性约定,即:使用“错误优先”回调。这个约定要求回调函数的第一个参数是错误值,而后续的参数包含成功数据。

创建路由处理器(Route handler)

app.get('/', (req, res) => {
  res.send('Hello World!');
});
  • 1
  • 2
  • 3

上文的示例中定义了一个(回调)路由处理函数来处理对站点根目录(‘/’)的 HTTP GET 请求。

回调函数将请求和响应对象作为参数。该函数直接调用响应的 send() 以返回字符串“Hello World!”。有 许多其他响应方法 可以结束请求/响应周期,例如,通过调用 res.json() 来发送 JSON 响应、调用 res.sendFile() 来发送文件。

虽然回调函数的参数命名没有限制,但是当调用回调时,第一个参数将始终是请求,第二个参数将始终是响应。合理的命名它们,在回调体中使用的对象将更容易识别。

Express 应用对象还提供了为其他所有 HTTP 动词定义路由处理器的方法,大多数处理器的使用方式完全一致:

checkout(), copy(), delete(), get(), head(), lock(), merge(), mkactivity(), mkcol(), move(), m-search(), notify(), options(), patch(), post(), purge(), put(), report(), search(), subscribe(), trace(), unlock(), unsubscribe().

有一个特殊的路由方法 app.all(),它可以在响应任意 HTTP 方法 时调用。用于在特定路径上为所有请求方法加载中间件函数。以下示例(来自 Express 文档)中的处理程序将在监听到针对 /secret 的任意 HTTP 动词(只要 HTTP 模块 支持)的请求后执行。

app.all('/secret', (req, res, next) => {
  console.log('访问私有文件 ...');
  next(); // 控制权传递给下一个处理器
});
  • 1
  • 2
  • 3
  • 4

路由器可以匹配 URL 中特定的字符串模式,并从 URL 中提取一些值作为参数传递给路由处理程序(作为请求对象的属性)。

可以为站点的特定部分提供一组路由处理器(使用公共路由前缀进行组合)。(比如对于一个有 维基(Wiki)内容的站点,可以把所有 Wiki 相关的路由放在同一个文件里,使用路由前缀 *‘/wiki/’ *访问它们)。在 Express 中可以使用 express.Router 对象实现。例如,可以把所有维基相关的路由都放在一个 wiki.js 模块中,然后导出 Router 对象,如下:

// wiki.js - 维基路由模块

const express = require('express');
const router = express.Router();

// 首页路由
router.get('/', (req, res) => {
  res.send('维基首页');
});

// “关于”页面路由
router.get('/about', (req, res) => {
  res.send('关于此维基');
});

module.exports = router;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

向 Router 对象添加路由就像向之前为 app 对象添加路由一样。

首先 require() 路由模块(wiki.js),然后在 Express 应用中调用 use() 把 Router 添加到中间件处理路径中,就可以在主应用中使用这个模块中的路由处理器了。路由路径有两条:/wiki 和 /wiki/about/。

const wiki = require('./wiki.js');
// ...
app.use('/wiki', wiki);
  • 1
  • 2
  • 3

使用中间件(Middleware)

中间件在 Express 应用中得到了广泛使用,从提供错误处理静态文件、到压缩 HTTP 响应等等。路由函数可以通过向 HTTP 客户端返回一些响应来结束 HTTP“请求 - 响应”周期,而中间件函数通常是对请求或响应执行某些操作,然后调用“栈”里的下一个函数,可能是其他中间件或路由处理器。中间件的调用顺序由应用开发者决定。

中间件可以执行任何操作,运行任何代码,更改请求和响应对象,也可以结束“请求 - 响应”周期。如果它没有结束循环,则必须调用 next() 将控制传递给下一个中间件函数(否则请求将成为悬挂请求)。

大多数应用会使用第三方中间件来简化常见的 web 开发任务,比如 cookie、会话、用户身份验证、访问请求 POST 和 JSON 数据,日志记录等。参见 Express 团队维护的中间件包列表(包含受欢迎的第三方包)。NPM 有提供其他 Express 包。

要使用第三方中间件,首先需要使用 NPM 将其安装到当前应用中。比如,要安装 morgan HTTP 请求记录器中间件,可以这样做:

npm install morgan
  • 1

然后,您可以对 Express 应用对象调用 use() 将该中间件添加到栈:

const express = require('express');
const logger = require('morgan');
const app = express();
app.use(logger('dev'));
...
  • 1
  • 2
  • 3
  • 4
  • 5

中间件和路由函数是按声明顺序调用的。一些中间件的引入顺序很重要(例如,如果会话中间件依赖于 cookie 中间件,则必须先添加 cookie 处理器)。绝大多数情况下要先调用中间件后设置路由,否则路由处理器将无法访问中间件的功能。

可以自己编写中间件函数,这是基本技能(仅仅为了创建错误处理代码也需要)。中间件函数和路由处理回调之间的唯一区别是:中间件函数有第三个参数 next,在中间件不会结束请求周期时应调用这个 next(它包含中间件函数调用后应调用的下一个函数)。

可以使用 app.use() 将一个中间件函数添加至处理链中,这取决于中间件是应用于所有响应的,还是应用于特定 HTTP 动词(GET,POST等)响应的。可以为两种情况指定相同的路由,但在调用 app.use() 时路由可以省略。

下面的示例显示了如何使用这两种方法添加中间件功能,以及是否使用路由。

const express = require('express');
const app = express();

// 示例中间件函数
const a_middleware_function = (req, res, next) => {
  // ... 进行一些操作
  next(); // 调用 next() ,Express 将调用处理链中下一个中间件函数。
};

// 用 use() 为所有的路由和动词添加该函数
app.use(a_middleware_function);

// 用 use() 为一个特定的路由添加该函数
app.use('/someroute', a_middleware_function);

// 为一个特定的 HTTP 动词和路由添加该函数
app.get('/', a_middleware_function);

app.listen(3000);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

上面代码中单独声明了中间件函数,并把它设置为回调。之前是把路由处理函数在使用时声明为回调。在 JavaScript 中,两种方法都可行。

托管静态文件

可以使用 express.static 中间件来托管静态文件,包括图片、CSS 以及 JavaScript 文件(其实 static() 是 Express 提供的原生中间件函数之一)。例如,可以通过下面一行来托管 ‘public’ 文件夹(应位于 Node 调用的同一级)中的文件:

app.use(express.static('public'));
  • 1

现在 ‘public’ 文件夹下的所有文件均可通过在根 URL 后直接添加文件名来访问了,比如:

http://localhost:3000/images/dog.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/about.html
  • 1
  • 2
  • 3
  • 4

可以通过多次调用 static() 来托管多个文件夹。如果一个中间件函数找不到某个文件,将直接传递给下一个中间件(中间件的调用顺序取决于声明顺序)。

app.use(express.static('public'));
app.use(express.static('media'));
  • 1
  • 2

还可以为静态 URL 创建一个虚拟的前缀,而不是直接把文件添加到根 URL 里。比如,这里 指定了一个装载路径,于是这些文件将通过 ‘/media’ 前缀调用:

app.use('/media', express.static('public'));
  • 1

现在可以通过 ‘/media’ 路径前缀来访问 ‘public’ 文件夹中的文件。

错误处理

用来处理错误的特殊中间件函数有四个参数(err, req, res, next),而不是之前的三个。例如:

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('出错了!');
});
  • 1
  • 2
  • 3
  • 4

错误处理中间件可以任何所需内容,但是必须在所有其他 app.use() 和路由调用后才能调用,因此它们是需求处理过程中最后的中间件。

Express 内建了错误处理机制,可以协助处理 app 中没有被处理的错误。默认的错误处理中间件函数在中间件函数栈的末尾。如果一个错误传递给 next() 而没有用错误处理器来处理它,内建处理机制将启动,栈跟踪的错误将回写给客户端。

生产环境中不保留栈跟踪轨迹。可将环境变量 NODE_ENV 设置为 ‘production’ 来运行所需的生产环境。

HTTP 404 和其他“错误”状态码不作为错误处理。可使用中间件来自行处理这些状态。更多信息请参阅 Express 文档 FAQ

使用数据库

Express 应用可以使用 Node 支持的所有数据库(Express 本身并没有定义任何数据库管理的附加行为或需求)。其中包括:PostgreSQL、MySQL、Redis、SQLite、MongoDB,等等。

使用数据库前先要用 NPM 来安装驱动程序。比如,要安装流行的 NoSQL 数据库 MongoDB 的驱动程序,可运行以下命令:

npm install mongodb
  • 1

数据库可以安装在本地或云端。在 Express 代码中 require() 驱动程序,连接,然后就可以执行增加、读取、更新、删除四种操作(CRUD)。以下示例展示了如何查找 MongoDB 表中 ‘哺乳动物’ 的记录:

// MongoDB 3.0 以上版本适用,老版本不适用。
const MongoClient = require('mongodb').MongoClient;

MongoClient.connect('mongodb://localhost:27017/animals', (err, client) => {
  if(err) {
    throw err;
  }

  let db = client.db('动物');
  db.collection('哺乳动物').find().toArray((err, result) => {
    if(err) throw err;
    console.log(result);
    client.close();
  });
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

还有一种通过“对象关系映射(Object Relational Mapper,简称 ORM)”间接访问数据库的方法。可以把数据定义为“对象”或“模型”,然后由 ORM 根据给定的数据库格式搞定所有映射关系。这种方法对于开发者有一个好处:可以用 JavaScript 对象的思维而无需直接使用数据库语法,同时传进的数据也有现成的检查工具。

更多信息请参阅 Express 文档 数据库集成

渲染数据(视图,view)

模板引擎可为输出文档的结构指定一个模板,在数据处先放置占位符,并于页面生成时填充。模板通常用于生成 HTML,也可以生成其他类型的文档。Express 支持 多个版本的模板引擎,可以参阅:JavaScript 模板引擎对比评测:Jade、Mustache、Dust 与其他

在应用设置代码中声明了模板引擎的名称和位置后,Express 可以使用 ‘views’ 和 ‘view engines’ 设置来寻找模板,如下所示(必须事先安装包含模板库的包!):

const express = require('express');
const app = express();

// 设置包含模板的文件夹('views')
app.set('views', path.join(__dirname, 'views'));

// 设置视图引擎,比如'some_template_engine_name'
app.set('view engine', 'some_template_engine_name');

模板的外观取决于所使用的引擎。假设一个模板文件名为 "index.<template_extension>",其中包括数据变量 'title''message' 的两个占位符,可以在路由处理器函数中调用 Response.render() 来创建并发送 HTML 响应:

https://www.expressjs.com.cn/4x/api.html#res.render

app.get('/', (req, res) => {
  res.render('index', { title: '关于狗狗', message: '狗狗很牛!' });
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

更多信息请参见 Express 文档 使用模板引擎

文件结构

Express 不对文件结构和组件的选用做任何约定。路由、视图、静态文件,以及其他应用具体逻辑均可按任意文件结构保存在任意数量的文件中。当然可以让整个 Express 应用保存在单一文件中,但是一般情况下,把应用按功能(比如账户管理、博客、论坛)和架构问题域(比如 MVC 架构 中的模型、视图、控制器)进行拆分是有意义的。

Express 是一个非常轻量的 web 应用框架,这是有意为之的,它巨大的裨益和无尽的潜能都来自第三方的库和功能。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/120272
推荐阅读
相关标签
  

闽ICP备14008679号