在 MEAN 堆栈上为 MongoDB 创建 OData 端点






4.76/5 (9投票s)
本文是一个关于在 MEAN 堆栈上为 MongoDB 数据库创建基本 OData(开放数据协议)端点的教程。
引言
对于初学者,这里有 OData 和 MEAN 堆栈的简要描述,以及我对其有用性的看法。
OData(开放数据协议)是一个 OASIS 标准,它定义了一套构建和消费 RESTful API 的最佳实践。OData 帮助您专注于业务逻辑,同时构建 RESTful API,而无需担心定义请求和响应头、状态码、HTTP 方法、URL 约定、媒体类型、有效负载格式、查询选项等各种方法。OData 还提供了跟踪更改、为可重用过程定义函数/操作以及发送异步/批量请求的指导。
-- http://www.odata.org/
由于 OData 是基于 HTTP 等核心协议并使用 REST 方法构建的,因此很容易构建可用于查询底层数据库的 RESTful API。客户端也很容易消费 OData Restful API,因为有标准的协议和 URL 约定用于查询、过滤、聚合、CRUD 操作和数据共享。OData API 的另一个优点是元数据。消费 API 的开发人员可以轻松理解 API 数据模型的描述,并且 OData 元数据是机器可读的。
MEAN 是一个免费的开源 JavaScript 软件栈,用于构建动态网站和 Web 应用程序。MEAN 堆栈使用 MongoDB、Express.js、Angular.js 和 Node.js。由于 MEAN 堆栈的所有组件都支持用 JavaScript 编写的程序,因此 MEAN 应用程序可以在一个语言中编写,用于服务器端和客户端执行环境。
-- https://en.wikipedia.org/wiki/MEAN_(software_bundle)
MEAN 堆栈的一大优势是其所有组件都是开源的,并且定期更新,免费使用。另一个优势是客户端和服务器端都使用 JavaScript,这使得 Web 开发人员很容易成为全栈开发人员。
背景
我一直在寻找一种标准方法来在 MEAN 堆栈上的 MongoDB 数据库上进行查询、过滤、搜索和执行 CRUD 操作。虽然有很多关于使用 Microsoft .NET 堆栈构建 OData 端点的教程,但我找不到任何从头到尾关于在 MEAN 堆栈上构建简单 OData 端点的教程。我的想法是分享我通过 Web 和开源技术学到的知识,希望它能帮助其他人。
必备组件
如果您打算按照本教程操作,您需要安装以下项目
1) 从 https://node.org.cn 安装适用于您机器的 node.js。我下载并安装了适用于 Windows (x64) 的 node-v4.4.7-x64.msi。
2) 从 https://mongodb.ac.cn 安装适用于您机器的 MongoDB。我下载了适用于 Windows Server 2008 R2 64 位及更高版本(带 SSL 支持)的 MongoDB Community Server 3.2.7 版本,并将其安装在我的 Windows 7 64 位机器上。有关安装的更多信息,请参阅 https://docs.mongodb.com/getting-started/shell/installation/
让我们开始吧
在本节中,我们将为我们的 OData 端点创建一个工作应用程序目录“odataapp”和 package.json 文件。我们还将在应用程序目录中安装 Express 和其他 npm 包。
创建应用程序目录
创建一个目录来存放您的应用程序,并将其设置为您的工作目录。在我的机器上,工作目录位于“C:\projects\odataapp”。打开命令提示符,并将目录更改为您的工作目录。使用 npm init 命令为您的应用程序创建一个 package.json 文件。
npm init
如下图所示,此命令会提示您输入一些信息,例如应用程序的名称和版本。目前,您可以直接按 RETURN 键接受大多数默认值。
在应用程序目录中安装 Express,并通过运行以下命令将其保存到 package.json 文件的依赖项列表中。
npm install express --save
既然我们讨论的是 MEAN 堆栈,我们将把数据存储在 MongoDB 中。安装适用于 mongodb 的 npm 包。
npm install mongodb --save
安装其他 npm 包。在本教程中,我使用 simple-odata-server 包为 MongoDB 集合创建 OData 端点。
npm install simple-odata-server --save
安装以下软件包是可选的。安装跨域资源共享(CORS)软件包将使应用程序能够服务来自端点域之外的其他域的 AJAX (XMLHttpRequest) 请求。
npm install cors --save
下图显示了我在应用程序目录中安装 simple-odata-server 和 cors 包后的命令提示符。
成功安装软件包后,“package.json”文件应包含所有列出的依赖项,如下所示。
{
"name": "odataapp",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.7.1",
"express": "^4.14.0",
"mongodb": "^2.2.1",
"simple-odata-server": "^0.2.0"
}
}
导入数据到 MongoDB 进行演示
在本教程中,我们将使用 MongoDB 附带的 'mongoimport' 工具将 files\products.json 中的数据导入到 MongoDB 服务器。您可以使用以下命令或 RoboMongo 或您选择的任何其他工具导入数据。我已经将数据文件包含在源代码中,并突出显示了下图中的导入命令。数据被导入到 MongoDB 服务器的 'demo' 数据库的 'products' 集合中。
如果数据成功导入到 demo 数据库的 'products' 集合中,您可以使用以下命令在 Mongo shell 中查看数据。
use demo
db.products.find()
如果您正在使用 RoboMongo,查询“products”集合时应该会看到 11 个项目。
使用代码
在 package.json 中,注意主字段中列出的文件。在我们的演示中,我们将其保留为 index.js。它是应用程序的主要入口点。让我们在应用程序目录中创建一个新的 index.js 文件,其中包含以下代码。您可以使用 Visual Studio Code、Sublime Text 或您选择的任何代码编辑器来创建和编辑此文件。我在变量和方法之前添加了注释,以帮助您理解代码。
// Assign the required packages and dependencies to variables var express = require('express'); var ODataServer = require("simple-odata-server"); var MongoClient = require('mongodb').MongoClient; var cors = require("cors"); // Create app variable to initialize Express var app = express(); // Enable Cross-origin resource sharing (CORS) for app. app.use(cors()); // Define Odata model of the resource entity i.e. Product. // The metadata is defined using OData type system called the Entity Data Model (EDM), // consisting of EntitySets, Entities, ComplexTypes and Scalar Types. var model = { namespace: "demo", entityTypes: { "Product": { "_id": {"type": "Edm.String", key: true}, "ProductNum": {"type": "Edm.Int32"}, "Name": {"type": "Edm.String"}, "Description": {"type": "Edm.String"}, "ReleaseDate": {"type": "Edm.DateTime"}, "DiscontinuedDate": {"type": "Edm.DateTime"}, "Rating": {"type": "Edm.Int32"}, "Price": {"type": "Edm.Double"} } }, entitySets: { "products": { entityType: "demo.Product" } } }; // Instantiates ODataServer and assigns to odataserver variable. var odataServer = ODataServer() .model(model); // Connection to demo database in MongoDB MongoClient.connect("mongodb:///demo", function(err, db) { odataServer.onMongo(function(cb) { cb(err, db); }); }); // The directive to set app route path. app.use("/odata", function (req, res) { odataServer.handle(req, res); }); // The app listens on port 3010 and prints the endpoint URI in console window. var server = app.listen(3010, function () { console.log('Server running at http://127.0.0.1:3010/'); });
让我们通过在工作目录打开命令提示符并运行 node index.js 来运行应用程序,如下所示。如果一切正常,您应该会看到服务器运行的控制台消息。
查询集合
让我们向在本地 Node 服务器端口 3010 运行的 OData 端点 index.js 发送一些 OData 查询。在下面的示例中,我使用 URL https://:3010/odata/$metadata 请求元数据。
让我们通过 HTTP GET 请求从 OData 端点请求数据。下面的请求返回 MongoDB 服务器中“demo”数据库中的“products”集合。
我正在 Google Chrome 浏览器中发出 HTTP 请求。我已在 Chrome 中添加了 JSONView 应用程序以查看 JSON 文档。您可以使用 Fiddler 或 Postman 或您选择的任何其他开发工具来发送 HTTP 请求和响应。
下面是按 ID 请求单个实体的示例。
让我们发送一个 get 请求 https://:3010/odata/products?$filter=Name eq 'Bread' 来过滤集合中 Name 等于 'Bread' 的数据。
您可以尝试以下 OData 查询来筛选、排序、统计 MongoDB 中的产品集合。
$filter 查询选项允许客户端筛选资源集合
https://:3010/odata/products?$filter=Name eq 'Bread' or Name eq 'Milk'
$top 查询选项请求查询集合中包含在结果中的项目数。
https://:3010/odata/products?$top=5
$skip 查询选项请求查询集合中要跳过且不包含在结果中的项目数。
https://:3010/odata/products?$skip=10
$select 查询选项允许客户端为每个实体请求一组有限的属性
https://:3010/odata/products?$select=Name
https://:3010/odata/products?$select=Name, Description
$orderby 查询选项允许客户端使用 asc 升序或 desc 降序请求资源。如果未指定 asc 或 desc,则资源将按升序排列。
https://:3010/odata/products?$orderby=ProductNum desc
https://:3010/odata/products?$orderby=Rating
$count 选项允许客户端请求集合中的计数。
https://:3010/odata/products/$count
根据上述查询,您可以看到 OData 端点可以是通用的 WebAPI,以标准方式查询集合。例如,我可以使用以下 OData 查询获取集合中最昂贵的三种产品及其 Id、名称、描述和价格。
https://:3010/odata/products?$select=Name, Description, Price&$orderby=Price desc&$top=3
我们还可以使用 OData 端点对我们的产品集合执行 CRUD 操作。为了演示这一点,我将使用 Chrome 浏览器中的 Postman 应用程序。
创建一个实体
我们可以向我们的 OData 端点发送 POST 请求,以在我们的数据库中添加一个新产品。以下是我们将 POST 到我们的端点 https://:3010/odata/products 的新产品。
{
"ProductNum": 12,
"Name": "Chai",
"Description": "Bulk packet of Darjeeling Tea",
"ReleaseDate": "1992-12-31T00:00:00Z",
"DiscontinuedDate": null,
"Rating": 1,
"Price": 5.99
}
在下图中,向 OData 端点发送上述 POST 请求后,您可以看到“Status: 201 Created”。请求已成功处理,并创建了一个新资源。在我演示数据库中创建的新产品的 Id 是“5799f7048e68450964afc81f”。我们将在下一节中使用此 Id 来更新新产品。
更新实体
OData 遵循 RESTful API 原则,允许 HTTP 方法 PATCH 和 PUT 执行更新。PATCH 执行部分更新,并且比 PUT 更受推荐。客户端只指定要更新的属性。PUT 替换整个实体,客户端必须为实体中的所有属性发送值,包括未更改的值。
我们将向 OData 端点 https://:3010/odata/products('5799f7048e68450964afc81f') 发送 PATCH 请求,以更改产品的名称和价格。
可以通过访问资源 URI https://:3010/odata/products('5799f7048e68450964afc81f') 在 MongoDB 数据库中查看更改。
请注意,更新和删除请求的 URI 中应传递产品 ID。
删除实体
我们还可以使用 OData 端点通过在 DELETE 请求中传递 ID 来删除数据库中的产品。
如果 DELETE 操作成功,查询 https://:3010/odata/products/$count 应该会减少一个值。
结论、限制和增强思路
在本教程中,我构建了一个基本的 OData 端点,它可以作为 WebAPI 来提供基本的数据操作,例如选择、过滤、排序、top、skip、计数、创建、更新和删除。我使用各种 npm 模块在 MEAN 堆栈上构建了 OData 端点。该端点受到模块支持的功能的限制。好的一点是,对于任何增强功能,您可以将模块替换为自己的或其他符合您要求的第三方模块。https://npmjs.net.cn 上有许多开源模块。对于面向公众的 WebAPI,您可能希望通过启用身份验证和授权来增强 API 的安全性。WebAPI 的安全性本身就是一个话题。如果符合您的要求,您可以使用身份验证包 Passport。由于 OData 端点是基于 REST 的,因此您可以使用 OAuth2、OpenID、bearer token 或任何其他标准来保护 Web 服务。