服务器上的Javascript和API简介

Makers在Unsplash上​​撰写的“女人和男人坐在监视器前”

在开始之前,有一个免责声明:编程发生在抽象的各个级别。 有些人仍然会直接编写汇编代码,有时(尽管很少)我们需要直接更改实际的位,但是几乎总是我们在后台使用对我们有很大帮助的工具。 这是任何库,框架甚至编程语言的本质。 它们为我们实现了很多自动化,因此我们可以专注于好东西,即我们想要制造的东西。 在本教程中,我们将使用某些框架和库来为我们提供帮助,但重要的是学习语言 ,而不仅仅是工具。 我们将讨论Java语言所产生的元素,但是它们将与特定框架交织在一起。


Javascript最初是为了在90年代的网站上提供基本功能而编写的。 但是,如今,Java可以在手表,机器人,服务器等所有设备上运行。 在本教程中,我们将专门学习如何在Web应用程序的后端使用Javascript来处理传入的用户请求。 我们还将集成一个第三方API,以探索工程师如何与外部数据源交互。 让我们深入研究吧!

在本教程中,我们将专注于使用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的文件,并将上面的脚本复制到其中。 第一行很简单,因为我们只是使用noderequire语法导入依赖项。 然后,我们继续通过调用express()创建我们的app对象。

注意:由于在第二行将express作为函数调用,因此我们知道express包仅导出函数。 软件包需要导出其关键部分,因此用户实际上可以利用它们。 程序包可以导出各种数据结构,因此,请务必阅读要导入的程序包的文档,以了解应如何使用它或可以访问哪些内容。

之后,我们调用app.get()并将两个参数传递给它:一个对应于URL的字符串,以及一个函数,该函数确定用户向该URL 请求时应运行什么代码。 在这种情况下,我们的路线是/ ,称为/ root 。 该函数将始终接受两个参数,即请求对象,然后是响应对象。 该请求是我们的应用程序正在接收的HTTP请求,它包含大量信息,例如用户尝试上载的数据或我们可能需要的用于验证用户身份的信息。 这是一个简单的后端API的基本结构:用户向我们的应用的特定路由发出请求,然后我们运行代码以确定向用户发送什么内容。 稍后我们将详细介绍。

注意 :我们传递给request.get()函数是一个匿名函数 ,这意味着我们正在定义它,并将其同时传递到某个地方。 我们无需为其命名,因为我们不会重复使用它,因此我们无需在任何地方保留对其的引用。 但是,对所有函数进行命名通常是一个很好的经验法则,因为名称会告诉我们某些功能,这使它可以自我记录 。 另外,如果我们为函数命名,那么如果出现错误,该名称将包含在任何堆栈跟踪中。 这将使我们更容易发现问题。

一旦定义了一些路由,我们的脚本将调用app.listen() ,该脚本 app.listen()两个参数:我们应在其上侦听请求的端口,以及在此过程完成后执行的函数。 在流程结束时调用的函数称为回调 。 在这种情况下,我们只想告诉用户该应用程序已准备好开始处理请求,因此我们的回调函数仅向控制台输出一条消息。

继续,通过在终端中运行node index.js来启动您的应用程序。 确保在包含index.js的目录中运行该命令,否则将收到错误消息,并且不会发生任何异常情况。 如果一切正常,您应该在控制台中看到“示例应用程序在端口3000上监听!”。 如果看不到,请尝试查看出现了什么错误。 您很可能不在正确的目录中,或者尚未安装express依赖项。

现在该应用程序正在运行,您可以在浏览器中导航到http://localhost:3000 ,然后在页面上将看到“ Hello World!”。 !

注意 :没有网络服务器 ,一个应用程序什么都不是,它是我们的应用程序获取请求之前处理请求的软件。 最受欢迎的Web服务器是Apache和nginx(发音为“ engine X”)。 如果我们没有运行Web服务器,则我们的应用程序将无法在我们的计算机上运行,​​因此express在本地为我们启动了一个小型Web服务器。 与Web服务器打交道已经开始进入开发运营领域,并且软件工程也越来越少,但人们需要知道我们的应用程序适合本地和生产环境中运行应用程序的更大生态系统的位置。


在继续扩展应用程序的功能之前,让我们进行一些重构以实践一些软件工程原则,其中最重要的是确保我们的代码具有可读性和自记录性。

第一步是将传递给路由的回调函数app.get() 。 让我们将其更改为如下所示:

 函数handleIndexRequest(req,res){ 
res.send('Hello World!');
} app.get('/',handleIndexRequest);

这是一个简单的示例,但是它将帮助我们养成确保根据函数的名称来命名函数,类和变量的习惯。 这样,其他人甚至是原始作者可以查看代码并快速了解正在发生的事情。 正如他们所说,软件工程只是在社交环境中进行编码,因此我们始终希望确保我们的代码对我们的同事而言清晰易读,这些同事以后可能需要处理我们编写的内容。

现在,我们唯一定义的路由是在我们的app对象上使用get方法(请记住,方法只是附加到对象或类的函数)。 这意味着该路由将仅响应GET请求,而不响应POST, PUTDELETE请求。 GET请求用于表示用户只想从服务器或应用程序中检索某些内容。 导航到浏览器地址栏中的URL时(例如,当我们不得不访问http://localhost:3000 ,将对该URL进行GET请求,浏览器将自动显示响应。

注意 :在本教程中,我们不会介绍如何处理表单和POST请求的示例,但是稍后我们链接一些资源,以自行探索应用程序开发的这一方面。 允许用户从您的应用程序中检索资源很酷,但是仅GET请求就不能使您构建令人印象深刻的动态应用程序,从而允许用户上载和更改信息。

因此,既然我们有一个非常基本的应用程序正在运行,我们就可以通过两种方式对其进行扩展:添加模板引擎,以及与第三方API进行接口。 这将要求我们安装一些新软件包,因此让我们一次使用这些软件包。


现在,当用户向我们的应用程序的根目录发出GET请求时,我们的应用程序将仅返回包含一些文本的基本响应。 但是,最常见的响应类型之一是HTML页面。 为了从我们的应用程序发送HTML页面,我们需要教它如何。

首先,让我们安装一个名为ejs的东西,它允许我们在服务器上使用Javascript构建HTML文件。

  npm install ejs-保存 

好漂亮 现在我们已经安装了依赖项,让我们告诉我们的应用程序使用它。

  app.set('view engine','ejs'); 

这是我们开始在应用程序中使用模板所需要做的大部分工作! 但是请记住,这只是express方式。 如果我们使用不同的Node框架,并想使用模板引擎,那将是一个不同的过程。 但是,我们确实确实站在巨人的肩膀上,因为这很难独自完成。 相反,我们可以依靠expressejs类的包来帮助我们更快地构建我们想要的东西。

注意 :但是,添加另一个软件包并不总是最好的做法。 每当我们在项目中添加其他人的代码时,我们就会信任他们。 更多的程序包意味着更多的安全问题机会,或者我们实际上不需要的额外代码可能会使我们的应用程序膨胀。 在这种情况下,模板化可能非常复杂,并且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页面,我们想教我们的应用程序如何基于我们的数据构建页面。 这被称为MVC架构,代表ModelViewController 。 我们的模型是我们的数据,我们的视图是我们的模板,我们的控制器是我们的逻辑,就像我们传递到路线中的函数一样。

现在,通过根据应用程序中的导航方式显示不同的内容,使模板动态化。

我们的路线可以通过几种方式包含动态信息。 我们可以将信息存储在称为查询参数的东西中。 查询参数是您在URL后面看到的内容? ,如果有的话。 例如ui/bento.html?reason=st类的示例,其中参数reason的值为st 。 我们可以在路由函数中将这些值作为req对象的一部分进行访问,如下所示:

  const原因=   req.query.reason;  //这等于'st' 

简单! express在获得URL之前会为我们手动读取URL,并为我们将URL组织到一个整洁的包中,因此我们只需检查req.query对象即可查看发送了哪些查询参数。 其他用于构建后端应用程序的框架(例如Django)将类似地预先打包请求数据,以使我们易于访问。 但是,向URL添加查询参数不会使它成为新路由,因此,如果我们向http://localhost:3000?foo=bar发出GET请求,则仍将触发应用程序根目录的路由定义。 因此,让我们继续更新路由功能以查找查询参数!

 函数handleIndexRequest(req,res){ 
const名称= req.query.name || '陌生人'; res.render('home'{name:name});
}

好吧,我们这里有一些新事物,不是吗? 我们定义了一个名为name的变量,并将其设置为等于“ name”查询参数字符串值“ Stranger”。 此语法仅表示如果条件的第一部分为false,则将使用第二部分,因此,如果我们在U​​RL中没有名为“ name”的查询参数,则此name变量将设置为“ Stranger”代替。 然后,在此之下,在render调用中,您看到我们添加了一个新对象作为第二个参数。 这就是所谓的上下文,基本上是我们要注入模板的数据。 现在,我们只需要担心注入name变量,因此我们可以将其传入。

注意 :在定义与变量名相同的Javascript对象时(例如,在{ name: name } ),我们可以将语法简化为更简单的{ name }例如{ name } 。 JS运行时将知道,这仅意味着我们希望键“名称”指向也称为“名称”的变量。

由于信息是通过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; 提取('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行,依此类推。不过,异步代码将扳手插入了这种模式,如果我们不确定发生的事情,事情可能会很快变得混乱。 当处理异步代码时,可能是执行第1行,然后执行第2行,然后执行第4行,然后执行第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()的调用。

可以使用then链接承诺,以便轻松处理异步操作。 直到上一个函数解析后 ,我们传递的每个函数才会执行 如果任何 then函数引发错误,都会执行catch方法。 我们始终希望确保能够处理错误,因此必须进行catch

注意fetch API可以完成很多涉及Web请求的工作,但是其默认行为是对传入的URL进行简单的GET请求。这是我们示例所需要的,但是我想清楚地解释一下,所做的就是向nexchange API发出GET请求,就像用户向我们的应用发出GET请求一样!

在继续之前,我们应该进行另一轮小型重构以实践一些软件工程原理。 函数一次只能真正关心一件事,因此,如果我们发现一个函数在做完全不同的两件事,则意味着我们可能应该将其分解为多个部分。

这就是责任的观念。 我们的路由功能负责发送回响应,但从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(代码)
.then(cryptoData => {
res.render('home',{cryptoData:cryptoData});
})
.catch(err => console.log(err))
});

现在,我们有了一个getCryptos()函数,该函数返回一个promise( fetch返回一个promise,但then也是如此,并且catch 。这是允许您链接它们的原因。)现在,我们的route函数只能调用getCryptos() ,而不会需要担心我们如何获取数据。 这样,如果我们决定稍后使用其他API来获取加密数据,则只需在getCryptos函数中对其进行更改。

注意 :根据代码的职责分离出代码的主要好处是它使代码更具可重用性。 如果我们有另一条需要加密数据的路由,也可以只调用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 ,将每个codename打印到li (list)元素中,如果货币是加密货币则返回✅,如果不是则返回’。 t。 不过,那只是伪装成怪异的ejs语法的基本if语句。 请记住,在调用forEachmap或类似的东西时,您传入一个函数作为参数,而该函数仅接受一条数据。 您可以决定每件作品的处理方式。 例如:

  const animals = ['dog','cat','moose'];函数printAnimal(animal){ 
console.log(animal);
} animals.forEach(printAnimal); //'狗'
// '猫'
//'驼鹿'

printAnimal运行3次,因为animals有3种物品。 第一次, animal论点是“狗”,第二次是“猫”,最后是“驼鹿”。 我们正在对加密数据执行相同的操作,但是我们正在构建HTML,而不是将其记录到控制台,这更酷!

无论如何,请继续保存所有内容,然后重新启动应用程序。 刷新浏览器标签后,您将看到最终结果!

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

……它只向我们显示了特定的一个!

这很多,但是它有助于设置后端应用程序的基础,因此您可以开始探索自己的想法。 我们介绍了:

  • 安装依赖项
  • 在Express应用中定义路线
  • 使用模板引擎进行HTML响应
  • 确保我们的代码干净且可读
  • 访问req对象以读取查询参数
  • 发出自己的HTTP请求以获取有趣的数据
  • 使我们的模板动态以向用户显示数据

没什么可打喷嚏的! 但是,如果您对自己的编程技能感到非常满意,那么以下是一些扩展您的应用程序的想法:

  • 添加CSS并改善布局
  • 使用其他API
  • 定义一条新路线并显示不同的数据

请确保订阅The Bit,以随时了解我们的教程,并随时阅读有关我们的更多信息或在此处注册我们的Beta。