

在本教程中,我们将专注于使用Express ,这是服务器端Javascript的微框架。 它建立在Node之上,Node只是运行在浏览器外部的JS运行时(如前面提到的在手表,机器人和服务器上)。
注意:在继续之前,您需要了解使用NPM或Yarn设置新的JS应用程序的基础知识(不用担心,对于这个项目,这很容易)。 您还需要确保在计算机上安装了Node。 如果您不知道该怎么做,请参阅有关使用NPM的教程。
为此项目创建一个新目录,并确保在其中使用npm init以便我们可以开始跟踪我们的软件包。 之后,我们可以安装express :
npm install-保存快递
现在,这就是我们所需要的,但是值得一提的是,服务器端JS生态系统是多样且广阔的。 这意味着您可以与express一起将成千上万的软件包添加到应用程序中,以处理身份验证,中间件,文件管理,数据库连接等。功能齐全的Javascript后端应用程序可能包含各种各样的软件包,所有这些软件包都可以协同工作。 这就是我之前所说的express微框架的意思:它只为我们处理少量功能,其余的将来自与express完全无关的其他软件包! 整齐。
好吧,让我们看一下Express文档中提供的官方Hello World示例:
const express = require('express');
const app = express(); app.get('/',(req,res)=> res.send('Hello World!')); app.listen(3000,()=> {
console.log('示例应用程序在端口3000上监听!');
});
我们可以稍作介绍,以便对基本概念有所了解。 继续,在您的项目目录中创建一个名为index.js的文件,并将上面的脚本复制到其中。 第一行很简单,因为我们只是使用node的require语法导入依赖项。 然后,我们继续通过调用express()创建我们的app对象。
之后,我们调用app.get()并将两个参数传递给它:一个对应于URL的字符串,以及一个函数,该函数确定用户向该URL 请求时应运行什么代码。 在这种情况下,我们的路线是/ ,称为/ root 。 该函数将始终接受两个参数,即请求对象,然后是响应对象。 该请求是我们的应用程序正在接收的HTTP请求,它包含大量信息,例如用户尝试上载的数据或我们可能需要的用于验证用户身份的信息。 这是一个简单的后端API的基本结构:用户向我们的应用的特定路由发出请求,然后我们运行代码以确定向用户发送什么内容。 稍后我们将详细介绍。
一旦定义了一些路由,我们的脚本将调用app.listen() ,该脚本还 app.listen()两个参数:我们应在其上侦听请求的端口,以及在此过程完成后执行的函数。 在流程结束时调用的函数称为回调 。
继续,通过在终端中运行node index.js来启动您的应用程序。 确保在包含index.js的目录中运行该命令,否则将收到错误消息,并且不会发生任何异常情况。 如果一切正常,您应该在控制台中看到“示例应用程序在端口3000上监听!”。 如果看不到,请尝试查看出现了什么错误。 您很可能不在正确的目录中,或者尚未安装express依赖项。
现在该应用程序正在运行,您可以在浏览器中导航到http://localhost:3000 ,然后在页面上将看到“ Hello World!”。 !
在继续扩展应用程序的功能之前,让我们进行一些重构以实践一些软件工程原则,其中最重要的是确保我们的代码具有可读性和自记录性。
第一步是将传递给路由的回调函数app.get() 。 让我们将其更改为如下所示:
函数handleIndexRequest(req,res){
res.send('Hello World!');
} app.get('/',handleIndexRequest);
这是一个简单的示例,但是它将帮助我们养成确保根据函数的名称来命名函数,类和变量的习惯。 这样,其他人甚至是原始作者可以查看代码并快速了解正在发生的事情。 正如他们所说,软件工程只是在社交环境中进行编码,因此我们始终希望确保我们的代码对我们的同事而言清晰易读,这些同事以后可能需要处理我们编写的内容。
现在,我们唯一定义的路由是在我们的app对象上使用get方法(请记住,方法只是附加到对象或类的函数)。 这意味着该路由将仅响应GET请求,而不响应POST, PUT或DELETE请求。 GET请求用于表示用户只想从服务器或应用程序中检索某些内容。 导航到浏览器地址栏中的URL时(例如,当我们不得不访问http://localhost:3000 ,将对该URL进行GET请求,浏览器将自动显示响应。
因此,既然我们有一个非常基本的应用程序正在运行,我们就可以通过两种方式对其进行扩展:添加模板引擎,以及与第三方API进行接口。 这将要求我们安装一些新软件包,因此让我们一次使用这些软件包。
现在,当用户向我们的应用程序的根目录发出GET请求时,我们的应用程序将仅返回包含一些文本的基本响应。 但是,最常见的响应类型之一是HTML页面。 为了从我们的应用程序发送HTML页面,我们需要教它如何。
首先,让我们安装一个名为ejs的东西,它允许我们在服务器上使用Javascript构建HTML文件。
npm install ejs-保存
好漂亮 现在我们已经安装了依赖项,让我们告诉我们的应用程序使用它。
app.set('view engine','ejs');
现在我们的应用程序可以从技术上理解HTML模板,让我们创建一些新目录来存储它们。 在与index.js文件相同的目录中,创建一个名为views的目录。 在其中创建一个名为home.ejs的文件。 注意文件扩展名.ejs ,它不是我们期望看到的普通.html扩展名。 这只是ejs的特质,因此程序包知道在为用户创建新HTML文件时应该查看哪些文件。
这是我们的文件结构现在的样子:


现在,将一些基本HTML添加到我们的home.ejs文件中:
是加密货币
我认为我们的模板正在运行。
现在应该这样做,以后我们可以使其变得动态。 让我们继续进行测试,方法是更新索引路由功能以发回该HTML而不是“ Hello World!”文本。 express具有多种响应类型,例如JSON和HTML,这可能是最常见的。 通过向模板传递要使用的模板,我们可以在res对象上使用render方法(这是我们将向用户发送的响应)。 更新handleIndexRequest函数,使其看起来像以下片段:
函数handleIndexRequest(req,res){
res.render('home');
}
注意 :
render方法将自动在我们的views目录中查找作为参数传递的模板(在本例中为home.ejs),因此我们不需要传递模板的完整相对路径。 您可能只有通过阅读express文档才能知道这一点。 RTFM!
现在回到您的终端,并通过Ctrl + C键盘上的Ctrl + C终止该应用程序(如果它仍在运行)(这是终止进程的命令)。 然后再次使用node index.js重新启动应用程序。 刷新浏览器时,您应该会看到一些漂亮的大文本,上面写着“我认为我们的模板正在工作”! 如果看不到,请确保所有文件都已保存,并且已安装ejs依赖项。


但是ejs让我们做一些更酷的事情,这使我们的视图变得动态起来 。 当您访问YouTube时,随着这些事情的改变,您不一定总是在首页上看到相同的视频。 我们不想手动构建所有HTML页面,我们想教我们的应用程序如何基于我们的数据构建页面。
现在,通过根据应用程序中的导航方式显示不同的内容,使模板动态化。
我们的路线可以通过几种方式包含动态信息。 我们可以将信息存储在称为查询参数的东西中。 查询参数是您在URL后面看到的内容? ,如果有的话。 例如ui/bento.html?reason=st类的示例,其中参数reason的值为st 。 我们可以在路由函数中将这些值作为req对象的一部分进行访问,如下所示:
const原因= req.query.reason; //这等于'st'
简单! express在获得URL之前会为我们手动读取URL,并为我们将URL组织到一个整洁的包中,因此我们只需检查req.query对象即可查看发送了哪些查询参数。 但是,向URL添加查询参数不会使它成为新路由,因此,如果我们向http://localhost:3000?foo=bar发出GET请求,则仍将触发应用程序根目录的路由定义。 因此,让我们继续更新路由功能以查找查询参数!
函数handleIndexRequest(req,res){
const名称= req.query.name || '陌生人'; res.render('home'{name:name});
}
好吧,我们这里有一些新事物,不是吗? 我们定义了一个名为name的变量,并将其设置为等于“ name”查询参数或字符串值“ Stranger”。 此语法仅表示如果条件的第一部分为false,则将使用第二部分,因此,如果我们在URL中没有名为“ name”的查询参数,则此name变量将设置为“ Stranger”代替。 然后,在此之下,在render调用中,您看到我们添加了一个新对象作为第二个参数。 这就是所谓的上下文,基本上是我们要注入模板的数据。 现在,我们只需要担心注入name变量,因此我们可以将其传入。
由于信息是通过render方法发送到我们的模板的,因此让我们更新模板以显示它。 用
将行更新为以下内容:
我认为我们的模板正在运行,!
我认为我们的模板正在运行,!
这种新的语法都是特定于ejs ,而不是Node或Javascript或其他任何东西。 但是现在,如果您重新启动应用程序并刷新浏览器选项卡,您将看到此信息!


如果您添加了一个名为“ name”的查询参数…


那有多光滑! 现在,我们的应用程序可以基于我们的数据构建视图。 这只是一个简单的示例,如果您想阅读文档并真正开始探索, ejs可以做更多的事情。 现在,如果我们有更多有趣的数据呢?
有时,我们将需要或希望依靠外部数据源来扩展应用程序的功能。 为此,我们可以使用API或应用程序编程接口。 术语“ API”基本上用于我们谈论与库,框架或服务的交互方式的任何地方。 但是,它最通常指的是后端应用程序上可用的端点。 从技术上讲,我们通过使用express创建路由来构建API,但是我们的API并不适合其他开发人员使用。
但是幸运的是,我们可以使用很多免费的第三方API,因此在本教程中,我们将使用nexchange.io API并显示一些基本的加密货币信息。 通常,使用像此类cryptocurrency API这样的第三方服务需要您注册和接收API Key 。 这样可以确保维护API的公司可以防止人们滥用其服务。 现在,在另一个教程中,我们可以进一步探讨nexchange.io允许我们查看的数据。
继续并访问https://api.nexchange.io/en/api/v1/currency/,您将看到我们从他们的服务器获得的响应。 一长串数据包含有关多种货币的一些基本信息,当我们从应用程序调用此终结点时,我们将可以直接访问相同的信息。 那么,我们如何继续提出该请求?
我们将使用fetch ,它直接内置在浏览器中的Javascript运行时中。 不幸的是,Node默认情况下不附带它,但是我们可以通过添加它来练习我们的依赖项管理技能。 继续并安装node-fetch软件包。 哦,别忘了导入依赖项:
var fetch = require('node-fetch');
一旦安装了node-fetch ,就让我们更新根路由功能,使其看起来像以下片段:
app.get('/',(req,res)=> {
const code = req.query.code; fetch('https://api.nexchange.io/en/api/v1/currency/')
.then(cryptoData => cryptoData.json())
.then(cryptoData => {
返回码?
cryptoData.filter(crypto => crypto.code ==代码):
cryptoData;
})
.then(cryptoData => {
res.render('home',{cryptoData:cryptoData});
})
.catch(err => console.log(err))
});
哇! 好的,这是不同的。 最明显的区别似乎是重复调用then 。 这是因为fetch返回Promise 。 Javascript开发人员使用承诺来处理异步代码,这仅意味着它在后台运行,我们无法确切知道何时完成运行。 Web请求始终是异步的,因为在服务器之间发送数据始终需要花费不同的时间来执行。
通常,代码按其编写的顺序运行。 首先执行第1行,然后执行第2行,然后执行第3行,依此类推。不过,异步代码将扳手插入了这种模式,如果我们不确定发生的事情,事情可能会很快变得混乱。 例如,为什么此代码不起作用?
app.get('/',(req,res)=> {
让数据;获取('https://api.nexchange.io/en/api/v1/currency/')
.then(cryptoData => cryptoData.json())
.then(cryptoData => {
数据= cryptoData;
})
.catch(err => console.log(err)); res.render('home',{cryptoData:data});
});
我们声明data变量,然后当从API中获取加密数据时,我们将data设置为等于加密数据,然后使用该数据渲染模板。
我不这么认为! fetch是异步的,因此实际发生的是:
- 我们声明
data变量 - 我们启动网络请求
- 我们返回我们的回应
- Web请求完成,我们将
data设置为等于加密数据
如果我们像上面的代码片段那样构造它,则我们的data将始终在模板中未定义! 我们需要确保在res.render()数据后才将响应发送回去,这意味着在res.render()的then方法中包括对res.render()的调用。
在继续之前,我们应该进行另一轮小型重构以实践一些软件工程原理。 函数一次只能真正关心一件事,因此,如果我们发现一个函数在做完全不同的两件事,则意味着我们可能应该将其分解为多个部分。
这就是责任的观念。 我们的路由功能负责发送回响应,但不从API获取数据。 路由功能并不关心数据的来源或获取方式,因此我们应该将责任分解为自己的功能。 让我们重构路由定义,使其看起来像这样:
函数getCryptos(code){
返回提取('https://api.nexchange.io/en/api/v1/currency/')
.then(cryptoData => cryptoData.json())
.then(cryptoData => {
返回cryptoData.filter(crypto => crypto.code ==代码);
})
.catch(err => console.log(err));
} app.get('/',(req,res)=> {
const code = req.query.code; getCryptos(code)
.then(cryptoData => {
res.render('home',{cryptoData:cryptoData});
})
.catch(err => console.log(err))
});
现在,我们有了一个getCryptos()函数,该函数返回一个promise( fetch返回一个promise,但then也是如此,并且catch 。这是允许您链接它们的原因。)现在,我们的route函数只能调用getCryptos() ,而不会需要担心我们如何获取数据。 这样,如果我们决定稍后使用其他API来获取加密数据,则只需在getCryptos函数中对其进行更改。
哦,还有关于诺言的最后一句话。 then返回的是下一个将作为参数的返回值。 这就是使我们能够链接它们的原因。 我们通常使用一次调用来对数据then每次转换。 假设我们从API接收了一些数据,然后需要对其进行排序,过滤并进行第一部分处理,我们的诺言代码可能如下所示:
getAsyncData()// [3,1,2]
.then(sortData)//返回[1、2、3]
.then(filterData)//返回大于1的值:[2,3]
.then(takeFirstDatum); //返回2
我们可以将所有逻辑包装到一个函数中,在其中对数据进行排序和过滤,然后截取第一部分,但是当我们的then函数开始变得有点复杂时,可以在其他地方定义这些函数,让它们清晰易懂名称, then像上面一样将函数引用传递给then 。 它有助于提高可读性,并使代码看起来像是如何将API数据转换为我们所需的食谱。
在上面的代码片段中,您将看到我们res.render的调用更新为在上下文对象中包含cryptoData ,该对象以前是name 。 现在我们可以继续更新模板,以显示返回的加密数据。 但是实际上,在执行此操作之前,让我们保存所有内容并重新启动应用程序。
当您导航到根路线时…


…您会看到该应用程序已损坏,我们会收到此错误消息和堆栈跟踪信息,以帮助您找出问题所在。 你能弄清楚发生了什么吗? 第一行告诉我们它的ReferenceError ,在代码片段下方您会看到消息抱怨name is not defined 。 这仅仅是因为我们没有将名为“ name”的变量传递到模板中,所以不确定该怎么做! 这是一个简单的修复,但是我们希望您看到正确的错误和堆栈跟踪。 您将希望熟悉错误和堆栈跟踪,因为无论您多么有才华,您仍然会每天看到它们。
无论如何,让我们更新模板以查找cryptoData而不是name 。 由于cryptoData将是一个数组,因此我们将必须学习如何使用ejs模板语法对数据进行迭代。 这是我们的新home.ejs文件:
标题
- -:
✅
❌
虽然语法有点混乱,但这是一个非常简单的逻辑,您将很快获得。 我们要做的就是在我们的加密数据数组上调用forEach ,将每个code和name打印到li (list)元素中,如果货币是加密货币则返回✅,如果不是则返回’。 t。 不过,那只是伪装成怪异的ejs语法的基本if语句。
继续并保存所有内容,然后重新启动应用程序。 刷新浏览器标签后,您将看到最终结果!


当我们使用一个密码代码添加一个名为“ code”的查询参数时…


……它只向我们显示了特定的一个!
这很多,但是它有助于设置后端应用程序的基础,因此您可以开始探索自己的想法。 我们介绍了:
- 安装依赖项
- 在Express应用中定义路线
- 使用模板引擎进行HTML响应
- 确保我们的代码干净且可读
- 访问
req对象以读取查询参数 - 发出自己的HTTP请求以获取有趣的数据
- 使我们的模板动态以向用户显示数据
没什么可打喷嚏的! 但是,如果您对自己的编程技能感到非常满意,那么以下是一些扩展您的应用程序的想法:
- 添加CSS并改善布局
- 使用其他API
- 定义一条新路线并显示不同的数据