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 进行开发。
目录
你应该已经知道的
本指南面向具备基本 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 或更高版本。
通过运行以下命令检查你已安装的版本:
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 命令创建新项目目录,具体步骤如下:
mkdir codesweetly-nodejs-app-001
注意: 您可以使用您喜欢的任何名称。在本指南中,我们将使用 codesweetly-nodejs-app-001 进行演示。
随后,使用命令行导航到您的项目目录。
cd codesweetly-nodejs-app-001
创建package.json文件
进入项目目录后,用 npm 初始化package.json文件。
npm init -y
接下来,使用以下命令删除 package.json 中可选的 main 字段:
npm pkg delete main
提示: The main field in package.json identifies a package's entry point when another program loads the package. 在这个项目里我们不需要它,因为我们会直接运行脚本。保留它也没有问题。
为什么选择 Node.js?
JavaScript 最初是为网页浏览器而创建的,但其他环境也提供了执行它的基础设施。
Node 在 2009 年的发布提供了在网页浏览器外运行 JavaScript 的环境。例如,让我们创建一个可在系统终端运行的脚本。
1. 创建 JavaScript 文件
创建您想让 Node 运行的 JavaScript 文件。
在支持 touch 的 shell(如 Bash 或 zsh)中,使用以下命令。否则,在您的代码编辑器中创建该文件:
touch console.js
2. 编写你的 JavaScript 程序
打开新建的 JavaScript 文件并编写你的程序:
console.log("=== Hello from the CodeSweetly Team! ===");
console.log("We hope you have fun Coding Sweetly with Node.js.");
console.log("Thank you for being part of the CodeSweetly community.");
console.log("=== Keep coding. Keep creating. Keep shipping. ===");
3. 运行 JavaScript 程序
安装 Node.js 后,即可在命令行中使用 node 命令运行 Node 应用。
node console.js
node: 用于运行 Node.js 脚本或 REPL 的命令。console.js: 您想让 Node 运行的 JavaScript 文件。
您还可以将该命令添加到项目的 package.json 文件的 "scripts" 字段中:
{
"scripts": {
"start": "node console.js",
"test": "echo \"Error: no test specified\" && exit 1"
}
}
脚本就绪后,就可以在终端里这样运行你的 JavaScript 程序:
npm run start
运行脚本后,Node 会把文件的输出打印到终端,效果如下:
$ npm run start
> codesweetly-nodejs-app-001@1.0.0 start
> node console.js
=== Hello from the CodeSweetly Team! ===
We hope you have fun Coding Sweetly with Node.js.
Thank you for being part of the CodeSweetly community.
=== Keep coding. Keep creating. Keep shipping. ===
正如你所看到的,我们已经成功在浏览器外执行了 JavaScript。这正是 Node.js 能帮助我们的地方。它是一个异步、事件驱动的运行时环境,用于在浏览器外运行 JavaScript 代码。你也可以自动化重新运行程序。下面我们来讨论一下怎么做。
4. 自动重新运行你的 JavaScript 程序
默认情况下,Node.js 需要你在每次修改后手动重新运行 JavaScript 文件。
每次修改后重复手动执行应用的过程会很麻烦。幸运的是,Node 提供了 --watch 标志来自动化此过程:
node --watch filename.extension
上面的代码片段使用 --watch 标志以监视模式启动 Node.js。只要你更新了依赖图中的任何文件,Node 就会重新执行指定的脚本。
node: 运行 Node.js 脚本或 REPL 的命令。--watch: 激活 Node 监视模式的标志。filename.extension: 您希望 Node 执行的脚本。
提示: 在 Windows、macOS 或 Linux 上使用 Ctrl + C 停止正在运行的进程。
--watch 标志默认会监视入口点及其所有依赖的模块。换句话说,监视命令会让 Node 监控:
入口文件(
filename.extension)入口文件导入的所有文件
那些被导入文件进一步导入的所有文件
如此递归,直至整个依赖图结束
Node 的默认监视模式不会监视无关文件。如果您希望 Node 监视其他文件,请使用 --watch-path 标志:
node --watch-path=./src --watch-path=./tests filename.extension
如上片段所示,--watch-path 标志让 Node 监视 src 和 tests 目录下的所有文件,即使这些文件不在入口点依赖图中。
node: 用于运行 Node.js 脚本或 REPL 的命令。--watch-path: 用于指定 Node 应监视更改的路径。filename.extension: 您希望 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
import http from "node:http";
另一方面,Node 会将下面的 script.cjs JavaScript 文件视为 CommonJS 模块,因为它具有 .cjs 文件扩展名。
script.cjs
const http = require("node:http");
假设你想为项目 .js 文件指定模块类型。在这种情况下,在 package.json 文件中添加 type 字段,如下所示:
package.json
{
"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 中仍然得到支持。
在本指南中,我们主要会使用带有 ES 模块的 Node.js,因为 ES 模块是 JavaScript 的标准模块系统。要跟随示例操作,请确保你的 package.json 文件中的 "type" 字段设置为 "module"。你可以在项目目录下运行以下命令来实现:
npm pkg set type=module
在相关情况下,我们将比较 CommonJS 语法。
现在让我们讨论使用 Node.js 创建能够接收并响应浏览器请求的 Web 服务器。
使用 Node.js 创建 HTTP 服务器
HTTP 服务器让您的 JavaScript 应用能够处理客户端(如浏览器)请求并发送响应。
配置应用 HTTP 服务器主要有三个步骤:
创建 HTTP
Server对象的新实例。指定服务器运行的系统端口。
定义服务器应如何响应客户端请求。
下面我们详细讨论这三个步骤。首先,为您的项目创建一个 ES 模块。
touch server.js
之后,打开新建的模块,初始化一个 HTTP Server 对象的新实例。
初始化 HTTP Server 对象的新实例
Node 提供 createServer 方法来创建 HTTP Server 对象的实例(http.Server)。
在项目中,可以通过从 Node 的 http 模块导入来使用它,具体步骤如下:
server.js
import { createServer } from "node:http";
const server = createServer();
这里是发生的事情:
import语句:将createServerAPI 从 Node 的http库导入到server.jsES 模块。server:用于存储 HTTPServer对象的变量,该对象由createServer()函数输出。
这里是 CommonJS 替代方案:
server.cjs
const http = require("node:http");
const server = http.createServer();
创建本地 HTTP Server 对象后,指定服务器的端口。
配置服务器应运行的系统端口
HTTP Server 对象提供一个 listen() 方法,用于配置服务器应运行的端口并接受客户端请求。
语法
import { createServer } from "node:http";
const server = createServer();
server.listen(port, hostname, backlog, callback);
此形式的 listen() 方法接受以下参数:
port: (number) 服务器监听客户端请求的端口号。如果省略,操作系统会分配任意未使用的端口。可以使用server.address().port在服务器开始监听客户端连接后获取其监听的端口。hostname: (string) 服务器接受客户端连接的主机名或 IP 地址。如果省略,服务器将默认为未指定的 IPv6 (::) 地址或 IPv4 (0.0.0.0) 地址。这些未指定的地址将绑定到相应地址族的所有网络接口。可以使用server.address().address在服务器开始监听客户端连接后获取已绑定的 IP 地址。backlog: (number) 待处理连接队列的最大长度。默认值为 511。callback: (function) 服务器开始监听客户端请求时要执行的函数。
示例
server.js
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}/`);
});
这里是正在发生的情况:
import语句: 从 Node 的http库导入createServerAPI 到server.jsES 模块。server: 用于存储 HTTPServer对象的变量,该对象由createServer()函数输出。server.listen(): 启动服务器以监听客户端连接。 (Tip: 该方法在服务器成功启动时会发出一个listening事件。)
这里是 CommonJS 替代方案:
server.cjs
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。您可以这样使用它:
server.js
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() 方法接受两个可选参数。语法如下:
import { createServer } from "node:http";
const server = createServer(options, callback);
options:用于自定义服务器行为的对象。callback:用于处理和响应客户端请求的requestListener函数。它接受两个参数:
import { createServer } from "node:http";
const server = createServer(options, function (request, response) {
// the requestListener function's body
});
request参数:一个http.IncomingMessage对象,提供客户端请求的详细信息。response参数:一个http.ServerResponse对象,用于响应客户端请求。
将 requestListener 回调函数作为 createServer 的第二个参数提供,会导致 Node.js 自动将其注册为 "request" 事件的监听器。因此,上面的语法等价于:
import { createServer } from "node:http";
const server = createServer(options);
server.on("request", function (request, response) {
// the requestListener function's body
});
server.on("request", callback) 告诉服务器监听 request 事件并在发生该事件时执行回调。
示例
server.js
// Add the Node.js HTTP module
import { createServer } from "node:http";
// Specify the hostname and port to run the server
const hostname = "localhost";
const port = 3000;
// Create a new Server instance with a requestListener callback
const server = createServer((req, res) => {
res.statusCode = 200; // Set an OK success (200) response status code
res.setHeader("Content-Type", "text/plain"); // Define the media type of the response data
res.end("Hello World!"); // Specify the response data and close the response stream
});
// Run the server on the specified port and hostname
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:
${port}/`);
});
上面的代码片段使用了 requestListener 回调函数的 response 参数来配置服务器以响应客户端请求。这里给出使用 server.on("request", callback) 的替代写法:
server.js
// Add the Node.js HTTP module
import { createServer } from "node:http";
// Specify the hostname and port to run the server
const hostname = "localhost";
const port = 3000;
// Create a new Server instance
const server = createServer();
// Listen for a request event and execute the requestListener callback
server.on("request", (req, res) => {
res.statusCode = 200; // Set an OK success (200) response status code
res.setHeader("Content-Type", "text/plain"); // Define the media type of the response data
res.end("Hello World!"); // Specify the response data and close the response stream
});
// Run the server on the specified port and hostname
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:
${port}/`);
});
服务器搭建完成、端口也配置好后,就可以运行应用了。
node server.js
node: 运行 Node.js 脚本或 REPL 的命令。server.js: 您希望 Node 运行的 JavaScript 文件。
服务器运行时,用户在服务器地址请求应用资源时,会看到如下图所示的 Hello World! 响应。
提示: 在 Windows、macOS 或 Linux 上使用 Ctrl + C 停止正在运行的进程。
虽然 node:http 模块是 Node.js 处理 HTTP 请求的原生 API,但开发者通常会使用框架来简化流程。一些流行的 Node.js Web 框架包括 Express.js、Fastify 和 Koa.js。下面以 Express.js 为例,看看框架如何简化 Node 中的 HTTP 请求处理。
Express.js 到底是什么?
Express.js 是一个 Web 框架,能够简化 Node.js 中的服务器端 HTTP 请求处理。它通过内置的路由和中间件等功能扩展了 Node 的 HTTP 模块,帮助开发者高效地构建 Web 应用和 API,而无需编写重复代码。下面介绍如何在 Node.js 项目中使用它。
1. 安装 Express
npm install express@5.2.1
2. 使用 Express.js 配置项目的 Node.js Web 服务器
打开 JavaScript 服务器文件,使用 Express.js 搭建一个 Web 服务器:
server.js
// Add the Express module
import express from "express";
// Create a new Express application instance
const app = express();
// Set the port number where the server will run and listen for requests
const port = 3000;
// Create a route handler for GET requests to the "/" path
app.get("/", (req, res) => res.send("Hello, world!"));
// Run the server on the specified port
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 请求的脚本。
node server.js
在浏览器中打开 http://localhost:3000 可以看到 Hello, world!。
提示: 在 Windows 上使用 Ctrl + C 停止服务器,在 macOS 上使用 Control + C。
原始 Node.js 代码 vs. 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 响应。
server.js
import { createServer } from "node:http";
const port = 3000;
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 原生 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 响应。
server.js
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 简化了以下任务:
Routing logic: Express.js 提供简单的路由声明,如
app.get()、app.post()和app.delete(),无需为每条路由编写复杂的条件判断来检查req.url和req.method。Response header configuration: Express 的
res.send()方法会根据响应体自动设置合适的响应头。当需要设置状态码时,使用res.status(),就像上面的 404 处理程序所示。JSON formatting: Express 的
res.json()方法会自动将 JavaScript 对象转换为有效的 JSON 响应,为你处理序列化和头部。
重要须知:在 Node.js 中创建 Express.js Web 服务器
在你的 Node.js 项目中创建 Express.js 服务器时,请牢记以下几个关键点。
常见做法是将 app.listen() 放在最后
在 Express.js 应用中,通常会把 app.listen() 放在最后。这样,所有路由、中间件和错误处理程序都会在服务器开始监听请求之前完成设置。
可以使用环境变量设置端口号
要从环境变量读取端口号,请用以下内容替换现有的 port 声明:
const port = process.env.PORT || 3000;
上面的代码片段指示 Node 使用环境变量 PORT 的值,如果未设置或为空则默认使用 3000。在 Bash 等 shell 中,下面的命令将此次服务器运行的 PORT 设置为 8000:
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 请求-响应循环,但它们不会结束路由处理函数的执行。
示例:
app.get("/", (req, res) => {
// This sends a text response to the client and ends the request-response cycle, but does not end the route handler's execution
res.send("Hello, client!");
// This logs the string to the console because the function is still running
console.log("Hello, devs!");
// This will cause an error because the request-response cycle is closed, so you cannot send additional responses to the client during the callback's execution
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 中的路由语法
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()方法在特定PATH上设置处理函数,以在所有支持的 HTTP 请求方法上运行。app.use()方法允许您设置中间件函数,当请求到达时,这些函数可以在所有 HTTP 请求方法和路径上运行。如果指定了路径参数,app.use()将匹配该路径及其子路径。例如,/book与/book、/book/dashboard和/book/dashboard/author/202605匹配。
中间件 vs. 路由处理程序
开发者常使用术语“路由处理程序”来描述负责解析请求并发送响应的最终函数,而之前的可重用函数则被称为“中间件”。
app.METHOD(PATH, MIDDLEWARE1, MIDDLEWARE2, HANDLER);
注意: Express 不会强制为路由附加的函数指定不同的名称或类别。从 Express 的角度来看,它们都是处理函数,通过 next() 在它们之间传递控制权。然而,开发者经常区分中间件和路由处理函数,以提升代码组织性、可读性和沟通效率。
中间件函数通常存放在
/middlewares目录中,它们是实用组件,用于执行诸如身份验证、验证、日志记录和数据加载之类的横切或准备任务。这些函数通常可复用,并且通常会调用next()将控制权传递给下一个函数。路由处理函数有时会被组织为
/controllers目录下的控制器,它们包含生成端点主要响应的业务逻辑,例如getUserProfile、createBlogPost或deleteAccount。这些函数可能特定于某个路由,通常通过调用res.send()或res.json()等方法来结束请求-响应周期。
Express.js 中的路由示例
以下是 Express.js 中的一些路由示例。
具有单个处理函数的 Express 路由
app.get("/single/handler", (req, res) => {
res.send("Request handled with a single handler function!");
});
上面的示例使用单个回调函数来处理对 GET /single/handler 端点的 GET 请求。在这种情况下,处理函数是一个内联控制器。一种常见的做法是将控制器放在 controllers 目录中。这样既能让代码更易于测试和维护,又能保持路由定义的可读性。
这是一个示例:
下面的示例展示了一个带有模块化控制器的 GET /single/handler 路由,该控制器已被移至其自身的模块(controllers/single-handler.js)。这种做法将路由定义与生成端点响应的请求处理逻辑分离开来。
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
function singleHandler(req, res) {
res.send("Request handled with a single handler function!");
}
export { singleHandler };
提示: 控制器是处理特定端点请求的路由处理程序或处理程序组。控制器通常放在独立模块中,以提升代码组织性并保持关注点清晰分离。
Express 具有多个中间件函数的路由
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 路由
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);
上面示例使用回调函数数组处理对 GET /array/middleware 端点的 GET 请求。
Express 路由:中间件函数数组与单独回调的组合
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) 上。
示例:
import express from "express";
const app = express();
app.get("/profile", (req, res, next) => {
console.log("Hi, there!");
next();
});
路由级中间件
此中间件挂载在 Express Router 实例(router)上。其工作方式类似于应用级中间件,但挂载在路由上而不是应用上。
示例:
import express from "express";
const router = express.Router();
router.get("/profile", (req, res, next) => {
console.log("Hi, there!");
next();
});
错误处理中间件
此中间件用于处理请求处理过程中的错误,通过四参数签名进行标识。
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 一起内置,默认可用,无需额外安装其他包。
示例:
import express from "express";
express.static("public");
express.json();
express.urlencoded();
第三方中间件
这些中间件函数由更广泛的 Express 社区开发和共享的外部软件包提供。例如 cors、morgan 和 helmet。
import express from "express";
import cors from "cors";
const app = express();
app.use(cors());
注意:Express 中的中间件类别不互斥
示例1:
import express from "express";
const app = express();
app.use(express.json());
上面片段中的中间件express.json()既是:
应用级,因为它附加到
app实例上内置,因为 Express 提供它
示例 2:
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() 方法将相关的路由和中间件分组。这能保持条理清晰,并使每个组更易于管理和复用。
虽然您可以在一个文件中创建所有路由器和应用实例,但开发者通常会把路由器放到单独文件中,以获得更好组织性和可维护性。让我们按照这种方法,为每个路由和中间件类别创建单独文件。
创建路由器目录
在项目根目录下创建一个 routes 目录,用于存储所有应用的路由器。
mkdir routes
注意: 你可以给这个文件夹起任何你喜欢的名字,但 routes 是一个常见名称,因为它保存了定义你应用路由(URL 端点)的文件。express.Router() 方法只是 Express 提供的一个用于管理这些路由的工具。
创建路由文件
路由文件是一个模块,你在其中定义相关的路由并导出一个 Express 路由器对象。每个文件通常代表一个资源或功能,例如用户、图书或身份验证。
在 routes 目录中创建路由文件。
touch routes/index.js routes/books.js routes/videos.js
创建用于书籍相关路由和中间件的 Mini-App
打开 books.js 文件并使用 express.Router() 实例将所有书籍相关的路由和中间件分组。
routes/books.js
// Import the Router function from Express
import { Router } from "express";
// Create a new Express router instance
const bookRouter = Router();
// Define middleware specific to this router instance
bookRouter.use((req, res, next) => {
console.log("Book route accessed at: ", Date.now());
next();
});
// Define routes specific to this router instance:
bookRouter.get("/", (req, res) => {
res.send("List of All Books
");
});
bookRouter.get("/:id", (req, res) => {
res.type("text/plain").send(`Details for book ${req.params.id}`);
});
bookRouter.get("/:id/draft", (req, res) => {
// Escape the ID before inserting it into HTML text
const bookId = req.params.id
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">");
res.send(`New Draft for Book ${bookId}
`);
});
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 前进行转义,以便以文本形式显示。使用
export语句导出 router 对象,以便在应用需要的地方导入。
练习使用路由器
这是你练习所学 Express 路由器的时刻。
为此练习,为项目的 index 和 video 相关路由及其中间件创建小型应用。将它们分别从 routes/index.js 导出为 indexRouter,从 routes/videos.js 导出为 videoRouter,以匹配下一节的导入。
在继续之前,先自行尝试一下。如果需要帮助,请查看 books.js 文件。练习有助于你更好地理解。
完成练习后,继续设置主 Express 应用。
更新主 Express 应用
打开项目的 server.js 文件,将路由器挂载到主 Express 应用上。
server.js
// Add the Express module
import express from "express";
// Import the routers from their separate files
import { indexRouter } from "./routes/index.js";
import { bookRouter } from "./routes/books.js";
import { videoRouter } from "./routes/videos.js";
// Create a new Express application instance
const app = express();
// Specify the host and port to receive requests
const host = "localhost";
const port = 3000;
// Mount the routers to specific paths:
// Mount indexRouter at /
app.use("/", indexRouter);
// Mount bookRouter at /books and its subpaths
app.use("/books", bookRouter);
// Mount videoRouter at /videos and its subpaths
app.use("/videos", videoRouter);
// Run the server on the specified port and host
app.listen(port, host, () => {
console.log(`Server running live at http://${host}:
${port}!`);
});
上面的代码实现了以下功能:
创建一个 Express 应用实例,以访问 Express API,如路由和中间件,从而简化 Node.js 中的 HTTP 请求处理。
使用 Express 的
app.use()方法将微型应用添加到主 Express 应用中。使用 Express 的
app.listen()方法在特定端口启动 Node.js HTTP 服务器,并在服务器开始监听客户端请求时执行回调。
注意: app.use() 方法的路径充当路由器的基础路径或挂载路径。在路由器中定义的路由相对于此挂载路径进行匹配。例如:
bookRouter的/路由匹配到/books的请求。bookRouter的/:id路由匹配到/books/:id的请求。bookRouter的/:id/draft路由匹配到/books/:id/draft的请求。
运行你的 Express.js 应用程序
设置好路由后,通过使用 Node.js 启动服务器文件来运行你的应用。
node --watch server.js
然后,在指定端点检查您的应用程序是否正在运行。例如, http://localhost:3000/books。
提示: 使用 Ctrl + C 在 Windows、macOS 或 Linux 上停止运行的进程。
Express 路由器的重要事项
使用 Express 路由器时,需要牢记以下几点。
默认情况下,路由器不会继承父路由的路由参数
默认情况下,Express 不会将路由器挂载路径上的参数传递给该路由器内部的路由。
示例:
// Add the Express module
import express from "express";
// Create a new Express application instance
const app = express();
// Create a new Express router instance
const bookRouter = express.Router();
// Define a sub-route
bookRouter.get("/books/:bookId", (req, res) => {
const topic = req.params.topic; // undefined by default
const bookId = req.params.bookId;
res.json({ topic, bookId });
});
// Define a parent route
app.use("/codesweetly/:topic", bookRouter);
// Run the server on the specified port and host
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:
http://127.0.0.1:3000/codesweetly/CSS/books/91827
由于子路由默认无法访问父路由的路径参数,req.params.topic 参数将是 undefined,因此服务器只会向客户端返回 {"bookId":"91827"}。
要让子路由能够访问父路由的路径参数,在创建路由器时将 mergeParams 设置为 true。
示例:
// Add the Express module
import express from "express";
// Create a new Express application instance
const app = express();
// Create a new Express router instance
const bookRouter = express.Router({ mergeParams: true });
// Define a sub-route
bookRouter.get("/books/:bookId", (req, res) => {
const topic = req.params.topic; // "CSS" for the URL below
const bookId = req.params.bookId;
res.json({ topic, bookId });
});
// Define a parent route
app.use("/codesweetly/:topic", bookRouter);
// Run the server on the specified port and host
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:
http://127.0.0.1:3000/codesweetly/CSS/books/91827
req.params.topic 参数的值将是 CSS,因为 mergeParams: true 使子路由能够访问父路由的路径参数。因此,服务器会向客户端发送 {"topic":"CSS","bookId":"91827"} 响应。
提示:
这行代码
const { address, port } = server.address()使用 对象解构 从server.address()对象中获取address和port的值。app.listen()方法返回一个原生的 Node.jshttp.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 路由器挂载到另一个路由器上。
示例:
// Add the Express module
import express from "express";
// Create a new Express application instance
const app = express();
// Create two Express router instances
const htmlRouter = express.Router();
const bookRouter = express.Router();
// Define a sub-route
bookRouter.get("/:bookId", (req, res) => {
res
.type("text/plain")
.send(`Details for the HTML book with ID ${req.params.bookId}`);
});
// Mount bookRouter on htmlRouter's /books path
htmlRouter.use("/books", bookRouter);
// Mount htmlRouter at /html and its subpaths
app.use("/html", htmlRouter);
// Run the server on the specified port and host
const server = app.listen(3000, "127.0.0.1", () => {
const { address, port } = server.address();
console.log(`Server running live at http://${address}:
${port}!`);
});
bookRouter 的 GET /:bookId 路由会匹配到 GET /html/books/:bookId 的请求,因为在本例中,我们把 book 路由器挂载到了 htmlRouter 上。
因此,如果你在上面的示例中运行 Web 服务器并在浏览器中输入以下 URL:
http://127.0.0.1:3000/html/books/91827
服务器将向客户端发送一条 “ID 91827 的 HTML 书籍详情” 响应。
现在你已经了解什么是路由器,让我们讨论如何使用视图来定义 Express.js 应用向用户返回的网页内容和结构。
Express.js 中的视图是什么?
视图是一个模板或文档,用于定义 Express.js 应用返回给用户的网页内容和结构。它可以是静态 HTML 文件,也可以是由模板引擎在 运行时 渲染为 HTML 的动态模板。
在本指南中,我们将使用两种类型的视图:
静态视图
动态视图
Express.js 中的静态视图是什么?
创建静态视图
在项目根目录中创建 index.html、about.html、contact.html 和 404.html 文件。
touch index.html about.html contact.html 404.html
打开每个视图并添加以下 HTML 内容:
index.html(主页)
Static Views in Express.js | CodeSweetly Tutorial
Welcome to the Static View Guide
Site's pages:
about.html(关于页面)
About Us | Static Views in Express.js
About Us
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?
contact.html(联系页面)
Contact | Static Views in Express.js
Contact Us
404.html(错误页面)
404 - Page not found | Static Views in Express.js
Page not found
Sorry, we couldn't find that page
接下来,配置项目以处理浏览器连接。
配置项目的 Web 服务器以提供静态视图
打开项目的 server.js 模块,并配置 Web 服务器以响应请求提供适当的静态 HTML 视图。
// Add the required modules
import express from "express";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
// Create a new Express application instance
const app = express();
// Specify the hostname and port to receive requests
const host = "localhost";
const port = 3000;
// Get the directory name of the current file's path
const __dirname = dirname(fileURLToPath(import.meta.url));
// Use the index.html static view as a response to 'GET /' requests
app.get("/", (req, res) => res.sendFile(join(__dirname, "index.html")));
// Use the about.html static view as a response to 'GET /about' requests
app.get("/about", (req, res) => {
res.sendFile(join(__dirname, "about.html"));
});
// Use the contact.html static view as a response to 'GET /contact' requests
app.get("/contact", (req, res) => {
res.sendFile(join(__dirname, "contact.html"));
});
// Use the 404.html static view as a response to unmatched requests
app.use((req, res) => {
res.status(404).sendFile(join(__dirname, "404.html"));
});
// Run the server on the specified port and hostname
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 运行它。
node --watch server.js
之后,在 http://localhost:3000 查看您的应用程序正在运行。
提示:在 Windows、macOS 或 Linux 上使用 Ctrl + C 停止正在运行的进程。
为了保持井井有条,请将所有静态视图放入 public 目录。这是一种常见做法。
创建存放静态视图的目录
在项目根目录下创建一个 public 目录,用于存放所有静态视图。
mkdir public
注意: 你可以给这个文件夹起任何名字,但 public 是一个常见的选择,用来说明其中的文件(HTML、CSS、客户端 JavaScript、图片)旨在提供给客户端。仅凭文件夹名称本身并不会让其中的内容可访问。必须配置服务器来提供这些文件。不要在其中放置敏感信息。
将所有静态视图移入 public 文件夹
mv index.html about.html contact.html 404.html public/
更新服务器以从 public 文件夹获取静态视图
server.js
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));
// Serve static assets (HTML, CSS, images) from the 'public' folder
app.use(express.static(join(__dirname, "public")));
// Use the index.html static view as a response to 'GET /' requests
app.get("/", (req, res) => {
res.sendFile(join(__dirname, "/public/index.html"));
});
// Use the about.html static view as a response to 'GET /about' requests
app.get("/about", (req, res) => {
res.sendFile(join(__dirname, "/public/about.html"));
});
// Use the contact.html static view as a response to 'GET /contact' requests
app.get("/contact", (req, res) => {
res.sendFile(join(__dirname, "/public/contact.html"));
});
// Use the 404.html static view as a response to unmatched requests
app.use((req, res) => {
res.status(404).sendFile(join(__dirname, "/public/404.html"));
});
// Run the server on the specified port and hostname:
app.listen(port, host, () => {
console.log(`Server running live at http://${host}:
${port}`);
});
注意 该 app.use(express.static(join(__dirname, 'public'))) 行指示 Express 在处理静态资源请求时,首先检查 public 文件夹。如果找到对应文件,Express 会立即将其发送到浏览器并终止请求‑响应循环。
例如:
如果用户请求
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路由处理该请求。
现在
// Add the Express and EJS modules
import express from "express";
import ejs from "ejs";
// Create a new Express application instance
const app = express();
// Specify the host and port to receive requests
const host = "localhost";
const port = 8000;
// Define the data object
const friendsArray = ["Sarah", "Abraham", "Mary"];
const replacementObject = { friends: friendsArray };
// Use backticks to define a template string with embedded JavaScript
const templateString = `
Hello, <%= friends.join(", "); %>!
Welcome to CodeSweetly.
`;
// Compile and render the template string to HTML (the rendered view)
const html = ejs.render(templateString, replacementObject);
// Use the dynamic view as a response to 'GET /' requests
app.get("/", (req, res) => res.send(html));
// Run the server on the specified port and hostname
app.listen(port, host, () => {
console.log(`Server running live at http://${host}:
${port}!`);
});
上面的代码片段告诉 Express 在用户请求 / 路径时把渲染好的视图发送给客户端。
运行时,EJS 使用传入的 friends 数组计算 friends.join(", "),并把结果 Sarah, Abraham, Mary 插入到 HTML 中。这里,渲染在脚本启动时只发生一次。每个请求都会收到同样的渲染后的 HTML。
EJS 模板标签是什么?
EJS 模板标签是使用 <% ... %> 语法编写的特殊标签。它们允许将 JavaScript 代码和值嵌入到 EJS 视图模板中。
EJS 渲染模板时,脚本标签会执行 JavaScript,输出标签会插入值,注释标签会被忽略。
例如,下面的 <%= %> 标签允许将 JavaScript 变量 friend 嵌入到视图模板中。
About <%= friend %>, my special pal
注意: 输出标签如 <%= %> 必须包含有效的 JavaScript 表达式。脚本标签可以包含 JavaScript 语句以及控制流结构的部分,而注释标签则包含注释。
示例:
<%=会抛出错误,因为I love CodeSweetly
%>是 HTML,而不是 JavaScript。I love CodeSweetly
<%= "是有效的,因为I love CodeSweetly
" %>"被嵌入为 JavaScript 字符串数据类型。I love CodeSweetly
"
EJS 模板标签的类型
此处介绍的九种 EJS 标签形式如下:
提示: 将 JavaScript 代码包裹在 EJS 标签内,以便与周围的模板内容区分。一对标签可以包含多行 JavaScript。
闭合标签 (%>)
使用闭合标签来结束一个 EJS 标签。它本身没有特殊行为。
脚本标签 (<%)
使用脚本标签可以运行 JavaScript 代码而不向生成的 HTML 输出任何内容。此标签常用于控制流,例如 if、for 和 forEach 的逻辑。
<% friends.forEach(function () { %>
- My friend
<% }) %>
上面的代码片段将 <% %> 包裹在控制流语法周围,以告诉 EJS 该 JavaScript 代码仅用于逻辑,不应渲染到生成的 HTML 中。
转义输出标签 (<%=)
使用转义输出标签来求值 JavaScript 表达式并将结果输出到生成的 HTML 中,同时转义 HTML 字符。此标签有助于防止 HTML 注入。
<%= 100 + 200 %>
上面的代码片段使用 <%= %> 包裹算术表达式,以告诉 EJS 将代码的值渲染到生成的 HTML 中。
注意: 如果表达式求值为包含 HTML 的字符串,<%= %> 会转义 HTML 语法,使浏览器将其显示为文本。例如,<%= " 会将 About CodeSweetly
" %> 渲染为文本,而不是实际的 About CodeSweetly
元素。
未转义的输出标签 (<%-)
使用未转义标签来求值 JavaScript 表达式,并将结果输出到生成的 HTML 中,而不对 HTML 字符进行转义。
<%- "About CodeSweetly
" %>
上面的代码片段把 <%- %> 包裹在含有 元素的字符串里,使浏览器把它渲染为 HTML。仅对可信的 HTML 使用未转义的输出。
字面开头标签 (<%%)
使用字面标签输出字面的 <% 序列,而不是把它当作 EJS 标签处理。在生成的输出中展示 EJS 语法时很有用。
<%% "Name: Oluwatobi
" %>
上面的代码片段会输出一个字面量的 <%,然后跟随剩余的文本,而不会把它当作 JavaScript 来求值。生成的 HTML 源码如下:
<% "
Name: Oluwatobi
" %>
HTML 标签未被转义,因此这显示的是生成的源码,而不是浏览器显示的页面。
换行修剪的结束标签 (-%>)
使用换行修剪的结束标签来删除紧随闭合标签之后的换行符。
<% friends.forEach(function (friend) { %>
- <%= friend -%>
<% }) %>
上面的代码片段在 friend 变量两侧使用 <%= -%>,告诉 EJS 去掉闭合 EJS 标签后面的换行。
提示:换行修剪的结束标签也叫换行吞噬标签。
空白吞噬开始标签 (<%_)
使用空白吞噬标签可删除同一行上开放 EJS 标签之前的空格和制表符。
<%_ friends.forEach(function (friend) { %>
- <%= friend %>
<%_ }) %>
上面的代码片段将 <%_ %> 包裹在控制流语法周围,以告诉 EJS 在模板标签之前修剪缩进。
空格吞噬结束标签 (_%>)
使用空格吞噬结束标签可以删除结束标签后紧随的空格和制表符,随后如果有的话会保留一个换行。
<% friends.forEach(function (friend) { _%>
- <%= friend %>
<% }) _%>
上面的代码片段在控制流语法周围包裹 <% _%>,以告诉 EJS 在每个闭合标签后修剪换行符。接下来一行的缩进保持不变。
注释标签 (<%#)
使用注释标签可以添加在渲染过程中被忽略且不会出现在生成的 HTML 中的 EJS 注释。
<%# Loop through the friends array %>
<% friends.forEach(function (friend) { _%>
- <%= friend %>
<% }) _%>
上面的代码片段将注释包裹在 <%# %> 中。
提示: EJS 允许您组合一些标签。例如,以下组合是可能的:
<%_ _%>: 将空格吞噬脚本标签与结束标签组合,可删除同一行上的相邻空格和制表符,并在存在时删除后面的一个换行符。<% -%>: 将控制流标签与换行符吞噬组合。<%= -%>: 将转义标签与换行符吞噬组合。
如何在 Express 项目中使用 EJS
以下章节将指导您在 Express.js 项目中使用 EJS 的过程。
安装 EJS
首先,在您的 Express 项目中安装 EJS。
npm install ejs@6.0.1
接下来,打开项目的 server.js 模块,配置 Web 服务器以响应请求提供适当的动态生成 HTML 视图。
server.js
// Add the Express and EJS modules
import express from "express";
import ejs from "ejs";
// Create a new Express application instance
const app = express();
// Specify the host and port to receive requests
const host = "localhost";
const port = 8000;
// Define the data object
const friendsData = {
bestFriend: "Sarah",
codingFriend: "Abraham",
dreamFriend: "Mary",
};
const replacementObject = { friends: friendsData };
// Use backticks to define a template string with embedded JavaScript
const templateString = `
List of Friends
<%# Loop through the friends object %>
<% for (const eachFriend in friends) { -%>
- <%= friends[eachFriend] %> is my <%= eachFriend %>
<% } -%>
`;
// Compile and render the template string to HTML (the rendered view)
const htmlData = ejs.render(templateString, replacementObject);
// Use the dynamic view as a response to 'GET /' requests
app.get("/", (req, res) => res.send(htmlData));
// Run the server on the specified port and hostname
app.listen(port, host, () => {
console.log(`Server running live at http://${host}:
${port}!`);
});
以下是上面代码片段的主要作用:
创建 Express 应用实例以访问 Express API,如路由和中间件,它们简化了 Node.js 中的 HTTP 请求处理。
在脚本启动时只渲染一次模板字符串,以生成服务器发送给每个客户端的 HTML。
创建路由以接收并响应客户端请求。
使用 Express 的 app.listen() 方法在特定端口启动 Node.js HTTP 服务器,并在服务器开始监听客户端请求时执行回调。
运行 Express 应用
设置服务器后,使用 Node.js 运行它。
node --watch server.js
之后,在 http://localhost:8000查看您的应用正在运行。
提示: 使用 Ctrl + C 在 Windows、macOS 或 Linux 上停止正在运行的进程。
为了保持井井有条,将所有视图模板放在 views 目录中。这是一种常见做法。
创建视图模板目录
在项目根目录创建一个 views 目录,用于存放所有视图模板。
mkdir views
创建索引视图模板
touch views/index.ejs
提示:EJS 模板文件使用 .ejs 扩展名。
打开文件并将 HTML 模板从 server.js 移动到其中。
views/index.ejs
List of Friends
<%# Loop through the friends object %>
<% for (const eachFriend in friends) { -%>
- <%= friends[eachFriend] %> is my <%= eachFriend %>
<% } -%>
更新服务器以从 views 文件夹检索视图模板
server.js
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));
// Specify the app's views directory
app.set("views", join(__dirname, "views"));
// Specify the app's view engine
app.set("view engine", "ejs");
// Define the data object
const friendsData = {
bestFriend: "Sarah",
codingFriend: "Abraham",
dreamFriend: "Mary",
};
const replacementObject = { friends: friendsData };
// Compile and render the index view template to HTML and use it as a response to 'GET /' requests
app.get("/", (req, res) => {
res.render("index", replacementObject);
});
// Run the server on the specified port and hostname
app.listen(port, host, () => {
console.log(`Server running live at http://${host}:
${port}`);
});
上面的代码片段使用 Express 的 render() 方法,从 index.ejs 模板文件生成 HTML。
注意:
- 如果省略
app.set("view engine", "ejs")这一行,则render()方法的视图模板参数必须包含文件扩展名,例如:
app.get("/", (req, res) => {
res.render("index.ejs", replacementObject);
});
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); // Outputs: "Oluwatobi"
如何创建可复用的 EJS 模板(部分)
EJS 提供了 include() 方法,用于在一个视图模板中包含(嵌套)另一个视图模板。
提示: 在 EJS 中,部分是可复用的模板。
include() 方法的语法
include() 方法接受两个参数。语法如下:
<%- include("path/to/partial", data) %>
"path/to/partial": (必需)可复用模板的路径,相对于当前文件(父模板)。例如,如果当前文件位于"./views/index.ejs",而部分模板位于"./views/partials/footer.ejs",则include()的路径参数应为"partials/footer"。data: (可选)包含要传递给部分模板属性的对象。
如何在视图模板中包含部分
请看下面的服务器文件:
server.js
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}`);
});
上面的代码片段在用户向 "/" 路径发送 GET 请求时会渲染一个 index 模板。下面是 index.ejs 模板文件。
views/index.ejs
List of Friends
<%# Loop through the friends object %>
<% for (const eachFriend in friends) { -%>
- <%= friends[eachFriend] %> is my <%= eachFriend %>
<% } -%>
如果你需要在多个模板中重复使用 元素,可以将其提取到一个单独的文件中,然后使用 include() 方法根据需要将其添加到任何模板。具体操作如下:
创建 partials 目录
在你的 views 文件夹中创建一个 partials 目录,用于存放所有可复用的视图模板。
mkdir views/partials
创建部分视图模板
为 元素创建部分视图模板。
touch views/partials/friendLi.ejs
打开文件并添加 元素。
views/partials/friendLi.ejs
<%= friend %> is my <%= friendType %>
更新主视图模板
打开 index.ejs 文件并使用 include() 方法添加 friendLi.ejs 部分视图模板。
views/index.ejs
List of Friends
<%# Loop through the friends object %>
<% for (const eachFriend in friends) { -%>
<%- include("partials/friendLi", { friend: friends[eachFriend], friendType: eachFriend }) %>
<% } -%>
使用 include() 添加
从根目录运行服务器
node --watch server.js
然后,在 http://localhost:8080 检查您的应用程序是否正在运行。
注意: 虽然上述步骤展示了使用简单的 元素的 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 和部署等主题。
