每个 Web 开发人员如何通过 Node.js 成为全栈开发人员
我相信你听说过 Node.js,但可能你还没有深入研究过,或者只是对它的概念和用途有一个大概的了解。我想解释一下什么是 Node,以及为什么应该使用它,特别是如果你从事 Web 开发,并且想要扩展你的工具库或工作机会。我们还将探讨为什么应该使用一些基于 Node 构建的库和框架来让我们的工作更轻松,代码更简洁。
通过本指南,我们将了解它是什么Node
以及Express
它是如何工作的,构建一个 REST API 来存储和检索数据,测试端点并上传我们的应用程序。
在本系列结束时,您将对 MERN 堆栈(MongoDB、Express、React 和 Node)和测试技能有一个完整的概述。
路线图
我还想给你一个本系列的路线图,其思路是从 Node 和 Express 的基础知识开始,我们将学习如何在服务器中存储和检索数据,但目前仅使用文件系统。在未来的指南中,我们将学习如何将其转换为真正的数据库数据检索/存储,甚至如何部署到云端。
在本系列中,我们还将创建一个 React 应用程序,它将使用我们现在创建的后端。如果您正在使用或刚刚开始使用 Next.js,您可能已经注意到 Next.js 自带了一个 Node 组件,即api.js
。我认为在 Next.js 中第一次接触到扁平 Node 之前,先尝试一下它很重要,但我们将会看到,我们今天构建的代码有多少在使用 Next.js 构建的项目中被复用了。
TypeScript
在示例项目中,我将使用 TypeScript 而不是纯 JavaScript,您可以放心遵循它,因为语法非常相似,但如果您想知道为什么要费心处理 TS 而不是 JS,我建议您阅读我的上一篇文章。
我上一篇博文是关于前端的 TypeScript,但那里解释的所有内容都适用于这里。如果说 TypeScript 在前端有用,那么在后端就更有用了,因为后端开发通常比前端开发包含更多逻辑,也更关键,但这种说法需要谨慎对待。
资源
项目示例
在本指南中,我们将开发一个简单的 REST API,用于存储和检索服务器上 JSON 文件中的数据。此 REST API 旨在构建一个招聘信息发布应用程序,用户可以在其中输入公司信息以及不同的招聘信息。
什么是 Node.js?
众所周知,我们分为前端和后端,直到 Node.js 发布之前,如果我们想到 JavaScript,它直接针对前端开发。
使用 Node.js,我们可以在服务器端甚至直接在计算机上运行 JavaScript。好吧,从技术上讲,服务器就是一台计算机,但你明白我的意思。但是 JavaScript 只能在浏览器内部运行,那么它怎么能直接在计算机上运行呢?Node.js 主要用 C++ 构建,Node 内部有 Google 的 V8 引擎,该引擎将 JavaScript 直接转换为本机机器码。
所以基本上你编写普通的 JavaScript,Node 将其传递给 V8,V8 生成机器代码,计算机能够读取该代码。
但是 Node 不仅仅是 JS 和 V8 之间的桥梁,通过不同的模块,Node 允许我们(举一些例子)与计算机的文件系统进行通信或设置一个响应请求并从数据库提供内容的服务器。
这很好,但是,我是一名 Web 开发人员,不打算为 Windows 或任何其他操作系统编写应用程序,如何将 Node.js 放在服务器上,并用 Lombok 注释替换我花哨的 Java Spring Boot + Hibernate 动态化?
你会从你的 React 或任何前端向服务器发送请求,服务器上会运行一个 Node.js 程序,它会监听请求并向客户端返回响应。响应可以是一个文件,因为我们可以访问文件系统,例如完整的 HTML、图像或任何其他二进制数据。
它还可以与数据库通信,检索一些数据,进行一些计算,并返回一个漂亮的 JSON 以供我们在前端使用。
为什么要使用 Node.js?
- 一切都是 JavaScript → 即使从个人角度或公司角度来看,这仍然是事实,只需一种语言就能开发出完整的应用程序,两者兼顾。对你来说,将你现有的技能运用到其他领域的语言中固然很有趣,但对公司来说,这也是一个好处,他们可以复用员工现有的专业知识。
- 全部都是 JavaScript x2 → 因为前端和后端都是 JavaScript,所以代码复用的可能性很大。你之前有验证身份证的功能吗?前端和后端使用完全一样的代码。
- 社区→有很多实用程序、包甚至框架建立在 Node.js 之上,您将获得大量支持,并且有大量可立即使用的工具可用。
- 它使用率很高 → 看看这张来自JS 2020 现状的截图,基于 Node.js 构建的 Express 状况很糟糕。不过,“每个人都在用它”这种说法确实应该谨慎对待。
设置
在系统上安装 Node.js 最简单的方法是访问官方网站,尤其是https://nodejs.org/en/download/current/,那里列出了所有平台和选项。您可以选择长期支持版本或最新版本,选择您想要的版本。就本指南而言,两种版本都适用,我个人使用的是当前版本 16.5.0。
对于 Windows 和 Mac 来说,安装没有什么神秘之处,所以如果您像我一样是 Linux 用户,您会发现这个资源更有用。
例如对于 Ubuntu 用户:
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt-get install -y nodejs
安装 Node.js 还会安装 npm(代表 Node 包管理器),如果您从事 Web 开发,那么您会非常习惯使用它。
要检查一切是否正常,请在终端中运行以下命令
node --version
npm --version
如果您在终端中输入node
,您将能够以与在浏览器内的开发者工具中相同的方式执行 JavaScript 代码。如果您想退出,请输入.exit
或使用Ctrl+C
。
打开您最喜欢的 IDE 并创建一个server.js
文件(名称完全由您决定),在这个 JS 文件中,您可以编写普通的 JavaScript 并通过node server
在终端上输入来运行它。
恭喜,您现在可以在浏览器外运行 JavaScript 代码了!
前端和后端运行 JS 的区别
正如我们已经看到的,Node.js 允许我们在项目后端执行 JavaScript,但由于 JavaScript 是在浏览器外部执行的,因此存在一些细微的差别。
全局对象
在前端,我们的全局对象是window
对象,如果你检查该对象,你会发现许多实用程序和变量,例如 fancy window.document.getElementById
。在 Node.js 中,window
对象 被 对象 取代global
。
使用server.js
之前创建的文件来 makeconsole.log(global)
并检查其中的内容。你会发现一些熟悉的函数,例如setTimeout
或setInterval
。
console.log(global);
/* <ref *1> Object [global] {
global: [Circular *1],
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
queueMicrotask: [Function: queueMicrotask],
performance: [Getter/Setter],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
}
} */
如果仔细观察,您会错过一些事情,例如 Node 没有document
对象或任何其他与 DOM 操作相关的对象。
就像在前端一样,您不需要global
在每次需要访问此对象内部的内容时进行键入,您可以setTimeout
直接使用而不必转到global.setTimeout
。
目录名和文件名
有两个新实用程序可供global
您经常使用:
__dirname
将告诉您当前脚本正在运行的目录的路径。__filename
返回当前运行脚本的名称和绝对路径。
console.log(__dirname); // /workspace/my-new-project
console.log(__filename); // /workspace/my-new-project/server.js
拆分代码
如果您想将代码拆分成不同的文件,您可能会习惯使用import
ES6 export
JavaScript,在 Node 中这也是可能的,但是您在互联网上找到的很多代码都带有commonJS
模块,所以我认为了解这一点也很重要。
要将当前模块中的成员导出到其他模块,您可以使用以下选项:
// module1.js
const name = "dastasoft";
const ultimate = "instant sleep";
module.exports = { name, ultimate };
// module2.js
const animes = ["Death Note", "Jujutsu Kaisen"];
module.exports = animes;
// module3.js
module.exports.today = () => new Date().getDay();
区别不仅在于要导出的参数数量,还在于如何使用这些值:
// module4.js
const { name, ultimate } = require("/module1");
const animes = require("./module2");
const aFunction = require("/module3");
console.log(name); // dastasoft
console.log(ultimate); // instant sleep
console.log(animes); // ["Death Note", "Jujutsu Kaisen"]
console.log(aFunction.today()); // 5
如你所见,我们使用require
关键字来包含其他模块,而不是使用 import 。这module
只是一个简单的 JavaScript 变量,它包含在所有 Node 模块中。
如果您尝试使用 ES6 模块,则很可能会遇到以下错误:
(node:22784) Warning: To load an ES module, set "type": "module" in
the package.json or use the .mjs extension.(node:22784)
Warning: To load an ES module, set "type": "module" in the package.json
or use the .mjs extension.
有不同的方法可以解决这个问题:
- 使用
.mjs
您想要使用和作为模块使用的文件的扩展名。 - 在您的中设置
type
为。module
package.json
- 使用 TypeScript 并将
tsconfig.json
模块设置为commonjs
这样您编写的 TS 将使用 commonjs 转换为 JS,并且 Node 会对此感到满意。
内置模块
除了 Node 之外,还有一些实用程序模块无需任何额外安装即可使用,让我们看一些示例:
操作系统
操作系统模块提供了有关其运行系统的大量信息:
const os = require("os");
console.log(os.arch()); // x64
console.log(os.version()); // #86-Ubuntu SMP Thu Jun 17 02:35:03 UTC 2021
console.log(os.platform()); // linux
FS
文件系统模块是 Node 的游戏规则改变者之一,您可以访问文件系统并执行许多操作。
让我们创建一个filesystem.js
文件系统模块来做一些测试:
// filesystem.js
const fs = require("fs");
fs.readFile("./assets/test.txt", (error, data) => {
if (error) console.log(error);
else console.log(data.toString());
});
如果你这样做,node filesystem
你将收到以下错误消息Error: ENOENT: no such file or directory, open './assets/test.txt'
。
创建一个名为的文件夹assets
和一个test.txt
包含一些内容的文件,然后重试。
让我们添加一个writeFile
函数:
// filesystem.js
const fs = require("fs");
fs.readFile("./assets/test.txt", (error, data) => {
if (error) console.log(error);
else console.log(data.toString());
});
fs.writeFile("./assets/test.txt", "I'm soooo fast", () => {
console.log("Done sir");
});
如果你尝试这段代码,你会发现在你读取文件之前,它就已经写入了新文本,并且在readFile
执行操作时会打印出新内容。这是因为这两个方法是异步的,不会阻塞代码的执行,代码会继续逐行执行,writeFile
直到先终止。
这是 Node.js 的关键点之一,也是许多大公司选择 Node 的原因:它的异步特性和非阻塞 I/O。有了它,您的服务器可以接收大量请求而不会阻塞应用程序。Node 有一个libuv
多线程库,名为 ,它可以处理 Node 单线程无法处理的所有异步进程并返回响应。
请尝试以下代码:
console.log(fs.readFileSync("./assets/test.txt").toString()); // I'm soooo fast
fs.writeFileSync("./assets/test.txt", "I'm actually faster");
现在您正在使用同步方法,并且代码包含在这些语句中。
FS 允许执行更多操作,但您有基本的想法,使用此模块,我们可以例如读取文件,进行一些计算,修改它并将其内容返回到前端。
http/http
通过这些模块,我们可以将我们的 Node 配置为 HTTP/HTTPS 服务器,这将是我们用来创建 REST API 的模块。
// server.js
const http = require("http");
const HOSTNAME = "localhost";
const PORT = 3000;
const server = http.createServer((req, res) => {
console.log(req);
console.log(res);
});
server.listen(PORT, HOSTNAME, () => {
console.log(`Server started on http://${HOSTNAME}:${PORT}`);
});
如果您使用node server
并打开浏览器,localhost:3000
您将在服务器控制台中看到console.log
包含两个有用参数的内容:请求对象和响应对象。这些对象包含一些有用的信息,我们稍后会详细介绍,但现在您可以先看看打印的内容。
- 我们使用内置
http
模块。 - 服务器将从中
hostname
响应我们的localhost
。 - 按照惯例,端口
3000
用于本地开发,但如果有可用端口,您可以使用任何您喜欢的端口。 - 我们使用该
createServer
函数。 - 我们用 启动服务器
listen
。
如您所见,它console.log
没有打印到浏览器控制台,而只打印到服务器控制台,这是因为我们在这里运行服务器代码,在下一节中,我们将看到如何将数据发送到前端,这将是我们的 REST API 的核心。
创建服务器
// server.js
const http = require("http");
const HOSTNAME = "localhost";
const PORT = 3000;
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.write("Hello from the Server!");
res.end();
});
server.listen(PORT, HOSTNAME, () => {
console.log(`Server started on http://${HOSTNAME}:${PORT}`);
});
现在尝试localhost:3000
在浏览器中访问并检查结果。
我们设置服务器以纯文本方式响应(使用响应对象)传入的请求,指示200
状态代码并终止通信。
如果您仔细观察上一节中的示例,就会发现一旦访问,localhost:3000
浏览器就永远不会解析请求,那是因为我们没有使用通知end
来通知通信的结束。
状态代码
如果您不知道什么是状态代码,请参阅此列表,简而言之,状态代码用于通知通信是否成功或发生了什么样的问题。
内容类型
此标头用于告知客户端返回的内容类型。如果您想查看不同类型的信息,请参阅此列表。
有用的外部包
我们已经看到了一些有用的内置模块,但是社区已经开发了大量值得一提的优秀软件包,当您查看互联网时您会发现很多。
如果您还没有,您可以npm
在项目文件夹中初始化您的项目:
npm init -y
这将生成一个简单的文件package.json
,它将在下一节中很有用,并且是安装外部包所必需的。
nodemon
如果您尝试在服务器运行时修改上述代码,您可能会注意到这些更改需要重新启动 Node 进程。nodemon 外部包会监视我们文件的更改并自动应用,而无需重启。
请参阅官方 nodemon 页面,但简而言之
npm install -D nodemon
安装为开发依赖项并按start
如下方式配置您的脚本:
"start": "nodemon server.js"
并执行它:
npm start
您的服务器将自动对变化做出反应。
表达
我们将在下一节详细介绍这个包。现在我们假设 Express 是一个 Node 的 Web 框架,它简化了 Web 应用程序的开发流程,旨在构建高效、快速的 Web 应用程序。Express 也是 MEAN/MERN/MEVN 堆栈的 E。
您无需使用 Express 甚至其他软件包即可实现该结果,但让我们看看这个特定软件包的优势。
要将 Express 添加到您的项目:
npm install express
摩根
Morgan 是 Express 的一个外部包,这个包允许我们以一种简单易行的方式记录事件,这对于我们第一步检查服务器中发生的事情非常方便。
在下一节中,我们将看到如何使用它,现在让我们将它添加到我们的项目中:
npm install -D morgan
一个提示,当使用外部包时,即使您已经在教程中看到过它,也要确保它确实解决了一个问题,例如,几乎所有此类指南中都存在但现在确实有自己的解决方案body-parser
的包。Express
表达
正如我们在上一节中看到的,我们将在项目中使用 Express,但我认为在向项目添加新包时最重要的是知道为什么以及它实际上解决了什么问题。
我们将构建一个简单的 REST API 作为示例。您无需安装 Express,只需使用 Node 即可实现此行为。
首先让我们创建一个database
文件夹并companies.json
在其中创建一个文件,该文件将充当简单的数据库。
// companies.json
[
{
"id": "0",
"name": "Capsule Corp",
"about": "Like WinRAR but we accept more file extensions.",
"industries": ["automobile", "house", "engineering"],
"numberEmployees": 2,
"yearFounded": 1990
},
{
"id": "1",
"name": "Red Ribbon",
"about": "We deliver the best Android you can ever had",
"industries": ["militar", "artificial intelligence", "engineering"],
"numberEmployees": 2000,
"yearFounded": 1000
}
]
// server.js
const fs = require("fs");
const http = require("http");
const HOSTNAME = "localhost";
const PORT = 3000;
const DB_PATH = `${__dirname}/database/companies.json`;
const getCompanies = res => {
fs.readFile(DB_PATH, (error, data) => {
if (error) {
console.error(error);
res.statusCode = 500;
res.end();
} else {
res.setHeader("Content-Type", "application/json");
res.statusCode = 200;
res.end(data);
}
});
};
const deleteCompany = (res, id) => {
fs.readFile(DB_PATH, (error, data) => {
if (error) {
console.error(error);
res.statusCode = 500;
res.end();
} else {
const companies = JSON.parse(data);
const filteredData = JSON.stringify(
companies.filter(company => company.id !== id),
null,
2
);
fs.writeFileSync(DB_PATH, filteredData);
res.setHeader("Content-Type", "application/json");
res.statusCode = 200;
res.end(filteredData);
}
});
};
const server = http.createServer((req, res) => {
const baseURL = "http://" + req.headers.host + "/";
const url = new URL(req.url, baseURL);
if (url.pathname === "/companies" && req.method === "GET") {
getCompanies(res);
} else if (url.pathname === "/companies" && req.method === "DELETE") {
deleteCompany(res, url.searchParams.get("id"));
} else {
res.statusCode = 404;
res.end();
}
});
server.listen(PORT, HOSTNAME, () => {
console.log(`Server started on http://${HOSTNAME}:${PORT}`);
});
从之前开始,createServer
我们设置一个服务器来监听请求,并根据所使用的 URL 和方法执行一个或另一个逻辑。
在两种不同的方法中,我们读取 JSON 文件并返回内容,在其中deleteCompany
查找特定内容Company
并过滤数组并写入文件,同时返回结果数组。
如果您想尝试前面的示例,我建议您使用Postman,这是我们稍后将详细介绍的应用程序,您可以使用它使用不同的方法对特定端点执行不同的请求。
如您所见,上面的 REST API 是不完整的,我们只有get
、delete
和not found
端点,但足以看到使用 Express 的一些优势,因此让我们将其与同一应用程序的 Express 版本进行比较。
创建一个新文件app.js
:
// app.js
const express = require("express");
const fs = require("fs");
const HOSTNAME = "localhost";
const PORT = 3000;
const DB_PATH = `${__dirname}/database/companies.json`;
const app = express();
const getCompanies = (req, res) => {
fs.readFile(DB_PATH, (error, data) => {
if (error) {
console.error(error);
res.status(500).end();
} else {
res.status(200).send(JSON.parse(data));
}
});
};
const deleteCompany = (req, res) => {
const { id } = req.params;
fs.readFile(DB_PATH, (error, data) => {
if (error) {
console.error(error);
res.status(500).end();
} else {
const companies = JSON.parse(data);
const filteredData = JSON.stringify(
companies.filter(company => company.id !== id),
null,
2
);
fs.writeFileSync(DB_PATH, filteredData);
res.status(200).send(JSON.parse(filteredData));
}
});
};
app.get("/companies", getCompanies);
app.delete("/companies/:id", deleteCompany);
app.use((req, res) => {
res.status(404).send("Not found");
});
app.listen(PORT, HOSTNAME, () => {
console.log("Server running");
});
让我们检查一下两个版本之间的差异。
服务器监听
服务器不需要指定 的默认值localhost
。
您还可以使用扩展版本:
app.listen(PORT, HOSTNAME, () => {
console.log("Server running");
});
路线
如你所见,路由部分更加简洁、清晰、可读性更强。每条路由都声明了一个与方法同名的函数,例如,列出所有公司的端点是一个get
方法,删除特定公司的端点也是一个delete
方法。
所有路由都接受一个接收请求和响应对象的函数:
app.get("/companies", (req, res) => {
// Do something
});
考虑到这一点,我们可以将逻辑隔离在函数内并直接传递该函数:
// app.get("/companies", (req, res) => getCompanies(req, res));
app.get("/companies", getCompanies);
对于删除端点,我们需要知道id
公司的,因为在这种情况下我们可以使用标识符,这些标识符将在哪里:
传播。req.params.identifierName
identifierName
id
最后,如果有人尝试访问我们未定义的路由,我们会定义 404 Not Found 错误。该app.use
方法是一个特殊方法,我们将在下一节中介绍。
回复
在 Node 版本中,我们发回并结束通信,end
方法仍然可用,但 Express 允许我们以更简单的方式来执行:
res.send(data);
send
Content-Type
将自动为我们设置。
状态代码
使用 Express 设置状态代码也更容易,大多数状态代码将由 Express 自动处理,但如果您需要明确定义某些内容:
res.status(200).send(data);
中间件
还记得app.use
我们保存的内容吗?现在是时候了。尝试将这app.use
几行粘贴到文件开头,并将它们放在其他路由之前,看看发出请求时会发生什么。
// app.js
app.use((req, res) => {
res.status(404).send("Not found");
});
app.get("/companies", getCompanies);
app.delete("/companies/:id", deleteCompany);
app.listen(PORT, HOSTNAME, () => {
console.log("Server running");
});
如您所见,现在每个请求都响应了 because Not found
,因为use
它捕获了所有请求并执行了相应的操作。现在删除它,并在文件顶部尝试以下语句:
// app.js
app.use((req, res, next) => {
console.log("I'm watching you");
next();
});
app.get("/companies", getCompanies);
app.delete("/companies/:id", deleteCompany);
app.use((req, res) => {
res.status(404).send("Not found");
});
app.listen(PORT, HOSTNAME, () => {
console.log("Server running");
});
现在每个请求都会I'm watching you
先打印,但都能正确执行。要理解为什么会发生这种情况,首先需要了解中间件。
中间件函数可以访问请求和响应对象,并在请求和响应之间的每次执行中执行。如果你仔细思考一下它的定义,就会发现整个 Express 都是由中间件函数组成的,而不仅仅是app.use
……
app.get
与或等其他函数的区别app.delete
在于,这些函数仅限于这些方法,但app.use
会根据任何请求执行。
中间件函数有两种可能的出口,继续使用下一个中间件函数next
或做出响应并终止链。
在上图中您可以看到以下内容:
- A
request
到达服务器。 - 第一个
app.use
被执行并执行next
。 - 第二个
app.use
是执行并执行next
。 - 该请求是一个请求路径 / 的 get 方法,因此
app.get
执行并发送响应。
发送响应会破坏中间件链,因此注意顺序很重要。
内置中间件
如果您正在构建一个向 REST API 提交数据的前端(例如提交表单),则可能需要读取这些值。过去,我们使用一个名为 的外部中间件来body.parser
从中读取这些值req.body
。现在,它已经集成在 Express 中,并且是内置中间件之一。
app.use(express.urlencoded({ extended: true }));
外部中间件
Express 有很多外部包,但前面我提到过morgan
,这个包只是一个外部中间件,如果我现在向你展示如何使用它,你就会完全理解这个想法:
import morgan from "morgan";
app.use(morgan("dev"));
正如您所见,使用外部中间件扩展 Express 的功能非常简单且干净。
最佳实践
MVC
MVC 代表模型-视图-控制器 (Model-View-Controller),它是不同系统中成熟的软件设计模式,在这里同样适用。以下是 MVC 的图形化概述:
在本教程的这个阶段,我们将仅使用Controller
,Model
稍后我们将在为数据库定义模型时添加,并且View
在这种情况下不适用,因为我们没有从服务器提供 HTML,视图在任何情况下都将是我们的 React 应用程序。
即使缺少某些部分,按照 MVC 模式拆分代码对于可读性和可维护性也是有用的,因此让我们隔离控制器中之前见过的所有用于操作数据的不同功能。
在该controller
文件夹下,我们将放置company.js
和joboffer.js
文件,其代码类似于以下内容:(查看示例项目以获取完整代码)
// controller/company.js
import path from "path";
import fs from "fs";
const DB_PATH = path.resolve("database/companies.json");
const list = (req, res) => {
fs.readFile(DB_PATH, (error, data) => {
if (error) {
console.error(error);
res.status(500).end();
} else {
res.status(200).send(JSON.parse(data));
}
});
};
const delete = (req, res) => {
const { id } = req.params;
fs.readFile(DB_PATH, (error, data) => {
if (error) {
console.error(error);
res.status(500).end();
} else {
const companies = JSON.parse(data);
const filteredData = JSON.stringify(
companies.filter(company => company.id !== id),
null,
2
);
fs.writeFileSync(DB_PATH, filteredData);
res.status(200).send(JSON.parse(filteredData));
}
});
};
export { list, delete }
*其他方法可以在示例项目中找到。
通过这样做,我们将与处理数据相关的代码隔离在一个文件中,然后我们可以根据需要重复使用这些代码,如下一节所示。
使用路由器的路由
有一种更好的方法来组织路线,尤其是现在我们想要添加另一个上下文。到目前为止,我们只讨论了 的路线,company
但现在我们想添加 的路线job offer
。让我们使用 来router
更好地组织路线。
在文件夹中routes
,我们将放置两个文件company.js
和joboffer.js
,其中将包含类似于此代码的内容:(查看示例项目以获取完整代码)
// routes/company.js
import express from "express";
import { list, create, details, update, remove } from "../controller/company";
const router = express.Router();
router.get("/", list);
router.post("/", create);
router.get("/find/:id", details);
router.put("/:id", update);
router.delete("/:id", remove);
export default router;
让我们检查一下那里发生了什么:
- 我们利用
Router
Express的功能。 - 使用路由器,我们可以按照与相同的方式添加路由
app
。 - 最后我们导出路由器。
稍后,我们可以使用该路由器来定义所有路由:
import express from "express";
import { companyRoutes, jobOfferRoutes } from "../routes";
const app = express();
// routes
app.use("/company", companyRoutes);
app.use("/job-offer", jobOfferRoutes);
我们为该app.use
路径定义一个上下文(这完全是可选的),并添加我们之前定义的路径。使用上下文的好处是,上面示例中的路由更简单,并且更容易在上下文之间移动。
因此,您无需在您的主文件中声明所有路由,而是app.js
将它们隔离在各自的文件中,这样其他开发人员将来修改时会更容易且更不容易出错。
TypeScript
正如我在本指南开头所说,TS 在这个项目中很有用,如果你检查示例项目是否在 TS 中输入,那么在指南的后期阶段,由于模型的类型检查,它将更加有用,但目前这里有一些好处:
清晰的数据结构
// types.ts
type Company = {
id: string;
about: string;
industries: string[];
name: string;
numberEmployees: string;
yearFounded: number;
};
type JobOffer = {
id: string;
availablePositions?: number;
companyId: string;
description: string;
function: string;
industry: string;
location: string;
numberApplicants?: number;
postDate: Date;
published: boolean;
requirements: string[];
salary?: number;
workType: string;
};
export { Company, JobOffer };
声明对象的类型可以让我们以及其他开发者对正在讨论的内容有一个大致的了解。现在,只需查看单个文件,您就可以清楚地了解数据的形式,哪些参数是必需的,哪些是可选的。
这在以后会更有用,但现在我们可以在控制器中使用这些类型来实现不易出错的功能,IntelliSense
高效使用并将这些类型包含在我们的测试中。
可读代码
remove
让我们检查一下公司控制器中该函数的更新版本:
// controller/company.ts
import { Request, Response } from "express";
import path from "path";
import fs from "fs";
import { Company } from "../types";
const DB_PATH = path.resolve("database/companies.json");
const remove = (req: Request, res: Response) => {
const { id } = req.params;
const companies: Company[] = JSON.parse(fs.readFileSync(DB_PATH).toString());
const company: Company | undefined = companies.find(company => company.id === id);
const newCompanies: Company[] = companies.filter(company => company.id !== id);
if (company) {
fs.writeFile(DB_PATH, JSON.stringify(newCompanies, null, 2), error => {
if (error) {
console.error(error);
res.status(500).end();
} else {
res.status(200).send({ message: `Company with id ${id} removed.` });
}
});
} else {
res.status(404).send({ message: `Company with id ${id} not found.` });
}
};
大多数类型都是推断出来的,没有必要明确地写出来,但我在这里添加了它,以便更好地理解我们现在知道每一步我们正在处理什么类型的数据,更重要的是,IDE 正在检查它是否遵循这种形式。
更好地理解外部工具
您在前面的例子中看到了这一点吗?
import { Request, Response } from "express";
const remove = (req: Request, res: Response) => {}
req
祝你好运,找出和参数里面的内容res
,你需要检查文档或调试,使用 TS,你将自动从 IDE 直接访问对象表单和文档,这是我目前在我的项目中使用 TS 的主要原因之一。
发布
让我们回顾一下发布后端的不同选项,以便其他人可以访问它,由于指南的当前大小,我将保留此部分作为摘要,但如果我觉得有必要,我会考虑就这一点制作更有针对性的指南。
当地的
从基本规模上讲,您已经拥有节点服务器的本地环境,但它在您当前的本地网络之外不可用,通过它您可以测试服务器,就像我们在 Postman 部分看到的那样。
如今,使用本地机器作为服务器已经不太常见了,如果你不想这样做,请查看下一部分,但如果你想将本地节点服务器公开给全世界,你可以使用ngrock,老实说,登陆页面上的介绍视频是不言而喻的😄
AWS
您可以使用 Amazon Web Services 来托管您的 Node.js 应用程序,我将列出步骤但不会详细介绍,因为使用 AWS 需要一些有关 AWS 的先验知识,并且超出了本指南的范围。
- 例如,使用 Ubuntu 请求弹性计算云 (EC2) 实例。
- 更新系统。
- 按照我们在 Ubuntu 的安装部分中所做的那样,在系统上安装 Node.js。
- 从 git 克隆您的后端项目或示例项目。
- 执行
npm install && npm start
此操作将使 Node.js 服务器可用。
这是本指南的简单分步说明,实际上有更好的方法来处理断开连接、重启等问题,如果您对此部分更感兴趣,请查看pm2 。
使用此选项时请谨慎,因为 AWS 有免费套餐,但可能会产生额外的使用费用。
赫罗库
最简单的选择之一,也是我将在这里详细介绍的选择,就是使用Heroku。Heroku 是一个平台即服务 (PaaS),它将简化配置系统使其从外部可见并充当服务器的过程。
Heroku 的一个很酷的功能是,我们可以在不使用任何信用卡或费用的情况下进行此类测试,因此它非常适合这样的指南以及您使用 Node.js 开发后端的第一次测试。
在示例项目中,我需要添加一个postinstall
TypeScript 脚本,以便 Heroku 在启动服务器之前将其编译为 JS 代码。
有两种方法可以上传后端项目(如本指南中的示例项目):
Heroku 命令行界面
Heroku 提供了一个命令行界面,我们可以使用它通过几个步骤来部署项目。首先直接从 npm 安装 cli:
npm install -g heroku
安装完成后,我们需要登录:
heroku login -i
如果您想在上传到 Heroku 之前验证一切是否正常,您可以使用以下命令进行检查:
heroku local web
web
将检查您的package.json
并查找start
脚本。
一旦一切都验证完毕,我们就在 Heroku 中创建项目并推送它:
heroku create
git push heroku main
create
获取存储 URL后,即可开始使用。如果您使用的是示例项目,则可以尝试使用新的 URL + /company
。例如,我的例子是 https://mars-pot-backend.herokuapp.com/company。
直接在网上。
- 登录 Heroku 后,在您的仪表板中选择
New
和Create new app
,您可以选择一个名称和一个区域。 - 然后您可以从 github 中选择您的项目并部署特定的分支。
- 部署完成后,
Settings
您可以查看Domains
项目 URL 部分。如果您使用的是示例项目,可以尝试使用新的 URL +/company
。在我的例子中,URL 是 https://mars-pot-backend.herokuapp.com/company。
为了成功部署,您必须有一个start
脚本,在package.json
这种情况下它将是启动节点服务器的脚本。
铁路
我在编写本指南的过程中发现了Railway ,我感到非常惊讶,我尝试在这里上传示例项目,几秒钟之内我就有了一个可以运行的实例,甚至有一个可用的预配置 MongoDB,但这是本指南的下一次迭代。
我还没有深入测试过这个选项,但我会在这个系列的未来版本中尝试它,因为它看起来很方便。
奖金
邮差
在本指南中,您可以直接在浏览器中测试不同的 api rest 端点,或者使用curl
一个可以让您和您的同事的生活更轻松的工具 - Postman。
与同事一起使用 Postman 甚至自己在业余项目中使用 Postman 的主要好处之一是可以轻松定义如何与 API 交互、提供示例以及在同一个工作区中协作以维护该集合。
还有大量可用的 API,因此您可以在开始编写之前测试其工作原理并规划如何编写任何代码,例如Twitter API 工作区。
测试端点
除了示例项目,我还提供了一个Postman 集合,您可以将其用作您的集合的示例或测试示例项目。
如果您想创建一组端点并测试您自己的应用程序,这就像选择请求方法和 url 一样简单。
对于必须向服务器传送数据的端点,可以通过 params 或 发送Body
。
Postman 提供了大量有关请求和响应的信息,因此您不会错过“开发人员工具网络”选项卡中的任何内容:
创建示例
在 Postman 集合中提供示例是一种很好的方法,可以确保您的同事或合作者无需实际运行任何东西就能看到数据的形状,在正常情况下这可能不是必需的,但当服务位于代理、身份验证甚至服务尚不可用时,它可以成为开发人员开始编写其部分代码的良好资源。
要创建新示例,请单击要添加示例的端点处的三个点并选择Add example
。
环境变量
就像在编程中一样,您可以将常量隔离在环境变量中以共享不同的配置,并更容易地修改集合或使用不同的环境测试端点。
在示例项目集合中,您可以找到用于在本地或直接在 Heroku 上发布版本上运行端点的变量。要使用 Postman 集合提供的环境,您必须导入同一文件夹中提供的两个 json 文件,即*environment.json
。