首页

我们将创建的第一个页面是网站首页,可以通过站点 (/) 或目录 (catalog/) 根目录访问。它将显示一些描述站点的静态文本,以及数据库中不同记录类型的动态计算的“计数”。

我们已经为首页创建了一个路由。为了完成该页面,我们需要更新我们的控制器函数以从数据库中获取记录的“计数”,并创建一个视图(模板)来渲染页面。

注意:我们将使用 Mongoose 获取数据库信息。在继续之前,您可能希望重新阅读 Mongoose 入门 部分中关于 搜索记录 的内容。

路由

我们在 之前的教程 中创建了索引页路由。提醒一下,所有路由函数都在 /routes/catalog.js 中定义。

js
// GET catalog home page.
router.get("/", book_controller.index); //This actually maps to /catalog/ because we import the route with a /catalog prefix

作为参数传递的书籍控制器索引函数 (book_controller.index) 在 /controllers/bookController.js 中定义了一个“占位符”实现。

js
exports.index = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Site Home Page");
});

我们将扩展此控制器函数,以从我们的模型中获取信息,然后使用模板(视图)进行渲染。

控制器

索引控制器函数需要获取有关数据库中 BookBookInstance(全部)、BookInstance(可用)、AuthorGenre 记录数量的信息,将这些数据渲染到模板中以创建 HTML 页面,然后将其返回到 HTTP 响应中。

打开 /controllers/bookController.js。在文件顶部附近,您应该会看到导出的 index() 函数。

js
const Book = require("../models/book");
const asyncHandler = require("express-async-handler");

exports.index = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Site Home Page");
});

将上面所有代码替换为以下代码片段。首先,它导入(require())所有模型。我们需要这样做,因为我们将使用它们来获取文档的计数。该代码还要求“express-async-handler”,它提供了一个包装器来 捕获路由处理程序函数中抛出的异常

js
const Book = require("../models/book");
const Author = require("../models/author");
const Genre = require("../models/genre");
const BookInstance = require("../models/bookinstance");

const asyncHandler = require("express-async-handler");

exports.index = asyncHandler(async (req, res, next) => {
  // Get details of books, book instances, authors and genre counts (in parallel)
  const [
    numBooks,
    numBookInstances,
    numAvailableBookInstances,
    numAuthors,
    numGenres,
  ] = await Promise.all([
    Book.countDocuments({}).exec(),
    BookInstance.countDocuments({}).exec(),
    BookInstance.countDocuments({ status: "Available" }).exec(),
    Author.countDocuments({}).exec(),
    Genre.countDocuments({}).exec(),
  ]);

  res.render("index", {
    title: "Local Library Home",
    book_count: numBooks,
    book_instance_count: numBookInstances,
    book_instance_available_count: numAvailableBookInstances,
    author_count: numAuthors,
    genre_count: numGenres,
  });
});

我们使用 countDocuments() 方法获取每个模型的实例数量。此方法在模型上调用,并带有一组可选的匹配条件,并返回一个 Query 对象。可以通过调用 exec() 来执行查询,该方法返回一个 Promise,该 Promise 或者以结果完成,或者如果存在数据库错误则被拒绝。

因为文档计数的查询彼此独立,所以我们使用 Promise.all() 并行运行它们。该方法返回一个新的 promise,我们 await 其完成(执行在 此函数 中的 await 处暂停)。当所有查询完成后,all() 返回的 promise 将完成,继续执行路由处理程序函数,并使用数据库查询的结果填充数组。

然后我们调用 res.render(),指定一个名为“index”的视图(模板)和对象,将数据库查询的结果映射到视图模板。数据作为键值对提供,可以使用键在模板中访问。

注意:如果您在 Pug 模板中使用了未传入的键/变量,则它将呈现为空字符串,并在表达式中被评估为 false。其他模板语言可能要求您为使用到的所有对象传入值。

请注意,代码非常简单,因为我们可以假设数据库查询成功。如果任何数据库操作失败,则抛出的异常将被 asyncHandler() 捕获并传递给链中的 next 中间件处理程序。

视图

打开 /views/index.pug 并将其内容替换为以下文本。

pug
extends layout

block content
  h1= title
  p Welcome to #[em LocalLibrary], a very basic Express website developed as a tutorial example on the Mozilla Developer Network.

  h2 Dynamic content

  p The library has the following record counts:

  ul
    li #[strong Books:] !{book_count}
    li #[strong Copies:] !{book_instance_count}
    li #[strong Copies available:] !{book_instance_available_count}
    li #[strong Authors:] !{author_count}
    li #[strong Genres:] !{genre_count}

视图很简单。我们扩展了 layout.pug 基本模板,覆盖了名为“content”的 block。第一个 h1 标题将是传递到 render() 函数的 title 变量的转义文本——请注意使用“h1=”,以便以下文本被视为 JavaScript 表达式。然后,我们包含一段介绍 LocalLibrary 的文字。

在“动态内容”标题下,我们列出了每个模型的副本数量。请注意,数据的模板值是在路由处理程序函数中调用 render() 时指定的键。

注意:我们没有转义计数值(即,我们使用了 !{} 语法),因为计数值是计算出来的。如果信息是由最终用户提供的,那么我们将转义变量以进行显示。

它是什么样子的?

此时,我们应该已经创建了显示索引页面所需的一切。运行应用程序并在浏览器中打开 https://127.0.0.1:3000/。如果一切设置正确,您的站点应该看起来像以下屏幕截图。

Home page - Express Local Library site

注意:您还不能使用侧边栏链接,因为这些页面的 URL、视图和模板尚未定义。如果您尝试,您将收到错误,例如“未实现:书籍列表”,具体取决于您单击的链接。这些字符串文字(将被替换为适当的数据)在“controllers”文件中存在的不同控制器中指定。

后续步骤