freeCodeCamp.org

The Node.js and Express.js Handbook for Beginners – Servers, Routes, Routers, and Views Explained

7.5内容质量
The Node.js and Express.js Handbook for Beginners – Servers, Routes, Routers, and Views Explained

TL;DR · AI 摘要

本文为Node.js和Express.js初学者提供完整实践指南,涵盖服务器搭建、路由配置及视图渲染等核心概念。

核心要点

  • Express.js通过路由和中间件简化HTTP请求处理
  • 创建Node.js服务器需配置HTTP模块和端口监听
  • Express路由器可实现模块化路由管理

结构提纲

按章节快速跳转。

  1. 介绍Node.js和Express.js在后端开发中的基础作用

  2. 讲解Node.js运行环境及核心API使用方法

  3. 分步骤演示如何用Node.js搭建HTTP服务器

  4. 解析Express.js框架的路由和中间件机制

  5. 对比原生Node.js与Express.js的路由处理方式

  6. 说明Express.js中静态和动态视图的渲染原理

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • Node.js与Express.js实践指南
    • Node.js基础
      • HTTP模块
      • 事件循环
    • Express.js核心
      • 路由系统
      • 中间件机制
    • 项目结构
      • 路由模块化
      • 视图渲染

金句 / Highlights

值得收藏与分享的关键句。

#Node.js#Express.js#后端开发
打开原文

Node.js 与 Express.js 入门手册 – 详解服务器、路由、路由器和视图

2026年10月1日

/

#JavaScript

Oluwatobi Sofela

Node.js 是一个用于在网页浏览器之外执行 JavaScript 的运行时环境。从小型脚本到大型后端应用,Node.js 提供了构建多种环境 JavaScript 应用所需的 API 和工具。

但学习 Node.js 可能会让人感到压力。面对众多 API、包、工具和概念,很容易感到迷失。

因此本书专注于构建实用应用所需的核心 Node.js 概念,避免不必要的干扰。你将学习 Node.js 的工作原理、如何使用其内置 API,以及如何创建能与文件、URL、事件、HTTP 请求等交互的应用程序。

你还将了解 Express.js 如何简化许多常见的服务器端任务。我们将探讨路由、路由器、控制器、视图等概念,帮助你更高效地组织和构建 Web 应用。

无论你是第一次学习后端开发,还是希望将 JavaScript 技能扩展到浏览器之外,这本指南都将为你提供使用 Node.js 和 Express.js 构建应用的清晰实用基础。

目录

  • 你应已掌握的知识
  • 所需工具
  • Node.js 究竟是什么?创建项目新目录 创建 package.json 文件
  • 为何选择 Node.js?1. 创建 JavaScript 文件 2. 编写 JavaScript 程序 3. 运行 JavaScript 程序 4. 自动重新运行 JavaScript 程序
  • Node 的模块支持
  • 使用 Node.js 创建 HTTP 服务器 初始化 HTTPServer 对象的新实例 配置系统端口 配置服务器响应客户端请求
  • Express.js 究竟是什么?1. 安装 Express 2. 使用 Express.js 配置项目 Node.js Web 服务器 3. 运行 Express.js Web 服务器
  • 原生 Node.js 代码与 Express.js 代码对比 示例 1:使用原生 Node.js 处理 Web 请求 示例 2:使用 Express.js 处理 Web 请求
  • 关于在 Node.js 中创建 Express.js Web 服务器的重要知识 通常最后调用 app.listen() 可以使用环境变量设置端口号 常见的 Express.js 响应方法 Express 的响应方法不会终止 HTTP 请求处理器的执行
  • Express.js 中的路由是什么?Express 路由的语法 中间件与路由处理器 Express.js 路由示例 关于 next 参数的重要知识 Express.js 中间件分类
  • express.Router() 方法是什么?为路由器创建目录 创建路由文件 为书籍相关路由和中间件创建迷你应用 与路由器实践 更新主 Express 应用 运行 Express.js 应用 关于 Express 路由器的重要知识
  • Express.js 中的视图是什么?Express.js 中的静态视图是什么?Express.js 中的动态视图是什么?
  • 概览 深入了解 Node.js 和 Express.js

你应已掌握的知识

本指南面向已经掌握基础 JavaScript 并希望使用 Node.js 和 Express.js 构建 Web 应用的读者。如果你熟悉以下内容,将能从本指南中获得最大价值:

  • JavaScript 基础知识,包括变量、函数、数组和对象。
  • 基础异步 JavaScript,包括回调函数、Promise 和 async/await。
  • 基本 HTML,包括链接、表单和输入元素。
  • 使用终端导航文件夹和运行命令。

你不需要具备 Node.js 或 Express.js 的先前经验。熟悉安装 npm 包会有帮助,但示例会引导你完成所需的命令。

你需要的工具

开始之前,请确保已安装以下内容:

  • Node.js 24.15.0 或更高版本。
  • npm 11.14.0 或更高版本。

通过运行以下命令检查已安装的版本:

code
node --version && npm --version

Node.js 包含 npm,但捆绑的版本可能比此处列出的版本更旧。检查两个版本,如有需要请单独更新 npm。CodeSweetly 包管理指南解释了如何安装、更新和检查这些工具。

文件创建命令假设环境支持 mkdir 和 touch 命令,例如 macOS 或 Linux 上的 Bash 或 zsh、Windows 上的 Git Bash,或通过 Windows Subsystem for Linux (WSL) 的 Linux shell。你也可以直接在编辑器中创建文件和文件夹。

让我们开始使用 Node.js。

Node.js 究竟是什么?

Node.js 不是框架或编程语言。相反,它是一个异步、事件驱动的环境,包含以下工具:

  • JavaScript 引擎(Google 的 V8)
  • 模块系统(CommonJS 和 ES 模块)
  • 操作系统 API(os)
  • 文件系统访问(fs)
  • 网络服务器(http、net、https)
  • 内存管理工具
  • 事件循环

这些组件构成了 Node.js 的核心基础设施,使 JavaScript 能够在浏览器之外运行。

上图将 Node.js 描述为一个功能齐全的运行时环境,提供基础设施以构建和运行任何规模的 JavaScript 应用程序,包括服务器、CLI、脚本和全栈应用。

让我们通过使用 Node.js 和 Express.js 构建应用程序来讨论图像中的要点。首先,创建项目目录。

为项目创建新目录

使用 mkdir CLI 命令创建新项目目录:

code
mkdir codesweetly-nodejs-app-001

注意:你可以使用任何喜欢的名称。在本指南中,我们将使用 codesweetly-nodejs-app-001 作为演示。

随后,使用命令行导航到项目目录:

code
cd codesweetly-nodejs-app-001

创建 package.json 文件

在进入项目目录后,使用 npm 初始化 package.json 文件:

code
npm init -y

接下来,使用以下命令从 package.json 中删除可选的 main 字段:

code
npm pkg delete main

提示:package.json 中的 main 字段在其他程序加载该包时标识包的入口点。由于我们将直接运行脚本,本项目不需要它。保留它也不会造成任何问题。

为什么选择 Node.js?

JavaScript 最初是为网页浏览器创建的,但其他环境也提供了执行它的基础设施。

Node.js 2009 年的发布为在网页浏览器之外运行 JavaScript 提供了环境。例如,让我们创建一个可以从系统终端运行的脚本。

1. 创建 JavaScript 文件

创建你希望 Node 运行的 JavaScript 文件。

在支持 touch 命令的 shell(如 Bash 或 zsh)中,使用以下命令。否则,在代码编辑器中创建文件:

code
touch console.js

2. 编写你的 JavaScript 程序

打开新创建的 JavaScript 文件并编写你的程序:

code
console.log("=== 来自 CodeSweetly 团队的问候 ===");
console.log("希望您在使用 Node.js 进行甜蜜编码时玩得开心。");
console.log("感谢您成为 CodeSweetly 社区的一员。");
console.log("=== 持续编码。持续创造。持续发布。 ===");

3. 运行您的 JavaScript 程序

安装 Node.js 后,您可以通过命令行使用 node 命令来运行 Node 应用程序。

code
node console.js
  • node : 运行 Node.js 脚本或 REPL 的命令。
  • console.js : 您希望 Node 运行的 JavaScript 文件。

您还可以将该命令添加到项目 package.json 文件的 "scripts" 字段中:

code
{
  "scripts": {
    "start": "node console.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}

添加此脚本后,您可以像这样从终端运行 JavaScript 程序:

code
npm run start

执行脚本后,Node 会将文件的输出打印到终端,效果如下:

code
$ npm run start

> [email protected] start
> node console.js

=== 来自 CodeSweetly 团队的问候 ===
希望您在使用 Node.js 进行甜蜜编码时玩得开心。
感谢您成为 CodeSweetly 社区的一员。
=== 持续编码。持续创造。持续发布。 ===

如您所见,我们已成功在浏览器之外执行了 JavaScript。这正是 Node.js 帮助我们实现的目标。它是一个异步、事件驱动的运行时环境,用于在浏览器之外执行 JavaScript 代码。您还可以自动化重新运行程序。让我们讨论如何实现。

4. 自动重新运行 JavaScript 程序

默认情况下,Node.js 要求您每次修改代码时手动重新运行 JavaScript 文件。

在修改代码时重复手动执行应用程序的过程可能很繁琐。幸运的是,Node 提供了 --watch 标志来自动化这一过程:

code
node --watch filename.extension

上面的代码片段使用 --watch 标志以监视模式启动 Node.js。这会导致 Node 在依赖图中的任何文件被更新时重新执行指定的脚本。

  • --watch : 启用 Node 监视模式的标志。
  • filename.extension : 您希望 Node 执行的脚本。

提示:在 Windows、macOS 或 Linux 上使用 Ctrl + C 停止运行过程。

默认情况下,--watch 标志会监视入口点及其所有依赖的模块。换句话说,监视命令会使 Node 监视:

  • 入口文件 (filename.extension)
  • 入口文件导入的所有文件
  • 这些导入中导入的所有文件
  • 以及整个依赖图中的其他文件

Node 的默认监视模式不会监视无关文件。如果希望 Node 监视其他文件,请使用 --watch-path 标志:

code
node --watch-path=./src --watch-path=./tests filename.extension

上面代码片段中的 --watch-path 标志告诉 Node 监视 src 和 tests 目录中的所有文件,即使它们不在入口点的依赖图中。

  • --watch-path : 指定 Node 需要监视更改路径的标志。

提示:

  • --watch-path 标志会使 Node 忽略依赖图中的模块,除非它们包含在 --watch-path 中。相反,Node.js 将只监视您指定的精确路径,不包括其他内容。
  • --watch-path 本身启用监视模式。如果您同时传递 --watch,Node 仍然只监视指定的路径。
  • --watch-path 在 macOS 和 Windows 上受支持,但 Linux 不支持。在 Linux 上,请使用 --watch 来监控入口文件及其依赖项。
  • 监听命令不会自动重新加载浏览器。它们仅检测文件更改,因此 Node 会重新运行你指定的程序。因此,如果你的程序向浏览器发送数据,你需要手动刷新浏览器或使用第三方工具实现自动刷新。

Node.js 支持两种模块系统。让我们来了解它们。

Node 的模块支持

Node.js 同时支持 CommonJS(.cjs)和 ECMAScript(.mjs)模块。这使你可以使用偏好的模块类型来创建 Node.js 应用程序。

例如,下面是一个 script.mjs JavaScript 文件。由于其文件扩展名为 .mjs,Node 会将其视为 ECMAScript 模块。

script.mjs

code
import http from "node:http";

另一方面,由于下面 script.cjs JavaScript 文件的文件扩展名为 .cjs,Node 会将其视为 CommonJS 模块。

script.cjs

code
const http = require("node:http");

如果你想为项目中的 .js 文件指定模块类型,可以在 package.json 文件中添加 type 字段,如下所示:

package.json

code
{
  "scripts": {
    "start": "node console.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "type": "module",
  "license": "ISC"
}

上面代码片段中的 "type": "module" 字段会让 Node 将受此 package.json 管理的 .js 文件视为 ES 模块。嵌套的 package.json 会建立自己的作用域。.mjs 和 .cjs 文件保留各自的模块类型。

将 "type" 字段设置为 "commonjs" 可让 Node 将该作用域内的 .js 文件视为 CommonJS 模块。

一些注意事项:

  • ES 模块系统是 JavaScript 的官方标准。
  • Kevin Dangoor 在 2009 年 1 月启动了后来成为 CommonJS 的项目,当时 JavaScript 还没有官方标准的模块系统。
  • ES 模块在 2015 年成为 ECMAScript 标准的一部分。CommonJS 仍然在 Node.js 中受支持。

在本指南中,我们将主要使用 Node.js 与 ES 模块配合,因为 ES 模块是 JavaScript 的标准模块系统。要跟随示例,请确保你的 package.json 文件中的 "type" 字段设置为 "module"。你可以通过从项目目录运行以下命令来实现:

code
npm pkg set type=module

在适当的时候,我们会比较 CommonJS 的语法。

现在让我们讨论如何使用 Node.js 创建接收并响应浏览器请求的 Web 服务器。

使用 Node.js 创建 HTTP 服务器

HTTP 服务器使你的 JavaScript 应用程序能够处理来自客户端(如浏览器)的请求并发送响应。

配置应用程序的 HTTP 服务器主要有三个步骤:

  • 创建 HTTP Server 对象的新实例。
  • 指定服务器运行的端口。
  • 定义服务器如何响应客户端请求。

让我们详细讨论这三个步骤。首先,为你的项目创建一个 ES 模块。

code
touch server.js

随后,打开新创建的模块并初始化 HTTP Server 对象的新实例。

初始化 HTTP Server 对象的新实例

Node 提供了 createServer 方法来创建 HTTP Server 对象(http.Server)的实例。

你可以通过从 Node 的 http 模块导入它来在项目中使用:

server.js

code
import { createServer } from "node:http";

const server = createServer();

以下是具体实现方式:

  • 导入语句:将 Node 的 http 库中 createServer API 导入到 server.js ES 模块中。
  • server:用于存储 createServer() 函数输出的 HTTP 服务器对象的变量。

以下是 CommonJS 的替代方案:

server.cjs

code
const http = require("node:http");

const server = http.createServer();

创建本地 HTTP 服务器对象后,需要指定服务器的端口。

配置服务器运行的系统端口

HTTP 服务器对象提供 listen() 方法来配置服务器运行的端口并接收客户端请求。

#### 语法

code
import { createServer } from "node:http";

const server = createServer();

server.listen(port, hostname, backlog, callback);

此 listen() 方法的参数说明如下:

  • port : (数字) 服务器运行和监听客户端请求的端口号。如果省略,操作系统将分配任意未使用的端口。服务器开始监听客户端连接后,可通过 server.address().port 获取当前监听的端口。
  • hostname : (字符串) 服务器接收客户端连接的主机名或 IP 地址。如果省略,服务器将默认使用未指定的 IPv6 (::) 地址或 IPv4 (0.0.0.0) 地址。这些未指定地址会绑定到对应地址族的所有网络接口。服务器开始监听客户端连接后,可通过 server.address().address 获取绑定的 IP 地址。
  • backlog : (数字) 等待连接队列的最大长度,默认值为 511。
  • callback : (函数) 服务器开始监听客户端请求时执行的函数。

#### 示例

code
import { createServer } from "node:http";

const server = createServer();

server.listen(3000, "127.0.0.1", 511, () => {
  const info = server.address();
  console.log(`Server running at http://${info.address}:
${info.port}/`);
});
  • server.listen():启动服务器监听客户端连接。(提示:该方法在服务器成功启动后会触发 listening 事件。)
code
const http = require("node:http");

const server = http.createServer();

server.listen(3000, "127.0.0.1", 511, () => {
  const info = server.address();
  console.log(`Server running at http://${info.address}:
${info.port}/`);
});

#### 什么是 127.0.0.1 地址?

127.0.0.1 地址是 IPv4 环回地址,指向本地计算机。主机名 localhost 也指向本地计算机,但可能解析到 127.0.0.1 或 IPv6 环回地址 ::1。使用方式如下:

code
import { createServer } from "node:http";

const server = createServer();

server.listen(3000, "localhost", 511, () => {
  console.log("Server running at http://localhost:3000/");
});

如果运行此服务器并访问其网页地址,浏览器将等待响应,因为我们尚未设置请求处理程序。现在我们来配置该处理程序。

配置服务器响应客户端请求

createServer() 方法接受一个 requestListener 回调函数,每当服务器接收到 HTTP 请求时会自动调用该回调。Node 允许使用此回调来响应请求。

createServer() 方法接受两个可选参数,语法如下:

code
import { createServer } from "node:http";

const server = createServer(options, callback);
  • options : 用于自定义服务器行为的对象。
  • callback : 用于处理和响应客户端请求的请求监听器函数。它接受两个参数:
code
import { createServer } from "node:http";

const server = createServer(options, function (request, response) {
  // 请求监听器函数的主体
});
  • request 参数:提供客户端请求详细信息的 http.IncomingMessage 对象。
  • response 参数:用于响应客户端请求的 http.ServerResponse 对象。

将 requestListener 回调函数作为 createServer 的第二个参数传入,会使 Node.js 自动将其注册为 "request" 事件的监听器。因此,上述语法等价于:

code
import { createServer } from "node:http";

const server = createServer(options);

server.on("request", function (request, response) {
  // 请求监听器函数的主体
});

server.on("request", callback) 告诉服务器监听请求事件并在发生该事件时执行回调函数。

code
// 引入 Node.js HTTP 模块
import { createServer } from "node:http";

// 指定运行服务器的主机名和端口
const hostname = "localhost";
const port = 3000;

// 使用请求监听器回调创建新的 Server 实例
const server = createServer((req, res) => {
  res.statusCode = 200; // 设置成功响应状态码(200)
  res.setHeader("Content-Type", "text/plain"); // 定义响应数据的媒体类型
  res.end("Hello World!"); // 指定响应数据并关闭响应流
});

// 在指定端口和主机名上运行服务器
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:
${port}/`);
});

上面的代码片段使用了请求监听器回调函数的 response 参数来配置服务器响应客户端请求。以下是 server.on("request", callback) 的替代方案:

code
// 引入 Node.js HTTP 模块
import { createServer } from "node:http";

// 指定运行服务器的主机名和端口
const hostname = "localhost";
const port = 3000;

// 创建新的 Server 实例
const server = createServer();

// 监听请求事件并执行请求监听器回调
server.on("request", (req, res) => {
  res.statusCode = 200; // 设置成功响应状态码(200)
  res.setHeader("Content-Type", "text/plain"); // 定义响应数据的媒体类型
  res.end("Hello World!"); // 指定响应数据并关闭响应流
});

// 在指定端口和主机名上运行服务器
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:
${port}/`);
});

现在服务器已配置并设置好端口,可以运行应用程序。

code
node server.js
  • server.js : Node.js 要运行的 JavaScript 文件。

当服务器运行时,如果用户通过服务器的网络地址请求应用资源,他们将看到如以下图片所示的 "Hello World!" 响应。

虽然 node:http 模块是 Node.js 处理 HTTP 请求的原生 API,但开发者通常会使用框架来简化流程。一些流行的 Node.js Web 框架包括 Express.js、Fastify 和 Koa.js。让我们以 Express.js 为例,看看框架如何简化 Node.js 中的 HTTP 请求处理。

Express.js 究竟是什么?

Express.js 是一个简化 Node.js 中服务器端 HTTP 请求处理的 Web 框架。它通过内置的路由和中间件等功能扩展了 Node 的 HTTP 模块,帮助开发者高效构建 Web 应用和 API,避免重复代码。以下是它在 Node.js 项目中的使用方式:

1. 安装 Express

code
npm install [email protected]

2. 使用 Express.js 配置项目的 Node.js Web 服务器

打开 JavaScript 服务器文件并使用 Express.js 设置 Web 服务器:

code
// 引入 Express 模块
import express from "express";

// 创建 Express 应用实例
const app = express();

// 设置服务器运行的端口号
const port = 3000;

// 为 "/" 路径的 GET 请求创建路由处理程序
app.get("/", (req, res) => res.send("Hello, world!"));

// 在指定端口启动服务器
app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

上述代码的作用:

  • 创建 Express 应用实例以访问 Express API(如路由器和中间件),简化 Node.js 中的 HTTP 请求处理。
  • 使用 Express 的 app.get() 方法定义服务器如何处理发送到 / 路径的 HTTP GET 请求。
  • 使用 Express 的 res.send() 方法发送 HTTP 响应,并根据提供的数据类型自动设置相应头信息。
  • 使用 Express 的 app.listen() 方法在特定端口启动 Node.js HTTP 服务器,并在服务器开始监听客户端请求时执行回调函数。
  • 在 app.get("/", (req, res) => res.send("Hello, world!")) 语句中,app.get() 是 Express 的路由方法,"/" 是路由路径,(req, res) => ... 是路由处理函数,整个语句构成路由(或路由定义)。
  • 传递给 Express 路由处理程序参数(req 和 res)的对象是 Node 的 http.IncomingMessage 和 http.ServerResponse 对象的增强版。Express 添加了属性和辅助方法以简化 HTTP 请求和响应的处理。
  • 路由定义的顺序会影响匹配逻辑。Express 按照定义顺序处理匹配路由。处理程序可以通过 next() 结束响应或将控制权传递给其他处理程序。

3. 运行 Express.js Web 服务器

使用 node 命令加上脚本文件名来运行 Node.js 脚本,包括使用 Express.js 处理 HTTP 请求的脚本。

在浏览器中打开 http://localhost:3000 即可看到 "Hello, world!"。

提示:在 Windows 上使用 Ctrl + C,在 macOS 上使用 Control + C 停止服务器。

原生 Node.js 代码与 Express.js 代码的对比

虽然可以使用 Node 原生 HTTP API 处理 Web 请求,但需要手动配置,例如检查每个请求的方法和 URL、设置响应头、将对象转换为 JSON 等。

Express 将重复的请求处理逻辑抽象为声明式方法,简化了 Node.js Web 服务器开发。

以下是两个示例,分别比较使用原生 HTTP 模块和 Express 方法编写的 Node.js Web 服务器代码:

示例 1:使用原生 Node.js 处理 Web 请求

以下 Web 服务器使用 Node 内置的 HTTP 模块处理发送到两个端点(主页 / 和书籍 /books)的 GET 请求,并对未匹配请求返回 404 响应。

code
import { createServer } from "node:http";
const port = 3000;
javascript
const server = createServer((req, res) => {
  if (req.method === "GET" && req.url === "/") {
    res.writeHead(200, { "Content-Type": "text/plain" });
    res.end("Welcome to the homepage!");
  } else if (req.method === "GET" && req.url === "/books") {
    const books = [
      { id: 1, name: "Code React Sweetly" },
      { id: 2, name: "Creating NPM Package" },
    ];
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify(books));
  } else {
    res.writeHead(404, { "Content-Type": "text/plain" });
    res.end("Page not found");
  }
});

server.listen(port, () => {
  console.log(`Raw Node server running at http://localhost:${port}`);
});

上述代码片段使用了 Node.js 原生的 http 模块构建了一个 Web 服务器,用于接收和响应浏览器连接。在这个原始的 Node.js 示例中,你需要手动处理以下任务:

  • 路由到正确端点:if (req.method === "...") && req.url === "..."
  • 响应头配置:res.writeHead(...)
  • JSON 格式化:JSON.stringify(...)

现在我们来看看如何使用 Express 构建一个类似的 Web 服务器。请依次运行这些示例,每次替换 server.js 的内容并重新启动服务器。

示例 2:使用 Express.js 处理 Web 请求

以下 Web 服务器使用 Express 路由处理两个端点的 GET 请求——首页(/)和书籍(/books),并为未匹配的请求返回 404 响应。

javascript
import express from "express";
const app = express();
const port = 3000;

app.get("/", (req, res) => {
  res.send("Welcome to the homepage!");
});

app.get("/books", (req, res) => {
  const books = [
    { id: 1, name: "Code React Sweetly" },
    { id: 2, name: "Creating NPM Package" },
  ];
  res.json(books);
});

app.use((req, res) => {
  res.status(404).send("Page not found");
});

app.listen(port, () => {
  console.log(`Express server running at http://localhost:${port}`);
});

上述代码片段使用 Express.js 构建了一个 Web 服务器,用于接收和响应浏览器连接。Express 简化了以下任务:

  • 路由逻辑:Express.js 提供了简单的路由声明方式,如 app.get()、app.post() 和 app.delete(),无需为每个路由编写复杂的条件逻辑来检查 req.url 和 req.method。
  • 响应头配置:Express 的 res.send() 方法会根据响应体自动设置合适的响应头。当需要设置状态码时(如上面的 404 处理器),可以使用 res.status()。
  • JSON 格式化:Express 的 res.json() 方法会自动将 JavaScript 对象转换为有效的 JSON 响应,为你处理序列化和响应头。

在 Node.js 中创建 Express.js Web 服务器的重要知识

在 Node.js 项目中创建 Express.js 服务器时,请记住以下关键点。

通常最后调用 app.listen()

在 Express.js 应用中,通常最后调用 app.listen()。这样可以在服务器开始监听请求之前,先设置好所有路由、中间件和错误处理器。

可以使用环境变量设置端口号

要从环境变量中读取端口号,请将现有的端口声明替换为以下内容:

javascript
const port = process.env.PORT || 3000;

以上代码片段指示 Node 使用 PORT 环境变量的值,如果该变量未设置或为空,则默认使用 3000。在 Bash 这样的 shell 中,以下命令会将 PORT 设置为 8000 来运行服务器:

code
PORT=8000 node server.js

Express.js 常见响应方法

以下是一些在 Express.js 中常用的响应方法:

  • res.send() : 向客户端发送 HTTP 响应,并根据方法参数提供的数据类型自动设置相应的响应头。
  • res.json() : 向客户端发送 JSON 响应,并自动将响应的 Content-Type 头设置为 application/json。
  • res.redirect() : 将客户端请求重定向到另一个 URL。
  • res.render() : 渲染模板引擎的视图,并将生成的 HTML 字符串发送给客户端。
  • res.status() : 设置响应的 HTTP 状态码,但不结束请求-响应周期。你可以将其与其他响应方法链接使用,例如 res.status(404).send("404 Error: Page not found")。你不需要调用此方法即可使用默认的 200 状态码。
  • res.end() : 结束响应。如果没有数据参数,它不会发送额外的响应体数据。它也可以在结束响应前接受数据进行发送。

Express 的响应方法不会终止 HTTP 请求处理器的执行

虽然像 res.send() 这样的响应方法会关闭 HTTP 请求-响应周期,但它们不会结束路由处理函数的执行。

以下是一个示例:

code
app.get("/", (req, res) => {
  // 这会向客户端发送文本响应并结束请求-响应周期,但不会结束路由处理函数的执行
  res.send("Hello, client!");

  // 由于函数仍在运行,这会将字符串记录到控制台
  console.log("Hello, devs!");

  // 这会导致错误,因为请求-响应周期已关闭,因此在回调执行期间无法向客户端发送额外响应
  res.send("Hello, client again!");
});

上述示例中的 console.log() 语句可以正常工作,因为请求处理函数仍在运行。res.send("Hello, client!") 行结束了对该请求的响应。它不会阻止回调的执行。

请注意,上面代码片段中的 app.get(...) 被称为一个 Express 路由。但 Express.js 中的“路由”到底是什么?我们来讨论一下。

Express.js 中的路由是什么?

Express.js 中的路由由 HTTP 方法、URL 路径和一个或多个用于处理客户端请求的中间件函数链组成。

  • 端点由 HTTP 方法和 URL 路径组成。(端点 = HTTP 方法 + URL 路径)
  • 路由包括 HTTP 方法、URL 路径以及处理客户端请求的服务器端逻辑。(路由 = 服务器 HTTP 方法 + 服务器 URL 路径 + 处理函数)

Express 中路由的语法

code
app.METHOD(PATH, HANDLER);
  • app : 一个 Express 应用实例,提供对 Express API 的访问,包括方法和中间件,以简化 Node.js 中的 HTTP 请求处理。
  • METHOD : 一个小写的 HTTP 请求方法,如 get、post 或 delete。
  • PATH : 路由将处理的 URL 路径。它可以是字符串或正则表达式。Express 4 还支持字符串模式,其语法在 Express 5 中有所不同。
  • HANDLER:当请求与路由端点匹配时,Express 执行的回调(或中间件)函数。处理程序可以是:单个函数、多个函数、函数数组、函数数组与单个函数参数的组合。
  • 可以使用 app.all() 方法为特定路径下所有支持的 HTTP 请求方法设置处理程序。
  • app.use() 方法允许设置中间件函数,这些函数可以处理所有 HTTP 请求方法和路径。如果指定了路径参数,app.use() 会匹配该路径及其子路径。例如,/book 会匹配 /book、/book/dashboard 和 /book/dashboard/author/202605。

中间件与路由处理程序

开发者通常使用 "路由处理程序" 一词描述最终负责处理请求并发送响应的函数,而前面的可复用函数则被称为 "中间件"。

code
app.METHOD(PATH, MIDDLEWARE1, MIDDLEWARE2, HANDLER);

注意:Express 不强制要求附加到路由的函数具有特定名称或分类。从 Express 的角度来看,它们都是处理程序函数,通过 next() 将控制权传递给下一个函数。然而,开发者通常会区分中间件和路由处理程序,以提高代码的组织性、可读性和沟通效率。

  • 中间件函数通常存储在 /middlewares 目录中,作为执行跨领域或预处理任务的实用组件,例如身份验证、验证、日志记录和数据加载。这些函数通常可复用,并通过调用 next() 将控制权传递给下一个函数。
  • 路由处理程序有时组织在 /controllers 目录中,包含生成端点主要响应的业务逻辑,例如 getUserProfile、createBlogPost 或 deleteAccount。这些函数可能特定于某个路由,通常通过调用 res.send() 或 res.json() 等方法结束请求-响应周期。

Express.js 路由示例

以下是 Express.js 中路由的一些示例。

#### 带单个处理程序函数的 Express 路由

code
app.get("/single/handler", (req, res) => {
  res.send("Request handled with a single handler function!");
});

上述示例使用单个回调函数处理发送到 /single/handler 端点的 GET 请求。在这种情况下,处理程序函数是一个内联控制器。常见的做法是将控制器保留在 controllers 目录中。这使代码更易于测试和维护,同时保持路由定义的可读性。

以下示例展示了将模块化控制器移动到其自己的模块(controllers/single-handler.js)后的 GET /single/handler 路由。这种方法将路由定义与生成端点响应的请求处理逻辑分离。

code
import express from "express";
import * as controller from "./controllers/single-handler.js";

const app = express();
const port = 3000;

app.get("/single/handler", controller.singleHandler);

app.listen(port, () => {
  console.log(`Express server running at http://localhost:${port}`);
});

下面是仅包含 singleHandler 处理程序函数的控制器文件示例。

controllers/single-handler.js

code
function singleHandler(req, res) {
  res.send("Request handled with a single handler function!");
}

export { singleHandler };

提示:控制器是用于处理特定端点请求的路由处理程序或一组处理程序。为了提高代码组织性并保持关注点分离,控制器通常放置在单独的模块中。

#### 使用多个中间件函数的 Express 路由

code
app.get(
  "/multiple/middleware",
  (req, res, next) => {
    console.log("First callback: passing control to the next middleware >");
    next();
  },
  (req, res, next) => {
    console.log("Second callback: passing control to the next middleware >");
    next();
  },
  (req, res) => {
    res.send("Request handled with multiple middleware functions!");
  },
);

上述示例使用多个中间件函数来处理发送到 /multiple/middleware 路径的 GET 请求。

#### 使用中间件函数数组的 Express 路由

code
const arrayOfCallbacks = [
  function callback1(req, res, next) {
    console.log("First callback: passing control to the next middleware >");
    next();
  },
  function callback2(req, res, next) {
    console.log("Second callback: passing control to the next middleware >");
    next();
  },
  function callback3(req, res) {
    res.send("Request handled with an array of middleware functions!");
  },
];

app.get("/array/middleware", arrayOfCallbacks);

上述示例使用回调函数数组来处理发送到 /array/middleware 端点的 GET 请求。

#### 使用中间件函数数组和单独回调组合的 Express 路由

code
const arrayOfCallbacks = [
  function callback1(req, res, next) {
    console.log("First callback: passing control to the next middleware >");
    next();
  },
  function callback2(req, res, next) {
    console.log("Second callback: passing control to the next middleware >");
    next();
  },
];

app.get(
  "/mix/middleware",
  arrayOfCallbacks,
  (req, res, next) => {
    console.log("Third callback: passing control to the next middleware >");
    next();
  },
  (req, res, next) => {
    console.log("Fourth callback: passing control to the next middleware >");
    next();
  },
  (req, res) => {
    res.send(
      "Request handled with a combination of an array of middleware functions and independent callback arguments!",
    );
  },
);

上述示例使用中间件函数数组和独立回调参数组合来处理发送到 /mix/middleware 路径的 GET 请求。

关于 next 参数的重要知识

  • next() 将控制权传递给堆栈中的下一个中间件函数。
  • next("route") 会跳过当前路由的剩余处理程序,并继续查找匹配的路由。匹配会同时考虑请求方法和路径。此功能仅在为 app.METHOD() 或 router.METHOD() 定义的中间件函数中有效。
  • next(new Error(value)) 将控制权传递给下一个错误处理中间件。
  • 如果当前运行的中间件未结束请求-响应周期,请在回调中包含 next 参数并调用它以将控制权传递给下一个中间件。否则,客户端的请求将保持挂起状态。

Express.js 中的中间件分类

Express 中间件可以分为五个主要类别:

  • 应用级
  • 路由级
  • 错误处理
  • 内置
  • 第三方

这些分类描述了中间件的挂载位置、行为方式或来源。

#### 应用级中间件

此类中间件附加到 Express 应用程序实例(app)。

code
import express from "express";
const app = express();

app.get("/profile", (req, res, next) => {
  console.log("Hi, there!");
  next();
});

#### 路由器级中间件

此类中间件绑定到 Express Router 实例(router)上。其工作方式与应用级中间件类似,但挂载在路由器上而非应用程序上。

code
import express from "express";
const router = express.Router();

router.get("/profile", (req, res, next) => {
  console.log("Hi, there!");
  next();
});

#### 错误处理中间件

此类中间件用于处理请求处理过程中的错误,通过其四个参数的签名特征被识别。

code
import express from "express";
const app = express();

app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).send("Error 500: Encountered an unexpected error");
});

注意:

  • 必须保留所有四个参数,即使某些参数未被使用。否则 Express 将无法识别该函数为错误处理中间件。Express 通过声明的参数数量(而非参数名称)来识别此类中间件。
  • 应在定义错误处理中间件时,将其放置在需要处理错误的路由和中间件之后。这种排列方式确保它能处理前面中间件函数传递的错误。

#### 内置中间件

这些中间件函数随 Express 一同提供,默认情况下无需安装额外包即可使用。

示例:

code
import express from "express";

express.static("public");
express.json();
express.urlencoded();

#### 第三方中间件

这些中间件函数由 Express 社区开发并共享的外部包提供。示例包括 cors、morgan 和 helmet。

code
import express from "express";
import cors from "cors";

const app = express();
app.use(cors());

#### 注意:Express 中的中间件分类并非互斥

示例 1:

code
import express from "express";

const app = express();
app.use(express.json());

上述代码片段中的 express.json() 中间件同时属于:

  • 应用级中间件,因为它绑定到 app 实例上
  • 内置中间件,因为 Express 提供了该功能

示例 2:

code
import express from "express";
import cors from "cors";

const router = express.Router();
router.use(cors());

上述代码片段中的 cors() 中间件同时属于:

  • 路由器级中间件,因为它绑定到 router 实例上
  • 第三方中间件,因为它是 Express 之外的包提供的

你是否在问上面示例中的 express.Router() 方法是什么?我们来讨论一下。

express.Router() 方法是什么?

express.Router() 方法创建一个路由器对象,用于将相关路由和中间件进行分组。你可以通过 app.use() 或 router.use() 将此路由器附加到 Express 应用程序或其他路由器上。

如上图所示,路由器通常被称为“迷你应用”,因为它提供了一个完整的中间件和路由系统。它通过允许你在一处定义相关路由并将其挂载到主 Express 应用程序上,帮助组织应用程序。例如,假设你的 Express 应用程序需要处理以下类别路由和中间件:

  • 索引:用于索引相关路由和中间件。
  • 书籍:用于书籍相关路由和中间件。
  • 视频:用于视频相关路由和中间件。

在这种情况中,你可以使用 express.Router() 方法对相关路由和中间件进行分组。这有助于保持代码的条理性,并使每个组更易于管理和复用。

虽然你可以将所有路由器和 app 实例创建在一个文件中,但开发者通常会将路由器放在单独的文件中以实现更好的组织性和可维护性。让我们通过为每个路由和中间件类别创建单独的文件来采用这种做法。

为路由器创建目录

在项目根目录下创建一个 routes 目录来存储所有应用的路由器。

code
mkdir routes

注意:你可以将这个文件夹命名为任何你喜欢的名字,但 routes 是一个常见的命名方式,因为它保存了定义应用路由(URL 端点)的文件。express.Router() 方法只是 Express 提供的一个用于管理这些路由的工具。

创建路由文件

路由文件是一个模块,你可以在其中定义相关路由并导出一个 Express 路由器对象。每个文件通常代表一个资源或功能,例如用户、书籍或认证。

在 routes 目录中创建路由文件。

code
touch routes/index.js routes/books.js routes/videos.js

为书籍相关路由和中间件创建微型应用

打开 books.js 文件,使用 express.Router() 实例对所有书籍相关路由和中间件进行分组。

routes/books.js

code
// 从 Express 导入 Router 函数
import { Router } from "express";

// 创建一个新的 Express 路由器实例
const bookRouter = Router();

// 为这个路由器实例定义特定的中间件
bookRouter.use((req, res, next) => {
  console.log("Book route accessed at: ", Date.now());
  next();
});

// 为这个路由器实例定义路由:

bookRouter.get("/", (req, res) => {
  res.send("<h1>List of All Books</h1>");
});

bookRouter.get("/:id", (req, res) => {
  res.type("text/plain").send(`Details for book ${req.params.id}`);
});

bookRouter.get("/:id/draft", (req, res) => {
  // 在插入 HTML 文本前对 ID 进行转义
  const bookId = req.params.id
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;");
  res.send(`<h1>New Draft for Book ${bookId}</h1>`);
});

export { bookRouter };
  • 创建 bookRouter 实例以访问 Express API,例如 .get()、.post()、.use() 和 .route()。
  • 使用 Express 的 router.use() 方法将中间件挂载到 bookRouter 实例上。
  • 使用 Express 的 router.get() 方法定义服务器如何处理 HTTP GET 请求。
  • 使用 Express 的 res.send() 方法发送 HTTP 响应,并根据提供的数据类型自动设置适当的头部。/:id 处理程序显式将响应类型设置为纯文本,而 /:id/draft 处理程序在将 ID 插入 HTML 前对其进行转义以确保以文本形式显示。
  • 使用导出语句使路由器对象在应用的其他需要的地方可以导入使用。

现在是时候用路由器进行练习了

现在是你尝试所学 Express 路由器知识的时刻。

在这个练习中,请为项目的 index 和 video 相关路由和中间件创建微型应用。将它们分别从 routes/index.js 导出为 indexRouter,从 routes/videos.js 导出为 videoRouter,以便与下一节的导入语句匹配。

在继续之前,花点时间自己尝试一下。如果需要帮助,请回顾 books.js 文件。实践将帮助你更好地理解。完成练习后,继续设置主 Express 应用。

更新主 Express 应用程序

打开项目中的 server.js 文件,将路由器附加到主 Express 应用程序。

code
// 引入 Express 模块
import express from "express";

// 从单独文件中导入路由器
import { indexRouter } from "./routes/index.js";
import { bookRouter } from "./routes/books.js";
import { videoRouter } from "./routes/videos.js";

// 创建新的 Express 应用程序实例
const app = express();

// 指定接收请求的主机和端口
const host = "localhost";
const port = 3000;

// 将路由器挂载到特定路径:

// 将 indexRouter 挂载到 /
app.use("/", indexRouter);

// 将 bookRouter 挂载到 /books 及其子路径
app.use("/books", bookRouter);

// 将 videoRouter 挂载到 /videos 及其子路径
app.use("/videos", videoRouter);

// 在指定端口和主机上运行服务器
app.listen(port, host, () => {
  console.log(`Server running live at http://${host}:
${port}!`);
});
  • 创建 Express 应用程序实例以访问 Express API,如路由器和中间件,这简化了 Node.js 中的 HTTP 请求处理。
  • 使用 Express 的 app.use() 方法将子应用程序添加到主 Express 应用程序。

注意:app.use() 方法的路径作为路由器的基准路径或挂载路径。路由器中定义的路由将相对于此挂载路径进行匹配。例如:

  • bookRouter 的 / 路径匹配 /books 的请求。
  • bookRouter 的 /:id 路径匹配 /books/:id 的请求。
  • bookRouter 的 /:id/draft 路径匹配 /books/:id/draft 的请求。

运行 Express.js 应用程序

设置完路由器后,通过 Node.js 启动服务器文件来运行应用程序。

code
node --watch server.js

然后,在指定的端点检查应用程序的运行情况。例如,http://localhost:3000/books 。

关于 Express 路由器的重要知识

使用 Express 路由器时需要记住的一些重要事项。

#### 默认情况下路由器不会继承父路由参数

默认情况下,Express 不会将路由器挂载路径的参数传递给路由器内部的路由。

code
// 引入 Express 模块
import express from "express";

// 创建新的 Express 应用程序实例
const app = express();

// 创建新的 Express 路由器实例
const bookRouter = express.Router();

// 定义子路由
bookRouter.get("/books/:bookId", (req, res) => {
  const topic = req.params.topic; // 默认为 undefined
  const bookId = req.params.bookId;
  res.json({ topic, bookId });
});

// 定义父路由
app.use("/codesweetly/:topic", bookRouter);

// 在指定端口和主机上运行服务器
const server = app.listen(3000, "127.0.0.1", () => {
  const { address, port } = server.address();
  console.log(`Server running live at http://${address}:
${port}!`);
});

如果运行上述示例中的 Web 服务器,并在浏览器中输入以下 URL:

code
http://127.0.0.1:3000/codesweetly/CSS/books/91827

req.params.topic 参数将为 undefined,因为默认情况下子路由无法访问父路由的路径参数。因此,服务器只会向客户端发送 {"bookId":"91827"} 的响应。

要让子路由访问父路由的路径参数,创建路由器时需要将 mergeParams 设置为 true。

code
// 引入 Express 模块
import express from "express";

// 创建新的 Express 应用程序实例
const app = express();
markdown
// 创建一个新的 Express 路由器实例
const bookRouter = express.Router({ mergeParams: true });

// 定义一个子路由
bookRouter.get("/books/:bookId", (req, res) => {
  const topic = req.params.topic; // 对于下面的 URL,值为 "CSS"
  const bookId = req.params.bookId;
  res.json({ topic, bookId });
});

// 定义父路由
app.use("/codesweetly/:topic", bookRouter);

// 在指定端口和主机上运行服务器
const server = app.listen(3000, "127.0.0.1", () => {
  const { address, port } = server.address();
  console.log(`Server running live at http://${address}:
${port}!`);
});
code
如果运行上面示例中的 Web 服务器,并在浏览器中输入以下 URL:

req.params.topic 参数将为 CSS,因为 mergeParams: true 允许子路由访问父路由的路径参数。因此,服务器将向客户端发送 {"topic":"CSS","bookId":"91827"} 的响应。

- const { address, port } = server.address() 这一行使用对象解构从 server.address() 对象中获取地址和端口值。

- app.listen() 方法返回一个原生的 Node.js http.Server 实例,允许访问底层服务器 API,如 .address()、.on() 和 .maxHeadersCount。

- 127.0.0.1 是一个 IPv4 环回地址。localhost 可能解析到 127.0.0.1 或 IPv6 环回地址 ::1。这些示例中使用 http://127.0.0.1:3000,因为服务器明确监听该 IPv4 地址。

#### 路由器可以挂载到其他路由器上

你可以将一个 Express 路由器附加到另一个路由器上。

// 引入 Express 模块 import express from "express";

// 创建一个新的 Express 应用实例 const app = express();

// 创建两个 Express 路由器实例 const htmlRouter = express.Router(); const bookRouter = express.Router();

// 定义子路由 bookRouter.get("/:bookId", (req, res) => { res .type("text/plain") .send(Details for the HTML book with ID ${req.params.bookId}); });

// 将 bookRouter 挂载到 htmlRouter 的 /books 路径下 htmlRouter.use("/books", bookRouter);

// 在 /html 及其子路径下挂载 htmlRouter app.use("/html", htmlRouter);

// 在指定端口和主机上运行服务器 const server = app.listen(3000, "127.0.0.1", () => { const { address, port } = server.address(); console.log(Server running live at http://${address}: ${port}!); });

code

bookRouter 的 GET /:bookId 路由会匹配对 GET /html/books/:bookId 的请求,因为在本示例中,book 路由器被附加到 htmlRouter 上。

因此,如果你运行上面示例中的 Web 服务器,并在浏览器中输入以下 URL:

http://127.0.0.1:3000/html/books/91827

code

服务器将向客户端发送 "Details for the HTML book with ID 91827" 的响应。

现在你已经了解了路由器的概念,接下来我们讨论如何使用视图来定义 Express.js 应用返回给用户的网页内容和结构。

## Express.js 中的视图是什么?

视图是一个模板或文档,用于定义 Express.js 应用返回给用户的网页内容和结构。它可以是静态 HTML 文件,也可以是通过模板引擎在运行时渲染成 HTML 的动态模板。

在本指南中,我们将处理两种类型的视图:

- 静态视图

- 动态视图

### Express.js 中的静态视图是什么?

静态视图包含服务器直接发送到浏览器的最终 HTML。例如,让我们配置你的 Express.js 应用来提供四个静态视图。

#### 创建静态视图

在项目根目录中创建 index.html、about.html、contact.html 和 404.html 文件。

code
touch index.html about.html contact.html 404.html

打开每个视图文件并添加以下 HTML 内容:

##### index.html(首页)

code
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Express.js 中的静态视图 | CodeSweetly 教程</title>
  </head>
  <body>
    <h1>欢迎来到静态视图指南</h1>
    <p>网站页面:</p>
    <ul>
      <li><a href="/about">关于</a></li>
      <li><a href="/contact">联系</a></li>
    </ul>
  </body>
</html>

##### about.html(关于页面)

code
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>关于我们 | Express.js 中的静态视图</title>
  </head>
  <body>
    <h1>关于我们</h1>
    <p>
      Lorem ipsum dolor sit amet consectetur adipisicing elit. Libero soluta
      voluptas reprehenderit minus veniam! Corrupti a esse quidem nostrum harum,
      explicabo tempore tempora aut, sint et voluptatem magni ea vel?
    </p>
    <div><a href="/">返回首页</a></div>
  </body>
</html>

##### contact.html(联系页面)

code
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>联系 | Express.js 中的静态视图</title>
  </head>
  <body>
    <h1>联系我们</h1>
    <ul>
      <li><a href="https://codesweetly.com">官网</a></li>
      <li><a href="https://x.com/oluwatobiss">X(推特)</a></li>
    </ul>
    <div><a href="/">返回首页</a></div>
  </body>
</html>

##### 404.html(错误页面)

code
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>404 - 页面未找到 | Express.js 中的静态视图</title>
  </head>
  <body>
    <h1>页面未找到</h1>
    <div>抱歉,我们找不到该页面</div>
    <div><a href="/">返回首页</a></div>
  </body>
</html>

接下来配置项目以处理浏览器连接。

#### 配置项目的 Web 服务器以提供静态视图

打开项目的 server.js 模块,配置 Web 服务器以响应请求时提供相应的静态 HTML 视图。

code
// 添加所需的模块
import express from "express";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

// 创建新的 Express 应用实例
const app = express();

// 指定接收请求的主机名和端口
const host = "localhost";
const port = 3000;

// 获取当前文件路径的目录名
const __dirname = dirname(fileURLToPath(import.meta.url));

// 将 index.html 静态视图作为 'GET /' 请求的响应
app.get("/", (req, res) => res.sendFile(join(__dirname, "index.html")));

// 将 about.html 静态视图作为 'GET /about' 请求的响应
app.get("/about", (req, res) => {
  res.sendFile(join(__dirname, "about.html"));
});

// 将 contact.html 静态视图作为 'GET /contact' 请求的响应
app.get("/contact", (req, res) => {
  res.sendFile(join(__dirname, "contact.html"));
});
markdown
// 将 404.html 静态视图用作未匹配请求的响应
app.use((req, res) => {
  res.status(404).sendFile(join(__dirname, "404.html"));
});

// 在指定端口和主机名上运行服务器
app.listen(port, host, () => {
  console.log(`Server running live at http://${host}:
${port}`);
});

上述代码片段主要实现了以下功能:

  • 创建一个 Express 应用程序实例以访问 Express API,如路由器和中间件,这些功能简化了 Node.js 中的 HTTP 请求处理。
  • 获取当前文件路径的目录名。dirname() 返回路径的目录名。fileURLToPath() 将 URL 转换为有效的路径字符串。import.meta.url 返回当前文件的绝对 URL。
  • 创建路由以接收和响应客户端的请求。app 是一个 Express 应用程序实例。app.get() 方法定义了服务器如何处理指定路径(方法的第一个参数)的 HTTP GET 请求。res.sendFile() 方法从服务器向客户端发送文件。join() 使用当前操作系统的路径分隔符将多个路径段组合成一个规范化的路径字符串。app.use() 方法允许您设置处理函数,Express 会为所有 HTTP 请求方法和路径运行这些处理函数。如果您指定了路径参数,app.use() 会将其视为匹配路由的前缀。例如,/book 匹配 /book、/book/dashboard 和 /book/dashboard/author/202605。res.status(404) 将 HTTP 状态码设置为 404,用于处理到达此最终处理程序但未收到响应的请求。
  • 使用 Express 的 app.listen() 方法在特定端口启动 Node.js HTTP 服务器,并在服务器开始监听客户端请求时执行回调。

#### 运行 Express.js 应用程序

设置好服务器后,使用 Node.js 运行它。

之后,您可以在 http://localhost:3000 查看应用程序的实时运行情况。

为了保持整洁,将所有静态视图放在 public 目录中。这是一种常见的做法。

#### 为静态视图创建目录

在项目根目录下创建一个 public 目录以存储所有静态视图。

code
mkdir public

注意:您可以将此文件夹命名为任何名称,但 public 是一个常见的选择,用于表明其中的文件(HTML、CSS、客户端 JavaScript、图片)旨在提供给客户端。文件夹名称本身不会使其内容可访问,服务器必须进行配置才能提供这些内容。不要将敏感信息放在其中。

#### 将所有静态视图移动到 public 文件夹

code
mv index.html about.html contact.html 404.html public/

#### 更新服务器以从 public 文件夹获取静态视图

code
import express from "express";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const app = express();
const host = "localhost";
const port = 3000;
const __dirname = dirname(fileURLToPath(import.meta.url));

// 从 'public' 文件夹提供静态资源(HTML、CSS、图片)
app.use(express.static(join(__dirname, "public")));

// 使用 index.html 静态视图响应 'GET /' 请求
app.get("/", (req, res) => {
  res.sendFile(join(__dirname, "/public/index.html"));
});

// 使用 about.html 静态视图响应 'GET /about' 请求
app.get("/about", (req, res) => {
  res.sendFile(join(__dirname, "/public/about.html"));
});

// 使用 contact.html 静态视图作为 'GET /contact' 请求的响应 app.get("/contact", (req, res) => { res.sendFile(join(__dirname, "/public/contact.html")); });

// 使用 404.html 静态视图作为未匹配请求的响应 app.use((req, res) => { res.status(404).sendFile(join(__dirname, "/public/404.html")); });

// 在指定端口和主机名上运行服务器: app.listen(port, host, () => { console.log(Server running live at http://${host}: ${port}); });

code

注意 app.use(express.static(join(__dirname, 'public'))) 这一行告诉 Express 在处理静态资源请求时优先检查 public 文件夹。如果在该文件夹中找到文件,会立即发送给浏览器并终止请求-响应周期。

- 当用户请求 localhost:3000/about.html 时,express.static 会在 public 文件夹中查找 about.html 文件。如果找到,Express 会直接发送该文件,而不会执行静态中间件之后的路由处理程序。

- 当用户请求 localhost:3000/about 时,express.static 会在 public 文件夹中查找没有扩展名的 about 文件。如果找到的是 about 目录,会重定向到 /about/ 并默认查找 index.html。根据本指南创建的文件,既不存在 about 文件也不存在 about 目录,因此 Express 会使用 GET /about 自定义路由来处理该请求。

现在让我们讨论动态视图。

### Express.js 中的动态视图是什么?

动态视图包含占位符,这些占位符由模板引擎(如 EJS)在运行时用数据替换。

Express.js 支持 EJS、Pug 和 Handlebars 等模板引擎。让我们以 EJS 为例来讨论动态视图的工作原理。

#### 什么是 EJS?

EJS(Embedded JavaScript templating)是一种模板语言,它允许你通过在 HTML 模板中使用模板标签嵌入 JavaScript 代码来创建动态 HTML 视图。

当服务器渲染模板时,EJS 引擎会执行嵌入的 JavaScript 并生成最终发送给客户端的 HTML。

例如,考虑以下代码。在运行此示例之前,请使用 npm install ejs 安装 EJS:

// 引入 Express 和 EJS 模块 import express from "express"; import ejs from "ejs";

// 创建新的 Express 应用实例 const app = express();

// 指定接收请求的主机和端口 const host = "localhost"; const port = 8000;

// 定义数据对象 const friendsArray = ["Sarah", "Abraham", "Mary"]; const replacementObject = { friends: friendsArray };

// 使用反引号定义包含嵌入 JavaScript 的模板字符串 const templateString = <h1>Hello, <%= friends.join(", "); %>!</h1> <p>Welcome to CodeSweetly.</p> ;

// 编译并渲染模板字符串为 HTML(渲染后的视图) const html = ejs.render(templateString, replacementObject);

// 使用动态视图作为 'GET /' 请求的响应 app.get("/", (req, res) => res.send(html));

// 在指定端口和主机名上运行服务器 app.listen(port, host, () => { console.log(Server running live at http://${host}: ${port}!); });

code

上述代码片段告诉 Express 当用户请求 / 路径时,将渲染后的视图发送给客户端。

在运行时,EJS 使用作为 friends 传递的数组评估 friends.join(", ") 并将结果 Sarah, Abraham, Mary 插入到 HTML 中。此处,渲染在脚本启动时发生一次。每个请求都会收到相同的渲染后的 HTML。

#### EJS 模板标签是什么?

EJS 模板标签是使用 `<% ... %>` 语法编写的特殊标签。它们允许将 JavaScript 代码和值嵌入到 EJS 视图模板中。

当 EJS 渲染模板时,脚本标签执行 JavaScript 代码,输出标签插入值,而注释标签会被忽略。

例如,下面的 `<%= %>` 标签允许将 JavaScript 的 friend 变量嵌入到视图模板中。

<h1>About <%= friend %>, my special pal</h1>

code

注意:像 `<%= %>` 这样的输出标签必须包含有效的 JavaScript 表达式。脚本标签可以包含 JavaScript 语句和控制流结构的部分,而注释标签包含注释。

- `<%= <p>I love CodeSweetly</p> %>` 会抛出错误,因为 `<p>I love CodeSweetly</p>` 是 HTML,而不是 JavaScript。

- `<%= "<p>I love CodeSweetly</p>" %>` 是有效的,因为 `"<p>I love CodeSweetly</p>"` 作为 JavaScript 字符串数据类型被嵌入。

#### EJS 模板标签类型

此处涵盖的九种 EJS 标签形式如下:

提示:将 JavaScript 代码包裹在 EJS 标签中,以区分周围的模板内容。一对标签可以包含多行 JavaScript 代码。

##### 结束标签 ( %> )

使用结束标签来结束一个 EJS 标签。它本身没有特殊行为。

##### 脚本标签 ( <% )

使用脚本标签来运行 JavaScript 代码,而不会将任何内容输出到生成的 HTML 中。此标签常用于控制流,例如 if、for 和 forEach 逻辑。

<ul> <% friends.forEach(function () { %> <li>My friend</li> <% }) %> </ul>

code

上面的代码片段将 `<% %>` 包裹在控制流语法周围,告诉 EJS 这段 JavaScript 代码仅用于逻辑,不应渲染到生成的 HTML 中。

##### 转义输出标签 ( <%= )

使用转义输出标签来评估 JavaScript 表达式,并将结果以转义 HTML 字符的方式输出到生成的 HTML 中。此标签有助于防止 HTML 注入。

<%= 100 + 200 %>

code

上面的代码片段将算术表达式包裹在 `<%= %>` 中,告诉 EJS 将代码的值渲染到生成的 HTML 中。

注意:如果表达式评估结果为包含 HTML 的字符串,`<%= %>` 会转义 HTML 语法,使浏览器将其显示为文本。例如,`<%= "<h1>About CodeSweetly</h1>" %>` 会将 `<h1>About CodeSweetly</h1>` 作为文本渲染,而不是实际的 `<h1>` 元素。

##### 非转义输出标签 ( <%- )

使用非转义标签来评估 JavaScript 表达式,并将结果以不转义 HTML 字符的方式输出到生成的 HTML 中。

<%- "<h1>About CodeSweetly</h1>" %>

code

上面的代码片段将包含 `<h1>` 元素的字符串包裹在 `<%- %>` 中,使浏览器将其渲染为 HTML。仅在信任的 HTML 内容中使用非转义输出。

##### 字面量开头标签 ( <%% )

使用字面量标签来输出字面量 `<%` 序列,而不是将其视为 EJS 标签。当希望在生成的输出中显示 EJS 语法时,这非常有用。

<%% "<p><strong>Name:</strong> <em>Oluwatobi</em></p>" %>

code

上面的代码片段输出字面量 `<%` 后跟剩余文本,而不会将其视为 JavaScript 进行求值。生成的 HTML 源代码为:

<% " <p><strong>Name:</strong> <em>Oluwatobi</em></p> " %>

code

HTML 标签保持未转义,因此这显示的是生成的源代码,而不是浏览器如何显示页面。

##### 换行符修剪结束标签 ( -%> )

使用换行符修剪结束标签来删除结束标签后的换行符。

<ul> <% friends.forEach(function (friend) { %> <li><%= friend -%></li> <% }) %> </ul>

code

上述代码片段将 <%= -%> 包裹在 friend 变量周围,告诉 EJS 删除关闭 EJS 标签后可能出现的换行符。

提示:换行符被删除的结束标签也称为换行符吞噬标签。

##### 空白符吞噬起始标签( <%_ )

使用空白符吞噬标签可以删除同一行中 EJS 起始标签前的空格和制表符。

<ul> <%_ friends.forEach(function (friend) { %> <li><%= friend %></li> <%_ }) %> </ul>

code

上述代码片段将 <%_ %> 包裹在控制流语法周围,告诉 EJS 删除模板标签前的缩进。

##### 空白符吞噬结束标签( _%> )

使用空白符吞噬结束标签可以删除关闭标签后的空格和制表符,并在存在时删除一个换行符。

<ul> <% friends.forEach(function (friend) { _%> <li><%= friend %></li> <% }) _%> </ul>

code

上述代码片段将 <% _%> 包裹在控制流语法周围,告诉 EJS 删除每个关闭标签后的换行符。下一行的缩进仍然保留。

##### 注释标签( <%# )

使用注释标签可以添加 EJS 注释,这些注释在渲染时会被忽略,也不会出现在生成的 HTML 中。

<ul> <%# Loop through the friends array %> <% friends.forEach(function (friend) { _%> <li><%= friend %></li> <% }) _%> </ul>

code

上述代码片段将注释包裹在 <%# %> 中。

提示:EJS 允许你组合一些标签。例如,以下组合是可能的:

- <%_ _%> :组合空白符吞噬脚本标签和结束标签,删除同一行上的相邻空格和制表符,以及存在的后续换行符。

- <% -%> :组合控制流与换行符吞噬。

- <%= -%> :组合转义标签与换行符吞噬。

#### 如何在 Express 项目中使用 EJS

以下部分将指导你如何在 Express.js 项目中使用 EJS。

##### 安装 EJS

首先,在 Express 项目中安装 EJS。

npm install [email protected]

code

接下来,打开项目中的 server.js 模块,并配置 Web 服务器以响应请求时提供适当的动态生成 HTML 视图。

// 添加 Express 和 EJS 模块 import express from "express"; import ejs from "ejs";

// 创建新的 Express 应用实例 const app = express();

// 指定接收请求的主机和端口 const host = "localhost"; const port = 8000;

// 定义数据对象 const friendsData = { bestFriend: "Sarah", codingFriend: "Abraham", dreamFriend: "Mary", }; const replacementObject = { friends: friendsData };

// 使用反引号定义带有嵌入式 JavaScript 的模板字符串 const templateString = <html> <body> <h1>Friends 列表</h1> <ul> <%# 遍历 friends 对象 %> <% for (const eachFriend in friends) { -%> <li><%= friends[eachFriend] %> 是我的 <%= eachFriend %></li> <% } -%> </ul> </body> </html> ;

// 编译并渲染模板字符串为 HTML(渲染后的视图) const htmlData = ejs.render(templateString, replacementObject);

// 将动态视图作为 'GET /' 请求的响应 app.get("/", (req, res) => res.send(htmlData));

// 在指定端口和主机名上运行服务器 app.listen(port, host, () => { console.log(服务器正在 http://${host}: ${port} 上运行!); });

code

- 在脚本启动时一次性渲染模板字符串,生成服务器发送给每个客户端的HTML。

- 创建路由以接收和响应客户端的请求。

##### 运行 Express 应用

设置好服务器后,使用 Node.js 运行它。

之后,可以在 http://localhost:8000 查看应用的实时运行情况。

为了保持组织性,将所有视图模板放在一个 views 目录中。这是一种常见做法。

##### 创建视图模板目录

在项目根目录下创建一个 views 目录以存储所有视图模板。

mkdir views

code

##### 创建 index 视图模板

touch views/index.ejs

code

提示:EJS 模板文件使用 .ejs 扩展名。

打开文件,将 server.js 中的 HTML 模板移动到该文件中。

views/index.ejs

<html> <body> <h1>好友列表</h1> <ul> <%# 遍历好友对象 %> <% for (const eachFriend in friends) { -%> <li><%= friends[eachFriend] %> 是我的 <%= eachFriend %></li> <% } -%> </ul> </body> </html>

code

##### 更新服务器以从 views 文件夹获取视图模板

import express from "express"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url";

const app = express(); const host = "localhost"; const port = 8000; const __dirname = dirname(fileURLToPath(import.meta.url));

// 指定应用的视图目录 app.set("views", join(__dirname, "views"));

// 指定应用的视图引擎 app.set("view engine", "ejs");

// 定义数据对象 const friendsData = { bestFriend: "Sarah", codingFriend: "Abraham", dreamFriend: "Mary", }; const replacementObject = { friends: friendsData };

// 编译并渲染 index 视图模板为 HTML,并将其作为 'GET /' 请求的响应 app.get("/", (req, res) => { res.render("index", replacementObject); });

// 在指定端口和主机名上运行服务器 app.listen(port, host, () => { console.log(服务器正在 http://${host}: ${port} 运行); });

code

上面的代码片段使用了 Express 的 render() 方法从 index.ejs 模板文件生成 HTML。

- 如果省略 app.set("view engine", "ejs") 这一行,那么 render() 方法的视图模板参数必须包含文件扩展名,如下所示:

app.get("/", (req, res) => { res.render("index.ejs", replacementObject); });

code

- Express 没有默认的模板引擎。Express 还支持诸如 Pug(以前称为 Jade)、EJS、Mustache 和 Handlebars 等引擎。

- app.set() 是向应用程序设置中添加键值对的方法。你可以通过 app.get() 获取设置的值,如下所示:

app.set("my name", "Oluwatobi");

const bio = app.get("my name");

console.log(bio); // 输出: "Oluwatobi"

code

#### 如何创建可重用的 EJS 模板(部分模板)

EJS 提供了 include() 方法,用于将一个视图模板包含(嵌套)到另一个模板中。

提示:在 EJS 中,部分模板是可重用的模板。

##### include() 方法的语法

include() 方法接受两个参数。以下是语法:

<%- include("path/to/partial", data) %>

code

- "path/to/partial" :(必需)相对于当前文件(父模板)的可重用模板路径。例如,如果当前文件位于 "./views/index.ejs",而部分模板位于 "./views/partials/footer.ejs",则 include() 的路径参数应为 "partials/footer"。

- data :(可选)一个包含要传递给部分模板的属性的对象。

##### 如何在视图模板中包含部分模板

考虑以下服务器文件:

import express from "express"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url";

const app = express(); const host = "localhost"; const port = 8080; const __dirname = dirname(fileURLToPath(import.meta.url));

app.set("views", join(__dirname, "views")); app.set("view engine", "ejs");

const friendsData = { bestFriend: "Sarah", codingFriend: "Abraham", dreamFriend: "Mary", }; const replacementObject = { friends: friendsData };

app.get("/", (req, res) => { res.render("index", replacementObject); });

app.listen(port, host, () => { console.log(Server running live at http://${host}: ${port}); });

code

上面的代码片段会在用户向“/”路径发送 GET 请求时渲染 index 模板。下面是 index.ejs 模板文件的内容。

<html> <body> <h1>List of Friends</h1> <ul> <%# Loop through the friends object %> <% for (const eachFriend in friends) { -%> <li><%= friends[eachFriend] %> is my <%= eachFriend %></li> <% } -%> </ul> </body> </html>

code

如果需要在多个模板中重复使用 <li> 元素,可以将其提取到单独文件中,并通过 include() 方法按需添加到任何模板中。以下是具体操作方式:

##### 创建部分模板目录

在 views 文件夹中创建一个 partials 目录,用于存储所有可复用的视图模板。

mkdir views/partials

code

##### 创建部分视图模板

为 <li> 元素创建一个部分视图模板。

touch views/partials/friendLi.ejs

code

打开文件并添加 <li> 元素。

views/partials/friendLi.ejs

<li><%= friend %> is my <%= friendType %></li>

code

##### 更新主视图模板

打开 index.ejs 文件,使用 include() 方法添加 friendLi.ejs 部分视图模板。

<html> <body> <h1>List of Friends</h1> <ul> <%# Loop through the friends object %> <% for (const eachFriend in friends) { -%> <%- include("partials/friendLi", { friend: friends[eachFriend], friendType: eachFriend }) %> <% } -%> </ul> </body> </html>

code

通过 include() 方法添加 <li> 元素,可以使其在多个模板中复用,而不仅仅局限于 index.ejs 视图模板。

##### 从根目录运行服务器

然后,访问 http://localhost:8080 查看应用程序的运行情况。

注意:虽然上述步骤演示了使用简单 <li> 元素的 EJS 部分模板,但你可以使用相同的概念在多个视图模板中复用应用程序的导航栏、页脚和其他组件。

## 概述

在本手册中,我们探讨了开始使用 Node.js 和 Express.js 构建应用程序所需的核心概念。我们讨论了 Node.js 的工作原理、模块支持以及如何创建 HTTP 服务器。我们还探讨了 Express.js 如何通过路由、路由器和视图简化服务器端开发。

无论你是考虑开发小型个人项目,还是为更大用户群构建全栈应用程序,你现在都已具备使用 Node.js 和 Express.js 开发的坚实起点。

感谢阅读!

### 更深入探索 Node.js 和 Express.js

本手册为你提供了我所著《Node.js 和 Express.js 简化指南》的精彩预览。

无论您是首次学习后端开发、希望将 JavaScript 技能拓展到前端开发之外,还是寻找关于 Node.js 和 Express.js 的实用入门指南,这本书都将帮助您巩固所学知识,并掌握构建、测试和部署完整应用程序所需的技能。

通过实践解释和动手示例,您将更深入地了解 Node.js 和 Express.js,内容涵盖文件管理、文件上传、表单处理、会话管理、前后端通信、测试、TypeScript 和部署等主题。

合作伙伴推荐课程

(在新标签页中打开)

广告

- Skillshare NodeJS 入门:后端 JavaScript Keegan Francis 3.7(满分5分)(32人评分)36分钟 初级 查看课程(在新标签页中打开)
- Coursera 后端开发 Packt - 课程讲师 3周,每周5小时 中级 证书 查看课程(在新标签页中打开)
- Coursera 使用 Node.js 和 Express 构建后端 API Edureka 4周,每周2小时 中级 证书 查看课程(在新标签页中打开)
- Udemy 学习并掌握 NodeJS(V8、Express、MERN/PERN 等)Anthony Alicea 4.7(满分5分)(31,354人评分)14小时38分钟 初级 证书 查看课程(在新标签页中打开)

更多 Node.Js 课程请访问 Class Central

→

阅读更多文章

如果您读到这里,请感谢作者以表达您的支持。说声谢谢

免费学习编程。freeCodeCamp 的开源课程已帮助超过 40,000 人成为开发者。立即开始