初学者指南:如何使用 Express 作为 Node.js 框架构建服务器
在我之前的博客文章《Node.js 服务器创建入门指南》中,我们深入了解了 Node.js 的内部机制。
现在我们已经了解了 Node.js 的工作原理,终于可以使用 Express 来构建服务器了。Express 即将让你的工作变得轻松许多,敬请期待!
什么是Express?
Express 是一个 Node.js Web 应用程序框架,它为 Web 和移动应用程序提供了一套强大的功能(定义来自express.js)。
换句话说,以前我们用 Node.js 编写的大量代码,用于从 HTTP 请求中提取数据、处理和解析数据以及其他繁琐的工作,现在都可以由 Express 来处理了。
这个框架提供了一系列工具、实用函数和应用构建规则。它允许我们在项目中安装第三方软件包,以帮助我们完成繁琐的任务。
正因如此,我们现在可以编写更简洁的代码,专注于业务逻辑,使我们的应用程序更加出色。
想用更少的代码完成更多的工作吗?让我们开始吧!
读完这篇博客,你将能够:
- 理解 Express 的核心概念,例如中间件、路由和服务文件。
- 使用 Express 构建一个简单的服务器,该服务器能够响应 GET 和 POST 请求。
前提安装
如果您尚未安装Node.js,请在此处下载。保存并运行安装程序。
先修阅读材料
我将频繁引用我之前的博客,以突出 Express 和 Vanilla Node.js 之间的区别。
我强烈建议你事先阅读一下,这将有助于你更深入地理解Express的概念。
GitHub 仓库
这是我们将要搭建的服务器的GitHub代码库。如果您在搭建服务器的过程中遇到任何错误,请随时参考此代码库。
代码仓库中有一个名为 views 的文件夹。其中包含服务器将发送到浏览器的 html 文件。
后续步骤中,您将被指示将这些文件的内容复制并粘贴到服务器上的文件中。
不过,稍后再谈!现在先在另一个标签页里打开看看吧。
我们在制作什么?
我特别喜欢搞笑的狗狗名字。我们将创建一个非常简单的服务器,用户可以用它来提交搞笑的狗狗名字。
首页会显示欢迎信息。导航栏中有一个名为“分享有趣的狗狗名字”的链接。
点击此链接将跳转到趣味名字页面,您需要在该页面通过表单提交名字。此操作会向服务器发送一个 POST 请求。
当服务器收到用户输入的 POST 请求时,它会将用户重定向到主页,并将用户输入的内容打印到服务器的控制台中。
在开始编写代码之前,我们需要安装和设置很多东西。请按照步骤 1-9 完成设置。
设置
步骤 1:为我们的服务器创建一个目录
在相应的目录中,在终端中输入以下命令。
#in terminal
mkdir Indiana_Bones
进入 Indiana_Bones 目录,并在文本编辑器中打开它。
#in terminal
cd Indiana_Bones
code .
步骤 2:
在 Indiana_Bones 目录下创建 server.js 文件。在终端中执行以下命令。
#in terminal
touch server.js
你会看到目录中已创建了 server.js 文件。
步骤 3:在 server.js 所在的层级创建一个名为“util”的文件夹。
在 util 文件夹内,创建一个名为 path.js 的文件。
步骤 4:在 server.js 所在的层级创建一个名为“routes”的文件夹。
在 routes 文件夹内,创建两个 JavaScript 文件:
- funny-name.js
- home.js
步骤 5:在 server.js 所在的层级创建一个名为“views”的文件夹。
在 views 文件夹内,创建两个 html 文件:
- 有趣的名字.html
- 首页.html
这两个文件包含用于在浏览器中显示信息的 HTML 代码。当我们的服务器收到来自浏览器的 HTTP 请求时,我们会将这些文件作为响应发送出去。
由于本博客将只关注如何使用 Express 创建服务器,因此我们不会详细介绍 html 代码。
我已经将 html 代码放在 GitHub 仓库中,您可以将代码复制粘贴到我们服务器上的指定文件中。
进入此仓库并点击 views 文件夹。在里面,你会看到两个 html 文件:funny-name.html 和 home.html。
您的服务器上应该有完全相同的文件夹和文件结构。请将内容复制粘贴到服务器上相应的文件中。
步骤 6:创建 package.json 文件
如果你是初学者,你很可能已经多次使用过 npm(Node 包管理器),但并没有真正理解它是什么。
npm 是一个用于发布开源 Node.js 项目的在线存储库;其次,它是一个用于与该存储库交互的命令行实用程序,有助于包安装、版本管理和依赖项管理(摘自node.js)。
我们将从 npm 安装第三方软件包,让他们为我们完成所有繁重的工作。
首先,我们需要创建一个 package.json 文件。该文件主要记录了项目依赖的所有软件包和应用程序、其独特的源代码控制信息,以及项目名称、描述和作者等特定元数据(摘自nodesource.com)。
在终端中输入:
#in terminal
npm init
接下来,系统会向您展示一系列关于您应用程序详细信息的问题,每回答完一个问题,按下回车键即可。
您可以自行选择填写这些信息,但为了本教程的目的,我们可以跳过这部分。多次按回车键,直到终端退出问卷调查。
此时,您应该可以看到已经创建了 package.json 文件。我们稍后会详细介绍这个文件的作用!
步骤 7:安装 nodemon
在我之前的博客中,每次我们想要查看对代码进行更改的结果时,都必须手动重启服务器。
我们可以通过安装名为 nodemon 的第三方软件包来避免这项繁琐的任务。每次您更改代码时,它都会自动重启服务器!
在终端中运行以下命令安装nodemon。
#in terminal
npm i nodemon --save--dev
注意我在末尾添加了 `--dev` 参数吗?我们是在指定仅在开发阶段使用此工具。这样做是因为当我们的应用部署后,运行应用的服务器上无需安装 nodemon。
在文件资源管理器中,您现在会看到已为您创建了 package-lock.json 文件(蓝色框)。查看 package.json 文件,您会看到 nodemon 已被添加为依赖项(红色箭头)。
请查看绿色方框高亮显示的脚本部分。您会看到“start”脚本设置为“node server.js”。请将其更改为:
#in package.json
"start": "nodemon server.js"
这一步骤实现了两个目的:
- 它允许 nodemon 在您更改服务器端代码时自动重启服务器。
- 我们不再需要使用命令“node server.js”来启动服务器。现在可以使用命令“npm start”来启动服务器。
步骤 8:安装 Express
#in terminal
npm i --save express
Express 是已部署应用程序不可或缺的一部分,必须安装在运行该应用程序的任何服务器上。通过在 `--save` 后省略 `--dev`,我们将 Express 作为生产依赖项进行安装。
现在您可以看到 express 已作为依赖项添加到 package.json 中。
步骤 9:安装 body-parser。body
-parser 是一个第三方软件包,用于解析传入的请求体。它本质上是将传入请求流的整个请求体部分提取出来,并将其暴露在 req.body 上。
这将使我们以后不必编写大量的代码,但我们以后会对此进行更深入的研究。
目前,您可以通过在终端中运行以下命令来安装它。
#in terminal
npm i body-parser
使用 Express 创建服务器和中间件函数
步骤 1:创建一个 Express 应用。
在 server.js 文件中,复制并粘贴以下代码。
#in server.js
const express = require('express');
const app = express();
const homeRoutes = require('./routes/home');
const nameRoutes = require('./routes/funny-name');
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: false}));
app.use(homeRoutes);
app.use(nameRoutes);
app.listen(3000);
server.js 启动服务器并监听指定端口上的请求。请求在此处接收,并依次经过中间件函数,直到找到能够响应该请求的中间件。
我们一行一行地来分析这段代码。为了更清晰地解释代码,我可能会稍微跳过一些部分,所以请务必注意行号。
第 1 行
为了使用 Express 创建服务器,我们需要导入 Express 以获得它所附带的所有炫酷功能。
第 2 行
这些功能中,express() 用于创建 Express 应用。将 express() 设置为一个常量 app。
第 13 行
app.listen(3000) 创建一个监听端口 3000 的服务器。
第 7-8 行:
我们的服务器将通过传入请求的主体接收数据。在处理数据之前,我们需要先解析数据。
在安装过程中,我们安装了 bodyParser。它是一个解析传入请求并处理数据的函数,它通过监听 req.on('data') 事件,并从接收到的数据块构建 req.body。有关此概念的更多解释,请查看我的博客(链接在此)。
bodyParser 会根据数据类型以不同的方式解析数据,因此需要我们指定数据类型。
数据类型可能有所不同,具体如下:
- application/x-www-form-urlencoded
- multipart/form-data
- application/json
- application/xml
- 其他的
在第 7 行,我们将 bodyParser 导入到 server.js 中。
在第 8 行,我们指定 bodyParser 将解析通过表单发送的数据正文。
在我之前的博客中,我们使用原生Node.js搭建了一个服务器。为了解析传入的请求数据,我们不得不编写下面这样一大段代码。
#route.js from a previous blog r
if (url === "/mood" && method === "POST") {
const body = [];
req.on("data", (chunk) => {
body.push(chunk);
});
return req.on("end", () => {
const parsedBody = Buffer.concat(body).toString();
console.log(parsedBody)
const mood = parsedBody.split("=")[1];
fs.writeFile("user_mood.txt", mood);
return res.end();
});
}
我们仅用两行代码(第 7-8 行)就用 Express 实现了同样的功能!
第 10-11 行
是我们的中间件函数。中间件函数通过调用 app.use() 或 app.http 方法(例如 app.get())加载。当服务器收到请求时,请求会从顶部(app.use(homeRoutes))到底部(app.use(nameRoutes))依次经过中间件函数。
expressjs.com上的这张图表出色地展示了中间件函数调用的各个组成部分。
中间件功能的具体细节实际上都写在了路由文件中,这样可以更好地隔离代码,保持 server.js 的简洁美观。我们稍后会深入探讨这张图!
第 4-5 行
为了访问路由文件(home.js 和 funny-name.js)中定义的中间件函数,我们将这些文件导入到 server.js 中。它们分别被设置为等于常量 homeRoutes 和 nameRoutes。
第 10-11 行
然后我们将这些文件中定义的中间件函数传递给 app.use(homeRoutes) 和 app.use(nameRoutes)。
如果你对中间件的概念感到困惑,别担心。我们将在下一步中详细讲解。我只是想先向你介绍一下这个概念,以便我们在本文结尾能够更好地理解。
第二步:理解中间件
中间件是使用 Express 构建应用时最重要的概念之一。让我们深入了解一下!
okta网站上的这段摘录是我迄今为止见过的最佳解释之一。我将在下面的段落中分享这个定义。
Express 中间件是在向 Express 服务器发出请求的生命周期内执行的函数。每个中间件都可以访问其所关联的每个路由(或路径)的 HTTP 请求和响应。事实上,Express 本身完全由中间件函数构成。此外,中间件可以终止 HTTP 请求,也可以使用 `next` 将其传递给另一个中间件函数。这种中间件的“链式”调用方式允许您对代码进行模块化,并创建可重用的中间件。
中间件函数可以访问请求对象 (req)、响应对象 (res) 和下一个函数 (next)。它以触发该函数的请求的 HTTP 方法和 URL 路径 (route) 为前缀。
此函数可以执行任何代码,更改请求和响应对象,结束请求-响应周期,或调用堆栈中的下一个中间件(摘自express.js)。
当服务器收到请求时,请求会从上到下依次经过各个中间件函数。它会一直向下传递,直到找到专门处理该请求的中间件。当请求找到正确的中间件时,该中间件会向浏览器发送相应的响应。
我制作了一张图表,以帮助您更好地理解这个概念。
我一直在思考如何让这篇教程更具学术性,然后想到了这个比喻,或许能帮助你更好地理解这个概念。
想象一下,你是一位老式电子游戏的英雄。你的任务是找到隐藏在小屋和城堡中的宝藏。游戏开始时,你会得到一把魔法钥匙(请求)。你的旅程始于一条单行道,你将穿过神秘的小屋和城堡(中间件),这些地方可能藏有你苦苦寻觅的宝藏(响应)。你必须尝试用钥匙(请求)打开这些神秘地点的大门。只有当你找到可以用钥匙打开的那扇门时,你才能找到宝藏,完成你的任务(发送到浏览器的响应)。
但这游戏并不像看起来那么简单。有些小屋和城堡上标有秘密徽章(next())。带有这些徽章的地点会将你传送到下一个地点(next middleware),而没有这些徽章的地点则会将你永远困在里面。
好了好了……我不打算再钻牛角尖了。咱们来写一些中间件函数,看看它到底是怎么回事吧!
步骤 4:为“趣味命名”页面设置路由器级别的中间件函数。
正如最终产品演示所示,我们的应用将包含两个页面:一个显示欢迎信息的主页和一个显示表单的“趣味命名”页面。用户可以使用此表单提交他们给狗狗起的趣味名字,并向服务器发送 POST 请求。
服务器收到 POST 请求后,会将用户重定向到主页,并将用户输入的内容打印到服务器的控制台中。
我们先从搞笑名字页面开始。
此页面可以发送两种类型的请求。
1. GET 请求:当用户访问 localhost:3000/funny-name 时,浏览器会向服务器发送 GET 请求。我们的服务器会将 funny-name.html 文件发送给浏览器。该文件包含 HTML 代码,用于在页面上显示导航栏、表单和表情包。
2. POST 请求:当用户通过表单提交一个有趣的狗狗名字时,页面会向服务器发送一个 POST 请求。用户输入的内容会打印在我们服务器的控制台(红色框)中。
如前所述,中间件函数在 server.js 中加载。但是,我们并没有在 server.js 中编写中间件函数,而是将其编写在不同的模块(routes>home.js 和 routes>funny-name.js)中,以实现代码的模块化。
在您的 routes 文件夹中,打开 funny-name.js 文件。复制并粘贴以下代码。
#in routes>funny-name.js
const express = require("express");
const router = express.Router();
const path = require("path");
const rootDir = require("../util/path");
router.get("/funny-name", (req, res, next) => {
res.sendFile(path.join(rootDir, "views", "funny-name.html"));
});
router.post("/funny-name", (req, res, next) => {
console.log(req.body);
res.redirect("/");
});
module.exports = router;
在您的 util 文件夹中,打开 path.js 文件。复制并粘贴以下代码。
#in util>path.js
const path = require('path')
module.exports = path.dirname(process.mainModule.filename)
让我们逐行分析!首先来看 funny-name.js 文件。
第 1 行
我们将 Express 导入到 funny-name.js 文件中。
第 3 行
导入 Express 使我们能够访问 express.Router()。这允许我们创建路由器级别的中间件来响应某些 HTTP 请求。
路由级中间件的工作方式与应用程序级中间件相同,只是它绑定到 express.Router() 的一个实例(摘自 Express.js)。
设置 express.Router() 等于常量 router。
第 9-11 行
这些代码设置了一个路由器级别的中间件,用于响应来自 funny-name 页面(localhost:3000/funny-name)的 get 请求。
当收到来自页面的 GET 请求时,它会向浏览器发送一个名为 funny-name.html 的文件(第 10 行)。让我们仔细看看这段代码。
#in routes>funny-name.js
router.get("/funny-name", (req, res, next) => {
res.sendFile(path.join(rootDir, "views", "funny-name.html"));
});
res.sendFile() 是一个实用函数,允许我们将文件发送到浏览器。在括号内,我们必须指定要发送的文件的路径(例如 funny-name.html)。
你的直觉可能会告诉你,应该像这样在项目中包含文件路径。
#in routes>funny-name.js
router.get("/", (req, res, next) => {
res.sendFile('./views/funny-name.html');
});
但是,如果您尝试执行它,将会遇到错误“路径必须是绝对路径或指定 res.sendFile 的根目录。”
`res.sendFile` 需要指定该文件在操作系统中的绝对路径。我们可以使用名为 `path` 的核心模块以及 `path.js` 文件中定义的辅助函数轻松获取绝对路径。
我们需要将这两个文件导入到 funny-name.js 文件中。第 5 行和第 7 行代码负责处理这个问题。
第 5 行
我们导入一个名为 path 的核心模块。
第 7 行
我们从 util 文件夹导入 path.js 文件。
这两点将在第 10 行发挥作用,我们在该行中将 funny-name.html 文件发送到浏览器以响应 get 请求。
让我们把注意力转移到 util 文件夹中的 path.js 文件。
第 1 行
我们将 path core 模块导入到此文件中,以便访问其 .dirname() 方法。
第 3 行的
`path.dirname(process.mainModule.filename)` 返回运行我们应用程序的文件的绝对路径。在本例中,它是 `server.js`。我们导出这个辅助函数,以便可以从 `funny-name.js` 中访问它。
第 7 行
我们将辅助函数导入 funny-name.js。
第 10 行
,我们使用 path.join 方法将“views”和“funny-name.html”连接到 server.js 的绝对路径。这样我们就可以构建 funny-name.html 的文件路径,并使服务器能够将正确的文件发送到浏览器。
第 13-16 行
我们设置了一个路由器级别的中间件,以便当我们收到 POST 请求时,用户输入会打印到我们的服务器控制台中,并将用户重定向到主页。
这个中间件与我们已经编写的 GET 请求非常相似。
你会看到,在路由之后,我们指定了要发送的 HTTP 请求。在我们的函数内部,我们将用户输入记录到控制台,这些输入存储在请求体(req.body)中。
然后,我们使用 res.redirect 并指定我们希望用户重定向到的页面的 URL。
第 18 行
,我们导出路由器,因为我们将把所有路由器级别的中间件导入到 server.js 中的中间件函数中。
步骤 5:设置首页的路由级中间件函数。
此路由级中间件函数将响应来自 localhost:3000/ 的 GET 请求。收到请求后,它会将 home.html 文件作为响应发送到浏览器。该文件包含将在首页显示以下信息的 HTML 代码。
在您的 routes 文件夹中,打开 home.js 文件。复制并粘贴以下代码。
#in routes>home.js
const express = require("express");
const router = express.Router();
const path = require("path");
const rootDir = require("../util/path");
router.get("/", (req, res, next) => {
res.sendFile(path.join(rootDir, "views", "home.html"));
});
module.exports = router;
home.js 文件看起来几乎与 funny-name.js 文件完全相同。
唯一的区别在于 home.js 只有一个路由级别的中间件函数。当它收到 GET 请求时,会将 home.html 文件作为响应发送!
真相大白的时刻
现在我们已经编写好了代码,让我们来测试一下。在终端中运行以下命令启动服务器。
#in terminal
npm start
打开浏览器,在地址栏输入 localhost:3000。
你应该会看到类似下面的主页。
在首页导航栏中,点击“分享搞笑狗狗名字”选项。点击后应该会跳转到 localhost:3000/funny-name。
在“搞笑名字”页面上,使用表单提交你的搞笑狗狗名字。返回服务器并查看控制台。你将在那里看到你输入的内容!
如果遇到任何错误,请尝试完全复制粘贴我代码库中的代码。很可能是一些肉眼难以察觉的拼写错误或空格错误。
恭喜你们读完了这篇博客!这可不是件容易的事,你们做到了。现在去创作一些有趣的东西吧!


















