让 LoopBack 来做:您梦寐以求的 Node API 框架演练

已发表: 2022-03-11

不用说 Node.js 在应用程序开发中的日益流行。 自 2011 年以来,eBay 一直在运行生产 Node API 服务。PayPal 正在积极地在 Node.js 中重建他们的前端。 沃尔玛的移动网站已成为最大的节点应用程序,流量明智。 2014 年感恩节周末,沃尔玛服务器处理了 15 亿个请求,其中 70% 通过移动设备交付并由 Node.js 提供支持。 在开发方面,Node 包管理器 (npm) 继续快速增长,最近超过 150,000 个托管模块。

虽然 Ruby 有 Rails,Python 有 Django,但主要的 Node 应用程序开发框架尚未建立。 但是,有一个强大的竞争者正在获得动力:LoopBack,一个由加利福尼亚州圣马特奥的 StrongLoop 公司构建的开源 API 框架。 StrongLoop 是最新 Node 版本的重要贡献者,更不用说 Express 的长期维护者了,Express 是现存最流行的 Node 框架之一。

让我们通过将所有内容付诸实践并构建示例应用程序来更深入地了解 LoopBack 及其功能。

什么是 LoopBack 以及它如何与 Node 一起使用?

LoopBack 是一个用于创建 API 并将它们与后端数据源连接起来的框架。 它建立在 Express 之上,可以采用数据模型定义并轻松生成功能齐全的端到端 REST API,任何客户端都可以调用该 API。

LoopBack 带有一个内置客户端API Explorer 。 我们将使用它,因为它可以更容易地查看我们的工作结果,并且我们的示例可以专注于构建 API 本身。

你当然需要在你的机器上安装 Node 来跟随。 在这里得到它。 npm 附带它,因此您可以轻松安装必要的软件包。 让我们开始吧。

创建骨架

我们的应用程序将管理想要向可能需要它们的人捐赠礼物或他们不再需要的东西的人。 因此,用户将是捐赠者和接收者。 捐赠者可以创建新礼物并查看礼物列表。 接收者可以查看所有用户的礼物列表,并可以领取任何无人领取的礼物。 当然,我们可以将捐赠者和​​接收者构建为同一实体(用户)上的不同角色,但让我们尝试将它们分开,以便了解如何在 LoopBack 中构建关系。 这个开创性的应用程序的名称将是Givesomebody

通过 npm 安装 StrongLoop 命令行工具:

 $ npm install -g strongloop

然后运行 ​​LoopBack 的应用程序生成器:

 $ slc loopback _-----_ | | .--------------------------. |--(o)--| | Let's create a LoopBack | `--------- | application! | ( _U`_ ) '--------------------------' /___A___\ | ~ | __'.___.'__ ` |° Y ` ? What's the name of your application? Givesomebody

让我们添加一个模型。 我们的第一个模型将被称为 Gift。 LoopBack 将询问数据源和基类。 由于我们还没有设置数据源,我们可以放db (memory) 。 基类是一个自动生成的模型类,我们想在这种情况下使用PersistedModel ,因为它已经包含了我们所有常用的 CRUD 方法。 接下来,LoopBack 询问它是否应该通过 REST 公开模型(是),以及 REST 服务的名称。 在此处按 enter 以使用默认值,即模型名称的复数形式(在我们的示例中为gifts )。

 $ slc loopback:model ? Enter the model name: Gift ? Select the data-source to attach Gift to: (Use arrow keys) ❯ db (memory) ? Select model's base class: (Use arrow keys) Model ❯ PersistedModel ? Expose Gift via the REST API? (Y/n) Yes ? Custom plural form (used to build REST URL):

最后,我们给出属性的名称、它们的数据类型和必需/非必需标志。 礼物将具有namedescription属性:

 Let's add some Gift properties now. Enter an empty property name when done. ? Property name: name invoke loopback:property ? Property type: (Use arrow keys) ❯ string ? Required? (y/N)Yes

输入一个空属性名称以指示您已完成定义属性。

模型生成器将在应用程序的common/models中创建两个定义模型的文件: gift.jsongift.js JSON 文件指定有关实体的所有元数据:属性、关系、验证、角色和方法名称。 JavaScript 文件用于定义附加行为,并指定要在某些操作(例如,创建、更新或删除)之前或之后调用的远程挂钩。

另外两个模型实体将是我们的 Donor 和 Receiver 模型。 我们可以使用相同的过程创建它们,只是这次我们将User作为基类。 它将为我们提供一些属性,如usernamepasswordemail等。 例如,我们可以只添加名称和国家/地区,以获得完整的实体。 对于收件人,我们也想添加送货地址。

项目结构

我们来看看生成的项目结构:

项目结构

三个主要目录是: - /server – 包含节点应用程序脚本和配置文件。 - /client – 包含 .js、.html、.css 和所有其他静态文件。 - /common – 此文件夹对服务器和客户端都是通用的。 模型文件放在这里。

以下是每个目录内容的详细分类,取自 LoopBack 文档:

文件或目录描述如何在代码中访问
顶级应用目录
package.json 标准 npm 包规范。 见 package.json 不适用
/server 目录 - 节点应用程序文件
server.js 主应用程序文件。 不适用
config.json 应用程序设置。 请参阅 config.json。 app.get('setting-name')
datasources.json 数据源配置文件。 请参阅 datasources.json。 有关示例,请参阅创建新数据源 app.datasources['datasource-name']
model-config.json 模型配置文件。 请参阅模型配置.json。 有关详细信息,请参阅 将模型连接到数据源 不适用
middleware.json 中间件定义文件。 有关更多信息,请参阅定义中间件。 不适用
/boot目录添加脚本以执行初始化和设置。 请参阅引导脚本。 脚本按字母顺序自动执行。
/client 目录 - 客户端应用程序文件
README.md LoopBack 生成器以 markdown 格式创建空的 README 文件。 不适用
其他添加您的 HTML、CSS、客户端 JavaScript 文件。
/common 目录 - 共享应用程序文件
/models目录自定义模型文件:
  • 模型定义 JSON 文件,按照约定命名为model-name .json ; 例如customer.json
  • 按约定命名的自定义模型脚本model-name .js ; 例如, customer.js
有关更多信息,请参阅模型定义 JSON 文件和自定义模型。
节点:
myModel = app.models.myModelName

建立关系

在我们的示例中,我们有一些重要的关系需要建模。 一个 Donor 可以捐赠许多 Gifts,这赋予了关系Donor has many Gift 。 一个 Receiver 也可以收到很多 Gifts,所以我们也有关系Receiver has many Gift 。 另一方面, Gift 属于 Donor ,如果 Receiver 选择接受,也可以属于 Receiver 。 让我们把它放到 LoopBack 的语言中。

 $ slc loopback:relation ? Select the model to create the relationship from: Donor ? Relation type: has many ? Choose a model to create a relationship with: Gift ? Enter the property name for the relation: gifts ? Optionally enter a custom foreign key: ? Require a through model? No

注意没有直通模式; 我们只是持有对礼物的引用。

如果我们对Receiver重复上述过程,并在Gift中添加两个属于关系,我们将在后端完成我们的模型设计。 LoopBack 自动更新模型的 JSON 文件,以准确表达我们刚刚通过这些简单对话框所做的事情:

 // common/models/donor.json ... "relations": { "gifts": { "type": "hasMany", "model": "Gift", "foreignKey": "" } }, ...

添加数据源

现在让我们看看如何附加一个真实的数据源来存储我们所有的应用程序数据。 出于本示例的目的,我们将使用 MongoDB,但 LoopBack 具有连接 Oracle、MySQL、PostgreSQL、Redis 和 SQL Server 的模块。

首先,安装连接器:

 $ npm install --save loopback-connector-mongodb

然后,将数据源添加到您的项目中:

 $ slc loopback:datasource ? Enter the data-source name: givesomebody ? Select the connector for givesomebody: MongoDB (supported by StrongLoop)

下一步是在server/datasources.json中配置您的数据源。 将此配置用于本地 MongoDB 服务器:

 ... "givesomebody": { "name": "givesomebody", "connector": "mongodb", "host": "localhost", "port": 27017, "database": "givesomebody", "username": "", "password": "" } ...

最后,打开server/model-config.json并将我们想要在数据库中持久化的所有实体的datasource更改为"givesomebody"

 { ... "User": { "dataSource": "givesomebody" }, "AccessToken": { "dataSource": "givesomebody", "public": false }, "ACL": { "dataSource": "givesomebody", "public": false }, "RoleMapping": { "dataSource": "givesomebody", "public": false }, "Role": { "dataSource": "givesomebody", "public": false }, "Gift": { "dataSource": "givesomebody", "public": true }, "Donor": { "dataSource": "givesomebody", "public": true }, "Receiver": { "dataSource": "givesomebody", "public": true } }

测试你的 REST API

是时候看看我们到目前为止所构建的内容了! 我们将使用很棒的内置工具API Explorer ,它可以用作我们刚刚创建的服务的客户端。 让我们尝试测试 REST API 调用。

在一个单独的窗口中,使用以下命令启动 MongoDB:

 $ mongod

运行应用程序:

 $ node .

在您的浏览器中,转到http://localhost:3000/explorer/ 。 您可以通过可用操作列表查看您的实体。 尝试通过 POST /Donors调用添加一位捐赠者。

测试你的 API 2

测试你的 API 3

API Explorer非常直观; 选择任何公开的方法,右下角会显示相应的模型架构。 在data文本区域,可以编写自定义 HTTP 请求。 填写好请求后,点击“Try it out”按钮,下面会显示服务器的响应。

测试你的 API 1

用户认证

如上所述,LoopBack 预构建的实体之一是 User 类。 用户拥有登录和注销方法,并且可以绑定到一个AccessToken实体,该实体保存特定用户的令牌。 事实上,一个完整的用户身份验证系统已经准备好开箱即用。 如果我们尝试通过API Explorer调用/Donors/login ,我们得到的响应如下:

 { "id": "9Kvp4zc0rTrH7IMMeRGwTNc6IqNxpVfv7D17DEcHHsgcAf9Z36A3CnPpZJ1iGrMS", "ttl": 1209600, "created": "2015-05-26T01:24:41.561Z", "userId": "" }

id实际上是AccessToken的值,自动生成并保存在数据库中。 正如您在此处看到的,可以设置访问令牌并将其用于每个后续请求。

用户认证

远程方法

远程方法是模型的静态方法,通过自定义 REST 端点公开。 远程方法可用于执行 LoopBack 的标准模型 REST API 未提供的操作。

除了我们开箱即用的 CRUD 方法之外,我们还可以添加任意数量的自定义方法。 所有这些都应该进入[model].js文件。 在我们的例子中,让我们在 Gift 模型中添加一个远程方法来检查礼物是否已被保留,并列出所有未保留的礼物。

首先,让我们为模型添加一个名为reserved的附加属性。 只需将其添加到gift.json的属性中:

 ... "reserved": { "type": "boolean" } ...

gift.js中的远程方法应该如下所示:

 module.exports = function(Gift) { // method which lists all free gifts Gift.listFree = function(cb) { Gift.find({ fields: { reserved: false } }, cb); }; // expose the above method through the REST Gift.remoteMethod('listFree', { returns: { arg: 'gifts', type: 'array' }, http: { path: '/list-free', verb: 'get' } }); // method to return if the gift is free Gift.isFree = function(id, cb) { var response; Gift.find({ fields: { id: id } }, function(err, gift) { if (err) return cb(err); if (gift.reserved) response = 'Sorry, the gift is reserved'; else response = 'Great, this gift can be yours'; }); cb(null, response); }; // expose the method through REST Gift.remoteMethod('isFree', { accepts: { arg: 'id', type: 'number' }, returns: { arg: 'response', type: 'string' }, http: { path: '/free', verb: 'post' } }); };

因此,要确定是否有特定礼物可用,客户端现在可以向/api/Gifts/free发送 POST 请求,并传入相关礼物的id

远程挂钩

有时需要在远程方法之前或之后执行某些方法。 您可以定义两种远程挂钩:

  • beforeRemote()在远程方法之前运行。
  • afterRemote()在远程方法之后运行。

在这两种情况下,您都提供了两个参数:一个与您要“挂钩”您的函数的远程方法匹配的字符串,以及回调函数。 远程挂钩的大部分功能是字符串可以包含通配符,因此它可以由任何匹配方法触发。

在我们的例子中,让我们设置一个挂钩,以便在创建新的 Donor 时将信息打印到控制台。 为了实现这一点,让我们在donor.js中添加一个“before create”钩子:

 module.exports = function(Donor) { Donor.beforeRemote('create', function(context, donor, next) { console.log('Saving new donor with name: ', context.req.body.name); next(); }); };

使用给定的context调用请求,并且在钩子运行后调用中间件(下面讨论)中的next()回调。

访问控制

LoopBack 应用程序通过模型访问数据,因此控制对数据的访问意味着定义对模型的限制; 也就是说,指定谁或什么可以读写数据或在模型上执行方法。 LoopBack 访问控制由访问控制列表或 ACL 确定。

让我们允许未登录的捐赠者和接收者查看礼物,但只有登录的捐赠者才能创建和删除它们。

 $ slc loopback:acl

首先,让我们拒绝所有人访问所有端点。

 ? Select the model to apply the ACL entry to: Gift ? Select the ACL scope: All methods and properties ? Select the access type: All (match all types) ? Select the role: All users ? Select the permission to apply: Explicitly deny access

接下来,让大家阅读Gift模型:

 $ slc loopback:acl ? Select the model to apply the ACL entry to: Gift ? Select the ACL scope: All methods and properties ? Select the access type: Read ? Select the role: All users ? Select the permission to apply: Explicitly grant access

然后,我们希望允许经过身份验证的用户创建礼物:

 $ slc loopback:acl ? Select the model to apply the ACL entry to: Gift ? Select the ACL scope: A single method ? Enter the method name: create ? Select the role: Any authenticated user ? Select the permission to apply: Explicitly grant access

最后,让我们允许礼物的所有者进行任何更改:

 $ slc loopback:acl ? Select the model to apply the ACL entry to: Gift ? Select the ACL scope: All methods and properties ? Select the access type: Write ? Select the role: The user owning the object ? Select the permission to apply: Explicitly grant access

现在,当我们查看gift.json时,一切都应该到位:

 "acls": [ { "accessType": "*", "principalType": "ROLE", "principalId": "$everyone", "permission": "DENY" }, { "accessType": "READ", "principalType": "ROLE", "principalId": "$everyone", "permission": "ALLOW" }, { "accessType": "EXECUTE", "principalType": "ROLE", "principalId": "$authenticated", "permission": "ALLOW", "property": "create" } ],

这里有一个重要说明: $authenticated是一个预定义角色,对应于系统中的所有用户(捐赠者和接收者),但我们只想允许捐赠者创建新礼物。 因此,我们需要一个自定义角色。 由于 Role 是我们开箱即用的另一个实体,我们可以利用它的 API 调用在 boot 函数中创建$authenticatedDonor角色,然后只需修改pricipalId中的gift.json

有必要创建一个新文件server/boot/script.js并添加以下代码:

 Role.create({ name: 'authenticatedDonor' }, function(err, role) { if (err) return debug(err); })

RoleMapping 实体将角色映射到用户。 确保 Role 和 RoleMapping 都通过 REST 公开。 在server/model-config.json中,检查 Role 实体的"public"是否设置为true 。 然后在donor.js中,我们可以编写一个“before create”钩子来映射RoleMapping POST API 调用中的userIDroleID

中间件

中间件包含向 REST 端点发出请求时执行的函数。 由于 LoopBack 基于 Express,它使用 Express 中间件和一个额外的概念,称为“中间件阶段”。 阶段用于明确定义调用中间件中函数的顺序。

以下是 LoopBack 文档中提供的预定义阶段列表:

  1. initial - 中间件可以运行的第一个点。
  2. session - 准备会话对象。
  3. auth - 处理身份验证和授权。
  4. parse - 解析请求正文。
  5. routes - 实现应用程序逻辑的 HTTP 路由。 通过 Express API app.use、app.route、app.get(和其他 HTTP 动词)注册的中间件在此阶段开始时运行。 将此阶段也用于 loopback/server/middleware/rest 或 loopback-explorer 等子应用程序。
  6. 文件- 提供静态资产(请求在此处访问文件系统)。
  7. final - 处理错误和对未知 URL 的请求。

每个阶段包含三个子阶段。 例如,初始阶段的子阶段是:

  1. 初始:之前
  2. 最初的
  3. 初始:之后

让我们快速浏览一下我们的默认 middleware.json:

 { "initial:before": { "loopback#favicon": {} }, "initial": { "compression": {}, "cors": { "params": { "origin": true, "credentials": true, "maxAge": 86400 } } }, "session": { }, "auth": { }, "parse": { }, "routes": { }, "files": { }, "final": { "loopback#urlNotFound": {} }, "final:after": { "errorhandler": {} } }

在初始阶段,我们调用loopback.favicon()loopback#favicon是该调用的中间件 ID)。 然后,调用第三方 npm 模块compressioncors (带或不带参数)。 在最后阶段,我们还有两个电话。 urlNotFound是 LoopBack 调用, errorhandler是第三方模块。 这个例子应该证明可以像使用外部 npm 模块一样使用许多内置调用。 当然,我们总是可以创建自己的中间件并通过这个 JSON 文件调用它们。

loopback-boot

最后,让我们提到一个模块,它导出初始化应用程序的boot()函数。 在server/server.js中,您将找到以下引导应用程序的代码:

 boot(app, __dirname, function(err) { if (err) throw err; // start the server if `$ node server.js` if (require.main === module) app.start(); });

该脚本将搜索server/boot文件夹,并按字母顺序加载它在其中找到的所有脚本。 因此,在server/boot中,我们可以指定任何应该在启动时运行的脚本。 一个例子是explorer.js ,它运行API Explorer ,我们用来测试我们的 API 的客户端。

有重复的忧郁吗? 不要再从头开始构建那个 Node API。 让 LoopBack 来做吧!
鸣叫

结论

在我离开你之前,我想提一下 StrongLoop Arc,它是一个图形 UI,可以用作slc命令行工具的替代品。 它还包括用于构建、分析和监控 Node 应用程序的工具。 对于那些不喜欢命令行的人来说,这绝对值得一试。 但是,StrongLoop Arc 即将被弃用,其功能正在集成到 IBM API Connect Developer Toolkit 中。

结论

一般来说,LoopBack 可以为您节省大量手动工作,因为您可以立即使用很多东西。 它使您可以专注于特定于应用程序的问题和业务逻辑。 如果您的应用程序基于 CRUD 操作并操纵预定义的实体,如果您厌倦了重写用户的身份验证和授权基础设施,而大量开发人员已经在您之前编写了该基础设施,或者您想利用一个伟大的 Web 框架的所有优势,例如Express,然后使用 LoopBack 构建您的 REST API 可以让您的梦想成真。 很简单的!