🔥 用 Node.js 为初学者构建一个简单的 API
我为什么学习Node.js
在用 JavaScript 做了一些项目之后,比如使用 Firebase 开发天气应用、待办事项列表,以及参与 JavaScript 30 天挑战等等,我渴望更深入地学习 JavaScript,达到能够熟练运用的水平,并学习一些后端技术。我记得刚开始学习 CSS 时,我花了很长时间才理解 CSS 选择器和 BEM 模型。但当我开始深入学习 CSS 处理器 SASS 时,我终于豁然开朗。通过学习 SASS,我的 CSS 水平得到了提升,也真正理解了 CSS 的强大之处。我喜欢使用嵌套、mixin、导入和函数,这让学习 CSS 变得充满乐趣。基于此,我决定将这种理念应用到我的 JavaScript 学习中,于是开始学习 Node.js。通过使用 Node.js 构建我的第一个小型 API,我理解了回调函数、数组方法,以及如何在实际场景中使用 replace 等函数,例如创建和填充 HTML 模板。
每个人的学习方式都不同,对我来说,学习一个 JavaScript 框架能帮助我更深入地理解 JavaScript,并把不同的东西联系起来,明白它们是如何协同工作的。看看Maximilian 的观点
吧。关键是要找到最适合你的学习方法,并把它运用到你的编程之旅中。适合我的方法可能不适合你,反之亦然。😊
Node.js简介
什么是Node.js?
根据官方网站的介绍,Node.js 是一个基于 Chrome V8 JavaScript 引擎的 JavaScript 运行时环境。JavaScript运行时环境就是你运行代码时执行 JavaScript 代码的地方。😲
别太迷糊,让我来给你解释一下。我相信你肯定用过 HTML、CSS 和 JavaScript 来创建网站功能。例如,创建响应式导航栏,或者使用 React 或 Vue 之类的 JS 框架。编写的 JavaScript 代码会在浏览器中执行,在这种情况下,浏览器就是 JavaScript 的运行时环境。
希望你现在都理解了。😏
现在你也可以在浏览器之外,在一个不受浏览器限制的新环境中编写 JavaScript 代码。这个环境叫做 Node.js。那么,如果我们不用浏览器来执行代码,我们用什么来执行呢?这要归功于Google 开发的V8 引擎,它负责运行 JavaScript 代码。
哇哦!现在你可以向任何人解释什么是 Node.js 了。
为什么要使用Node.js?
- 处理速度快——借助V8发动机,各项功能得以高速编译。了解一下V8发动机的性能速度吧。
- 非常适合可扩展的数据密集型应用
- 它是单线程的,并且具有非阻塞 I/O(输入/输出)模型。
- 它非常流行,并且拥有强大的支持。Node.js 社区非常活跃,可以帮助你,而且官方网站也提供了清晰的文档。像 Netflix、PayPal、Uber 和 eBay 这样的大公司都在他们的应用程序中使用它。
缺点
- 不建议用于繁重的服务器端处理。最好使用 Python 或 Ruby。
- 某些情况下会出现回调地狱。
Node.js 命令
- .break 有时候你会遇到困境,这可以帮你摆脱困境。
- .clear 是 .break 的别名。
- .editor 进入编辑器模式
- .exit 退出 REPL
- .help 打印此帮助信息
- .load 将文件中的 JS 代码加载到 REPL 会话中
- .save 将此 REPL 会话中所有已执行的命令保存到文件中
- Ctrl + D 退出节点终端
如何在Node.js中编写你的第一个代码
- 在你的文件夹中创建一个 index.js 文件
- 编写以下代码
const hello = 'Hello World';
console.log(hello);
- 打开终端并输入
node index.js
恭喜!你刚刚编写了你的第一行Node.js代码。
Node 模块
模块是一组函数。在 Node.js 中,你可以创建或使用现有的内置模块。请查看此内置模块列表。
如何使用模块
你需要使用名为 `require()` 的函数,并将模块名称作为参数传递给它。记得将模块名称存储在一个变量中,以便在代码中更方便地访问。`fs`
模块可以帮助我们直接与文件系统交互,进行数据读写操作。
const fs = require('fs');
如何创建和导入您自己的模块
- 创建一个文件夹和一个js文件,并在其中编写你的函数。
module.exports = (card, product) => {
let output = card.replace(/{%PRODUCT__NAME%}/g, product.productName);
output = output.replace(/{%IMAGE%}/g, product.image);
return output;
}
- 将文件导入到你的主js文件中
const templateReplace = require('./modules/templateReplace');
太棒了,你刚刚创建了你的第一个模块。我为你感到骄傲!🔥
如何读取文件
使用同步读取文件函数,该函数会读取文件的全部内容。输入参数为文件路径,字符编码为utf8。
const text = fs.readFileSync('./text/input.txt', 'utf8');
console.log(text);
// in the terminal run node index.js(file with the code) and you will see the text as the output.
如何写入文件
将你的代码存储在一个变量中。
const hello = 'Hello World! I am finally ready to learn Node!'
- 使用文件系统模块和写入文件同步功能,将数据同步写入文件,或者在文件已存在时替换文件。
//you have to specify the file path and the code you want to write
fs.writeFileSync('./txt/output.txt', hello);
了解更多关于文件系统及其使用方法的信息。
让我们来了解一下同步编码和异步编码之间的区别。
- 同步编码是指只有在前一段代码执行完毕后,下一段代码才能运行。如果代码执行时间过长,就会出现问题。如果应用程序用户活动频繁,情况会更加糟糕。注册、登录、阅读信息流或发布文章等简单的功能都需要更长时间才能完成。这种方法也称为阻塞式编码。
const fs = require('fs');
const text = fs.readFileSync('./text/prac.md', 'utf8');
console.log(text);
- 异步编程是指在应用程序运行其他代码的同时,在后台执行耗时较长的繁重代码。当其他代码执行完毕后,会通过回调函数调用异步代码,执行其结果/输出。这种编程方式也称为非阻塞编程。在下面的示例中,读取文件函数接受一个回调函数来获取数据。数据获取过程会在后台运行,首先会打印控制台日志输入。当函数获取完数据后,会将其作为输出记录下来。
const fs = require('fs');
fs.readFile('./text/prac.md', 'utf8', (err,data) => {
console.log(data);
});
console.log('Asynchronous code');
了解更多关于阻塞和非阻塞的信息
如何创建一个简单的 API
- 首先,我先在一张纸上画出了设计草图。
- 我用 Figma 设计了高保真原型和模型。这是我在 Figma 里的设计。(哈哈,我这个界面设计师对这个原型非常满意😊)
- 我打开VS Code编写项目的HTML和CSS代码。
如果你想查看项目文件,可以从 Github 上获取。
项目第 1 页
它列出了可供订购的食物清单。
第2页
详细介绍食物种类,并提供烹饪步骤指南。
如何通过一次 API 调用以用户身份请求数据。
- 使用 if 语句来匹配请求的 API URL。
if(pathName === '/api') {}
- 创建一个文件系统函数,该函数运行一次以读取数据。因此,您将使用同步文件系统函数。应用程序首次运行时,它将读取一次 JSON 数据;当用户访问 API 路由时,它将根据请求返回数据。使用 dirname 变量来定位脚本所在的目录是一个好习惯。要在 HTML 中使用 JSON 数据,您必须使用 JSON.parse 将 JSON 字符串数据转换为 JavaScript 并将其存储在一个变量中。
const data = fs.readFileSync(`${__dirname}/dev-data/data.json` 'utf-8');
const objData = JSON.parse(data);
- 使用 res.end 方法将数据作为响应发送到浏览器。
- 告诉浏览器你的数据是 JSON 格式,使用 res.writeHead 方法。
const pathName = req.url;
const data = fs.readFileSync(`${__dirname}/dev-data/data.json`, 'utf-8');
const objData = JSON.parse(data);
const server = http.createServer((req, res) => {
if(pathName === '/api') {
res.writeHead(200, {'Content-Type': 'application/json'})
res.end(data);
}else{
res.writeHead(404, {
'Content-type' : 'text/html ,charset=utf-8',
'my-own-header' : 'hello-world'
});
res.end('<h1>Page not found!</h1>');
}
});
server.listen(8000, '127.0.0.1', () => {
console.log('Server listening! 💣');
});
HTML模板
- 首先将你想动态添加的文本替换为稍后要添加的数据。
<div class="product__detail-box">
<p class="product__detail-text"><span class="product__detail-emoji">👨🍳</span>{%CHEF%}</p>
<p class="product__detail-text"><span class="product__detail-emoji">⏲</span>{%TIME%}</p>
<p class="product__detail-text"><span class="product__detail-emoji">📦</span>{%SERVERS%}</p>
<p class="product__detail-text"><span class="product__detail-emoji">💰</span>{%PRICE%}</p>
</div>
- 对于链接,请使用您存储在 JSON 数据中的 ID 号。由于数据存储在数组中,最好从 id = "0" 开始。在 href 属性中,您需要添加 API 路由和 ID 号,而不是使用 #。
<a href="/product?id={%ID%}" class="card__link">
<p class="card__link-text"> More →</p>
</a>
归档模板
- 请确保在应用程序初始化期间读取模板。您可以使用同步文件系统函数(这是一个阻塞方法)并将其存储在一个变量中。但需要注意的是,该函数只会在应用程序运行时运行一次,而不是在调用 createServer 回调函数时运行。
const templateOverview = fs.readFileSync(`${__dirname}/templates/template-overview.html`, 'utf8');
const templateCard = fs.readFileSync(`${__dirname}/templates/template-card.html`, 'utf8');
const templateProduct = fs.readFileSync(`${__dirname}/templates/template-product.html`, 'utf8');
- 在 createServer 回调函数中,使用 writeHead 函数告诉浏览器它即将接收的信息类型。
- 使用 map 方法遍历 JSON 数据,该方法将返回 replaceTemplate 函数,该函数包含来自 json 数据数组和卡片模板的各个数据。
if(pathname === '/' || pathName === '/overview' ){
//tells the browser the type of information it's about to receive
res.writeHead(200 , {'content-type' : 'text/html'});
const cardHtml = objData.map( obj => replaceTemplate(tempCard,obj)).join('');
const output = tempOverview.replace('{%PRODUCT_CARD%}', cardHtml);
res.end(output);
}
- 在 replaceTemplate 函数中,您可以使用 replace 方法将占位符文本替换为数据。
- 您可以使用正则表达式来确保在整个项目中全局选中占位符文本。
const replaceTemplate = (card, product) => {
let output = card.replace(/{%PRODUCT__NAME%}/g, product.productName);
output = output.replace(/{%IMAGE%}/g, product.image);
output = output.replace(/{%TIME%}/g, product.time);
output = output.replace(/{%CHEF%}/g, product.Chef);
output = output.replace(/{%SERVERS%}/g, product.servers);
output = output.replace(/{%PRICE%}/g, product.price);
output = output.replace(/{%ID%}/g, product.id);
output = output.replace(/{%TITLE__ONE%}/g, product.titleOne);
output = output.replace(/{%STEP__ONE%}/g, product.stepOne);
output = output.replace(/{%TITLE__TWO%}/g, product.titleTwo);
output = output.replace(/{%STEP__TWO%}/g, product.stepTwo);
output = output.replace(/{%TITLE__THREE%}/g, product.titleThree);
output = output.replace(/{%STEP__THREE%}/g, product.stepThree);
output = output.replace(/{%TITLE__FOUR%}/g, product.titleFour);
output = output.replace(/{%STEP__FOUR%}/g, product.stepFour);
output = output.replace(/{%TITLE__FIVE%}/g, product.titleFive);
output = output.replace(/{%STEP__FIVE%}/g, product.stepFive);
if(!product.vegetarian) output = output.replace(/{%NOT__VEGETARIAN%}/g, 'not-vegetarian');
return output;
}
- 对于 URL,您可以使用 url 模块。然后使用 url.parse 方法并将 req.url 作为参数传递。结果将得到一个查询对象,其中包含 ID 索引信息。在末尾添加 true 将使查询输出成为一个对象。
const {query, pathname} = url.parse(req.url, true);
query: {id = "0"},
pathname : "/overview"
- 使用查询 ID 获取每个元素的单独数据。
const product = objData[query.id];
结果
我们完成了
哇哦!!!这篇文章有点长。但我希望它能帮助你构建一个简单的 API。
在选择要学习的框架时,大胆尝试,专注于学习过程。不要被那些精通多种语言的人吓倒。记住,不要拿自己的起点和别人的中途比较。
感谢你读到这里,即使它这么长。随着我对 Node 的了解加深,我会继续发布内容。
本文内容/设计/代码参考了Jonas 的 Udemy 课程 - Node Bootcamp、官方 Node.js 文档和
W3School Node.js 教程。
学习Node.js的资源
- Node.js 官方文档
- W3School Node.js 教程
- 安德鲁·米德的Udemy课程
- Net Ninja 在 YouTube 上发布的 Node 速成课程
- Jonas Udemy课程 - Node Bootcamp

