Express 教程第二部分:创建一个骨架网站

这是我们Express 教程的第二篇文章,它展示了如何创建一个“骨架”网站项目,然后你可以用特定于网站的路由、模板/视图和数据库调用来填充它。

预备知识 设置 Node 开发环境。复习 Express 教程。
目标 能够使用 Express 应用程序生成器启动你自己的新网站项目。

概述

本文展示了如何使用 Express 应用程序生成器工具创建一个“骨架”网站,然后你可以用特定于网站的路由、视图/模板和数据库调用来填充它。在这种情况下,我们将使用该工具为我们的本地图书馆网站创建框架,稍后我们将为该网站添加所有其他所需的代码。这个过程非常简单,只需要你在命令行中调用生成器并指定一个新的项目名称,还可以选择指定网站的模板引擎和 CSS 生成器。

以下部分将向你展示如何调用应用程序生成器,并对不同的视图/CSS 选项进行一些解释。我们还将解释骨架网站的结构。最后,我们将展示如何运行网站以验证其是否正常工作。

备注

  • Express 应用程序生成器并不是 Express 应用程序的唯一生成器,生成的项目也不是组织文件和目录的唯一可行方式。然而,生成的网站确实有一个模块化结构,易于扩展和理解。有关最小 Express 应用程序的信息,请参阅Hello world 示例(Express 文档)。
  • Express 应用程序生成器使用 var 声明了大多数变量。我们在教程中将其中大部分更改为 const(少数更改为 let),因为我们希望演示现代 JavaScript 实践。
  • 本教程使用 Express 和其他依赖项的版本,这些版本在 Express 应用程序生成器创建的 package.json 中定义。这些版本不一定是最新版本,在将实际应用程序部署到生产环境时,你应该更新它们。

使用应用程序生成器

你已经应该在设置 Node 开发环境时安装了生成器。快速回顾一下,你可以使用 npm 包管理器在全站范围内安装生成器工具,如下所示

bash
npm install express-generator -g

生成器有许多选项,你可以使用 --help(或 -h)命令在命令行中查看它们。

bash
> express --help

    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

你可以指定 Express 在当前目录中创建项目,使用 Jade 视图引擎和纯 CSS(如果你指定目录名,则项目将在该名称的子文件夹中创建)。

bash
express

你还可以使用 --view 选择一个视图(模板)引擎,和/或使用 --css 选择一个 CSS 生成引擎。

注意:选择模板引擎的其他选项(例如,--hogan--ejs--hbs 等)已弃用。请使用 --view(或 -v)。

我应该使用哪个视图引擎?

Express 应用程序生成器允许你配置许多流行的视图/模板引擎,包括 EJSHbsPug (Jade)、TwigVash,尽管如果你不指定视图选项,它默认选择 Jade。Express 本身也可以开箱即用地支持大量其他模板语言。

注意:如果你想使用生成器不支持的模板引擎,请参阅 将模板引擎与 Express 一起使用 (Express 文档) 和你的目标视图引擎的文档。

一般来说,你应该选择一个能够提供你所需所有功能并让你更快地提高生产力的模板引擎——换句话说,就像你选择任何其他组件一样!在比较模板引擎时需要考虑的一些因素:

  • 生产力时间 — 如果你的团队已经有使用某种模板语言的经验,那么他们使用该语言可能会更快地提高生产力。如果没有,那么你应该考虑候选模板引擎的相对学习曲线。
  • 流行度和活跃度 — 查看引擎的流行度以及它是否有一个活跃的社区。在网站的整个生命周期中出现问题时,能够获得支持是很重要的。
  • 样式 — 有些模板引擎使用特定的标记来指示“普通”HTML 中插入的内容,而另一些则使用不同的语法(例如,使用缩进和块名称)来构建 HTML。
  • 性能/渲染时间。
  • 功能——你应该考虑你所查看的引擎是否具有以下可用功能:
    • 布局继承:允许您定义一个基本模板,然后只继承您希望特定页面不同的部分。这通常比通过包含多个所需组件或每次从头构建模板来构建模板更好。
    • “包含”支持:允许您通过包含其他模板来构建模板。
    • 简洁的变量和循环控制语法。
    • 能够在模板级别过滤变量值,例如将变量转换为大写,或格式化日期值。
    • 能够生成除 HTML 之外的其他输出格式,例如 JSON 或 XML。
    • 支持异步操作和流式传输。
    • 客户端功能。如果模板引擎可以在客户端使用,则可以实现所有或大部分渲染在客户端完成。

注意:互联网上有很多资源可以帮助你比较不同的选项!

对于这个项目,我们将使用 Pug 模板引擎(以前称为“Jade”),因为它是最流行的 Express/JavaScript 模板语言之一,并且生成器开箱即用就支持它。

我应该使用哪个 CSS 样式表引擎?

Express 应用程序生成器允许你创建一个配置为使用最常见的 CSS 样式表引擎的项目:LESSSASSStylus

注意:CSS 存在一些限制,使得某些任务变得困难。CSS 样式表引擎允许你使用更强大的语法来定义你的 CSS,然后将定义编译成浏览器使用的纯旧 CSS。

与模板引擎一样,你应该使用能让你的团队最高效的样式表引擎。对于这个项目,我们将使用普通的 CSS(默认),因为我们的 CSS 需求不够复杂,不需要使用其他任何东西。

我应该使用哪个数据库?

生成的代码不使用/包含任何数据库。Express 应用程序可以使用 Node 支持的任何数据库机制Express 本身不定义任何特定的数据库管理附加行为/要求)。

我们将在后续文章中讨论如何与数据库集成。

创建项目

对于我们将要构建的示例 Local Library 应用程序,我们将使用 Pug 模板库和无 CSS 引擎创建一个名为 express-locallibrary-tutorial 的项目。

首先,导航到你想要创建项目的位置,然后在命令提示符下运行 Express 应用程序生成器,如下所示

bash
express express-locallibrary-tutorial --view=pug

生成器将创建(并列出)项目文件。

   create : express-locallibrary-tutorial\
   create : express-locallibrary-tutorial\public\
   create : express-locallibrary-tutorial\public\javascripts\
   create : express-locallibrary-tutorial\public\images\
   create : express-locallibrary-tutorial\public\stylesheets\
   create : express-locallibrary-tutorial\public\stylesheets\style.css
   create : express-locallibrary-tutorial\routes\
   create : express-locallibrary-tutorial\routes\index.js
   create : express-locallibrary-tutorial\routes\users.js
   create : express-locallibrary-tutorial\views\
   create : express-locallibrary-tutorial\views\error.pug
   create : express-locallibrary-tutorial\views\index.pug
   create : express-locallibrary-tutorial\views\layout.pug
   create : express-locallibrary-tutorial\app.js
   create : express-locallibrary-tutorial\package.json
   create : express-locallibrary-tutorial\bin\
   create : express-locallibrary-tutorial\bin\www

   change directory:
     > cd express-locallibrary-tutorial

   install dependencies:
     > npm install

   run the app (Bash (Linux or macOS))
     > DEBUG=express-locallibrary-tutorial:* npm start

   run the app (PowerShell (Windows))
     > $env:DEBUG = "express-locallibrary-tutorial:*"; npm start

   run the app (Command Prompt (Windows)):
     > SET DEBUG=express-locallibrary-tutorial:* & npm start

在输出的末尾,生成器提供了有关如何安装依赖项(如 package.json 文件中列出的)以及如何在不同操作系统上运行应用程序的说明。

注意:生成器创建的文件将所有变量定义为 var。在继续之前,打开所有生成的文件,并将 var 声明更改为 const(本教程的其余部分假定你已完成此操作)。

运行骨架网站

至此,我们有了一个完整的骨架项目。这个网站实际上还没有做很多事情,但值得运行一下来证明它能工作。

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

    bash
    cd express-locallibrary-tutorial
    npm install
    
  2. 然后运行应用程序。

    • 在 Windows CMD 提示符下,使用此命令

      批处理
      SET DEBUG=express-locallibrary-tutorial:* & npm start
      
    • 在 Windows PowerShell 中,使用此命令

      powershell
      $env:DEBUG = "express-locallibrary-tutorial:*"; npm start
      

      注意:本教程不涉及 PowerShell 命令(所提供的“Windows”命令假定您使用的是 Windows CMD 提示符)。

    • 在 macOS 或 Linux 上,使用此命令

      bash
      DEBUG=express-locallibrary-tutorial:* npm start
      
  3. 然后,在浏览器中加载 https://:3000/ 访问应用程序。

你应该会看到一个如下所示的浏览器页面

Browser for default Express app generator website

恭喜!你现在拥有一个可工作的 Express 应用程序,可以通过端口 3000 访问。

注意:你也可以只使用 npm start 命令启动应用程序。如所示指定 DEBUG 变量可以启用控制台日志/调试。例如,当你访问上面页面时,你将看到如下调试输出

bash
SET DEBUG=express-locallibrary-tutorial:* & npm start
> express-locallibrary-tutorial@0.0.0 start D:\github\mdn\test\exprgen\express-locallibrary-tutorial
> node ./bin/www

  express-locallibrary-tutorial:server Listening on port 3000 +0ms
GET / 304 490.296 ms - -
GET /stylesheets/style.css 200 4.886 ms - 111

启用文件更改时服务器重启

你对 Express 网站所做的任何更改目前都不可见,直到你重新启动服务器。每次进行更改时都必须停止和重新启动服务器很快就会变得非常烦人,因此值得花时间在需要时自动化服务器重新启动。

为此目的,一个方便的工具是 nodemon。它通常全局安装(因为它是一个“工具”),但在这里我们将它作为开发依赖在本地安装和使用,这样任何使用该项目的开发人员在安装应用程序时都会自动获得它。在骨架项目的根目录中使用以下命令

bash
npm install --save-dev nodemon

如果你仍然选择将 nodemon 全局安装到你的机器上,而不仅仅是安装到你项目的 package.json 文件中

bash
npm install -g nodemon

如果你打开项目的 package.json 文件,你现在会看到一个包含此依赖项的新部分

json
{
  "devDependencies": {
    "nodemon": "^3.1.10"
  }
}

由于该工具不是全局安装的,我们不能从命令行启动它(除非我们将其添加到路径中)。但是,我们可以从 npm 脚本调用它,因为 npm 知道哪些包已安装。找到 package.json 文件中的 scripts 部分。最初,它将包含一行,以 "start" 开头。通过在该行末尾添加逗号,并添加 "devstart""serverstart" 行来更新它

  • 在 Linux 和 macOS 上,脚本部分将如下所示

    json
    {
      "scripts": {
        "start": "node ./bin/www",
        "devstart": "nodemon ./bin/www",
        "serverstart": "DEBUG=express-locallibrary-tutorial:* npm run devstart"
      }
    }
    
  • 在 Windows 上,“serverstart”的值将如下所示(如果使用命令提示符)

    bash
    "serverstart": "SET DEBUG=express-locallibrary-tutorial:* & npm run devstart"
    

我们现在可以用几乎与以前完全相同的方式启动服务器,但是使用 devstart 命令。

注意:现在,如果你修改项目中的任何文件,服务器将重新启动(或者你可以在命令提示符下随时键入 rs 来重新启动它)。你仍然需要重新加载浏览器以刷新页面。

我们现在必须调用 npm run <script-name> 而不是仅仅调用 npm start,因为“start”实际上是一个 npm 命令,它映射到命名的脚本。我们可以替换 start 脚本中的命令,但我们只想在开发过程中使用 nodemon,因此创建新的脚本命令是有意义的。

上面添加到 package.json 脚本中的 serverstart 命令是一个很好的例子。使用这种方法意味着你不再需要输入一个长命令来启动服务器。请注意,添加到脚本中的特定命令仅适用于 macOS 或 Linux。

生成的项目

现在我们来看看刚刚创建的项目。我们将对其进行一些小的修改。

目录结构

现在你已经安装了依赖项,生成的项目具有以下文件结构(文件是以“/”为前缀的项)。package.json 文件定义了应用程序的依赖项和其他信息。它还定义了一个启动脚本,该脚本将调用应用程序的入口点,即 JavaScript 文件 /bin/www。该文件设置了一些应用程序错误处理,然后加载 app.js 来完成其余的工作。应用程序路由存储在 routes/ 目录下的单独模块中。模板存储在 /views 目录下。

express-locallibrary-tutorial
    app.js
    /bin
        www
    package.json
    package-lock.json
    /node_modules
        [about 6700 subdirectories and files]
    /public
        /images
        /javascripts
        /stylesheets
            style.css
    /routes
        index.js
        users.js
    /views
        error.pug
        index.pug
        layout.pug

以下部分将更详细地描述这些文件。

package.json

package.json 文件定义了应用程序的依赖项和其他信息

json
{
  "name": "express-locallibrary-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"
  },
  "devDependencies": {
    "nodemon": "^3.1.10"
  }
}

脚本部分首先定义了一个“start”脚本,当我们调用 npm start 来启动服务器时,我们就是调用这个脚本(这个脚本是由 Express 应用程序生成器添加的)。从脚本定义中,你可以看到它实际上是用 node 启动了 JavaScript 文件 ./bin/www

我们已经在启用文件更改时服务器重启中修改了这一部分,添加了 devstartserverstart 脚本。这些脚本可以用来用 nodemon 而不是 node 启动相同的 ./bin/www 文件(如上所述,这些脚本版本适用于 Linux 和 macOS)。

json
{
  "scripts": {
    "start": "node ./bin/www",
    "devstart": "nodemon ./bin/www",
    "serverstart": "DEBUG=express-locallibrary-tutorial:* npm run devstart"
  }
}

依赖项包括 express 包和我们选择的视图引擎 (pug) 的包。此外,我们还有以下在许多 Web 应用程序中都有用的包

  • cookie-parser:用于解析 cookie 头并填充 req.cookies(本质上提供了一种方便的访问 cookie 信息的方法)。
  • debug:一个微小的 Node 调试工具,仿照 Node 核心的调试技术。
  • morgan:Node 的 HTTP 请求日志中间件。
  • http-errors:在需要时创建 HTTP 错误(用于 Express 错误处理)。

生成项目中的默认版本有点过时了。用以下文本替换 package.json 文件的依赖项部分,该文本指定了撰写本文时这些库的最新版本

json
{
  "dependencies": {
    "cookie-parser": "^1.4.7",
    "debug": "^4.4.1",
    "express": "^5.1.0",
    "http-errors": "~2.0.0",
    "morgan": "^1.10.0",
    "pug": "3.0.3"
  }
}

然后使用以下命令更新已安装的依赖项

bash
npm install

注意:定期更新到依赖库的最新兼容版本是一个好主意——这甚至可以通过持续集成设置自动或半自动完成。

通常,次要版本和补丁版本的库更新会保持兼容。我们上面在每个版本前加上 ^,这样我们就可以通过运行以下命令自动更新到最新的 minor.patch 版本

bash
npm update --save

主要版本会改变兼容性。对于这些更新,我们需要手动更新 package.json 和使用该库的代码,并对项目进行全面的重新测试。

www 文件

文件 /bin/www 是应用程序的入口点!它做的第一件事是 require() “真正的”应用程序入口点(app.js,在项目根目录),它设置并返回 express() 应用程序对象。require()CommonJS 方式,用于将 JavaScript 代码、JSON 和其他文件导入到当前文件中。这里我们使用相对路径指定 app.js 模块,并省略可选的 (.js) 文件扩展名。

js
#!/usr/bin/env node

/**
 * Module dependencies.
 */

const app = require("../app");

注意:Node.js 14 及更高版本支持 ES6 import 语句来导入 JavaScript (ECMAScript) 模块。要使用此功能,你必须在 Express 的 package.json 文件中添加 "type": "module",应用程序中的所有模块都必须使用 import 而不是 require(),并且对于相对导入,你必须包含文件扩展名(有关更多信息,请参阅 Node 文档)。虽然使用 import 有好处,但本教程使用 require() 以与 Express 文档保持一致。

该文件中其余的代码设置了一个 Node HTTP 服务器,将 app 设置为特定端口(在环境变量中定义,如果未定义则为 3000),并开始监听和报告服务器错误和连接。目前你不需要了解代码的其他任何内容(该文件中的所有内容都是“样板文件”),但如果你感兴趣,可以随意查看它。

app.js

此文件创建一个 express 应用程序对象(按照约定命名为 app),使用各种设置和中间件配置应用程序,然后从模块中导出该应用程序。下面的代码仅显示创建和导出应用程序对象的文件部分

js
const express = require("express");

const app = express();
// …
module.exports = app;

回到上面的 www 入口文件,当这个文件被导入时,这个 module.exports 对象被提供给调用者。

让我们详细地看看 app.js 文件。首先,我们使用 require() 将一些有用的 Node 库导入到文件中,包括我们之前使用 npm 为应用程序下载的 http-errorsexpressmorgancookie-parser;以及 path,这是一个用于解析文件和目录路径的核心 Node 库。

js
const createError = require("http-errors");
const express = require("express");
const path = require("path");
const cookieParser = require("cookie-parser");
const logger = require("morgan");

然后我们 require() 来自我们路由目录的模块。这些模块/文件包含处理特定相关“路由”(URL 路径)的代码。当我们扩展骨架应用程序时,例如列出图书馆中的所有书籍,我们将添加一个新文件来处理与书籍相关的路由。

js
const indexRouter = require("./routes/index");
const usersRouter = require("./routes/users");

注意:此时,我们只是导入了模块;我们实际上还没有使用它的路由(这在文件稍后发生)。

接下来,我们使用导入的 express 模块创建 app 对象,然后使用它来设置视图(模板)引擎。设置引擎分为两部分。首先,我们设置 "views" 值来指定模板将存储的文件夹(在这种情况下是子文件夹 /views)。然后我们设置 "view engine" 值来指定模板库(在这种情况下是 "pug")。

js
const app = express();

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

接下来的一组函数调用 app.use() 将我们上面导入的中间件库添加到请求处理链中。例如,express.json()express.urlencoded() 需要用于用表单字段填充 req.body。在这些库之后,我们还使用 express.static 中间件,它使 Express 服务项目根目录中 /public 目录中的所有静态文件。

js
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")));

现在所有其他中间件都已设置好,我们将我们(之前导入的)路由处理代码添加到请求处理链中。导入的代码将为网站的不同部分定义特定的路由

js
app.use("/", indexRouter);
app.use("/users", usersRouter);

注意:上面指定的路径 ("/""/users") 被视为导入文件中定义的路由的前缀。因此,例如,如果导入的 users 模块定义了 /profile 的路由,你将通过 /users/profile 访问该路由。我们将在后续文章中讨论更多关于路由的内容。

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

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

// error handler
app.use((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");
});

Express 应用程序对象(app)现在已完全配置。最后一步是将其添加到模块导出中(这就是它允许被 /bin/www 导入的原因)。

js
module.exports = app;

路由

路由文件 /routes/users.js 如下所示(路由文件结构相似,因此我们无需再展示 index.js)。首先,它加载 express 模块并使用它获取 express.Router 对象。然后,它在该对象上指定一个路由,最后从模块中导出路由器(这就是文件可以导入到 app.js 中的原因)。

js
const express = require("express");

const router = express.Router();

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

module.exports = router;

该路由定义了一个回调函数,当检测到具有正确模式的 HTTP GET 请求时,该回调函数将被调用。匹配模式是模块导入时指定的路由 ("/users") 加上此文件中定义的任何内容 ("/")。换句话说,当收到 /users/ 的 URL 时,将使用此路由。

注意:通过使用 Node 运行服务器并在浏览器中访问 URL:https://:3000/users/ 来尝试此操作。你应该会看到一条消息:“respond with a resource”。

上面一个有趣的地方是回调函数有第三个参数 next,因此它是一个中间件函数而不是简单的路由回调。虽然代码目前没有使用 next 参数,但如果你想为 '/' 路由路径添加多个路由处理程序,它将来可能会很有用。

视图(模板)

视图(模板)存储在 /views 目录中(如 app.js 中指定),并使用 .pug 文件扩展名。方法 Response.render() 用于渲染指定的模板以及以对象形式传递的命名变量值,然后将结果作为响应发送。在下面来自 /routes/index.js 的代码中,你可以看到该路由如何使用模板“index”并传递模板变量“title”来渲染响应。

js
/* GET home page. */
router.get("/", (req, res, next) => {
  res.render("index", { title: "Express" });
});

上述路由对应的模板如下(index.pug)。我们稍后将详细讨论语法。现在你只需知道 title 变量(值为 'Express')会插入到模板中指定的位置。

pug
extends layout

block content
  h1= title
  p Welcome to #{title}

挑战自我

/routes/users.js 中创建一个新路由,该路由将在 URL /users/cool/ 显示文本“You're so cool”。通过运行服务器并在浏览器中访问 https://:3000/users/cool/ 来测试它

总结

你现在已经为本地图书馆创建了一个骨架网站项目,并验证它可以使用 node 运行。最重要的是,你还了解了项目的结构,因此你清楚地知道我们需要在哪里进行更改以添加本地图书馆的路由和视图。

接下来,我们将开始修改骨架,使其作为图书馆网站运行。

另见