本文最早发表在Heroku开发中心
MEAN堆栈是由MongoDB , Express , AngularJS和Node.js组成的流行的Web开发堆栈。 MEAN之所以受到欢迎是因为它允许开发人员在客户端和服务器上都使用JavaScript进行编程。 MEAN堆栈实现了JavaScript对象表示法(JSON)开发的完美协调:MongoDB以类似JSON的格式存储数据,Express和Node.js促进了轻松的JSON查询创建,而AngularJS允许客户端无缝地发送和接收JSON文档。
MEAN通常用于创建基于浏览器的Web应用程序,因为AngularJS(客户端)和Express(服务器端)都是Web应用程序的框架。 MEAN的另一个引人注目的用例是RESTful API服务器的开发。 随着应用程序越来越需要优雅地支持各种最终用户设备(例如手机和平板电脑),创建RESTful API服务器已经变得越来越重要,并且成为常见的开发任务。 本教程将演示如何使用MEAN堆栈快速创建RESTful API服务器。
客户端框架AngularJS并不是创建API服务器所必需的组件。 您还可以编写在REST API之上运行的Android或iOS应用程序。 我们在本教程中包括AngularJS,以演示它如何使我们能够快速创建在API服务器之上运行的Web应用程序。
我们将在本教程中开发的应用程序是一个基本的联系人管理应用程序,它支持标准的CRUD (创建,读取,更新,删除)操作。 首先,我们将创建一个RESTful API服务器,以充当查询和持久存储MongoDB数据库中的数据的接口。 然后,我们将利用API服务器来构建基于Angular的Web应用程序,该应用程序为最终用户提供接口。 最后,我们将我们的应用程序部署到Heroku 。
为了使我们能够专注于说明MEAN应用程序的基本结构,我们将故意省略一些通用功能,例如身份验证 ,访问控制和可靠的数据验证。
先决条件
要将应用程序部署到Heroku,您需要一个Heroku帐户 。 如果您以前从未将Node.js应用程序部署到Heroku,建议您在开始之前先阅读一下Heroku上的Node.js入门教程。
另外,请确保在本地计算机上安装了以下软件:
源代码结构
该项目的源代码可从GitHub上的https://github.com/sitepoint-editors/mean-contactlist获得 。 该存储库包含:
-
package.json
—一个配置文件,其中包含有关您的应用程序的元数据。 当此文件出现在项目的根目录中时,Heroku将使用Node.js buildpack。 -
app.json
用于描述Web应用程序的清单格式。 它声明了环境变量,加载项以及在Heroku上运行应用程序所需的其他信息。 需要创建一个“ Deploy to Heroku”按钮。 -
server.js
—该文件包含我们所有的服务器端代码,这些代码实现了REST API。 它使用Express框架和MongoDB Node.js驱动程序以Node.js编写。 -
/public
目录-此目录包含所有客户端文件,其中包括AngularJS代码。
查看示例应用程序运行
要查看本教程将创建的应用程序的运行版本,您可以在此处查看我们的运行示例: https : //sleepy-citadel-45065.herokuapp.com/
现在,让我们逐步学习本教程。
创建一个新的应用程序
为您的应用程序创建一个新目录,并使用cd
命令导航到该目录。 在此目录中,我们将在Heroku上创建一个应用程序,该应用程序将为Heroku准备接收您的源代码。 我们将使用Heroku CLI入门。
$ git init
Initialized empty Git repository in /path/.git/
$ heroku create
Creating app... done, stack is cedar-14
https://sleepy-citadel-45065.herokuapp.com/ | https://git.heroku.com/sleepy-citadel-45065.git
创建应用程序时,还将创建一个git远程(称为heroku)并将其与本地git存储库相关联。 Heroku还会为您的应用程序生成一个随机名称(在本例中为sleepy-citadel-45065)。
Heroku通过在根目录中存在package.json
文件将应用识别为Node.js。 创建一个名为package.json
的文件,并将以下内容复制到其中:
{
"name": "MEAN",
"version": "1.0.0",
"description": "A MEAN app that allows users to manage contact lists",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"dependencies": {
"body-parser": "^1.13.3",
"express": "^4.13.3",
"mongodb": "^2.1.6"
}
}
package.json
文件确定将用于在Heroku上运行应用程序的Node.js的版本,以及应随应用程序安装的依赖项。 部署应用程序后,Heroku会读取该文件,并使用npm install
命令安装相应的Node.js版本以及相关性。
要准备系统以在本地运行该应用程序,请在本地目录中运行以下命令以安装依赖项:
$ npm install
安装依赖项后,您就可以在本地运行应用程序了。
设置MongoDB数据库
设置应用程序和文件目录后,创建一个MongoDB实例来持久化应用程序的数据。 我们将使用mlab托管数据库(一项完全托管的MongoDB服务)轻松配置新的MongoDB数据库:
- 注册一个免费的mLab帐户 。
- 在US EAST中创建一个新的单节点Sandbox MongoDB数据库 。
- 现在,您应该在帐户中看到一个mLab Sandbox数据库。
- 单击刚创建的数据库。
- 单击通知您创建用户的通知。
- 输入用户名和密码
创建mLab数据库时,将为您提供MongoDB 连接字符串 。 该字符串包含访问数据库的凭据,因此,最佳做法是将值存储在config变量中。 让我们继续并将连接字符串存储在名为MONGODB_URI
的配置MONGODB_URI
:
heroku config:set MONGODB_URI=mongodb://your-user:your-pass@host:port/db-name
您可以在Node.js中以process.env.MONGODB_URI
访问此变量,我们将在以后进行操作。
现在我们的数据库已经准备就绪,我们可以开始编码了。
使用Node.js驱动程序连接MongoDB和App Server
Node.js开发人员使用两种流行的MongoDB驱动程序:官方的Node.js驱动程序和一个名为Mongoose的对象文档映射器 ,该映射器包装了Node.js驱动程序(类似于SQL ORM)。 两者都有其优势,但是在本示例中,我们将使用官方的Node.js驱动程序。
创建一个名为server.js
的文件。 在此文件中,我们将创建一个新的Express应用程序并连接到我们的mLab数据库。
var express = require("express");
var path = require("path");
var bodyParser = require("body-parser");
var mongodb = require("mongodb");
var ObjectID = mongodb.ObjectID;
var CONTACTS_COLLECTION = "contacts";
var app = express();
app.use(express.static(__dirname + "/public"));
app.use(bodyParser.json());
// Create a database variable outside of the database connection callback to reuse the connection pool in your app.
var db;
// Connect to the database before starting the application server.
mongodb.MongoClient.connect(process.env.MONGODB_URI, function (err, database) {
if (err) {
console.log(err);
process.exit(1);
}
// Save database object from the callback for reuse.
db = database;
console.log("Database connection ready");
// Initialize the app.
var server = app.listen(process.env.PORT || 8080, function () {
var port = server.address().port;
console.log("App now running on port", port);
});
});
// CONTACTS API ROUTES BELOW
关于连接数据库,需要注意以下几点:
- 我们希望尽可能多地使用数据库连接池 ,以最好地管理可用资源。 我们在全局范围内初始化
db
变量,以便所有路由处理程序都可以使用该连接。 - 我们仅在数据库连接就绪后才初始化应用程序。 这样可以确保在建立连接之前,通过尝试数据库操作,应用程序不会崩溃或出错。
现在我们的应用程序和数据库已连接。 接下来,我们将通过首先定义所有端点来实现RESTful API服务器。
使用Node.js和Express创建RESTful API服务器
创建API的第一步是定义要公开的端点(或数据)。 我们的联系人列表应用程序将允许用户对其联系人执行CRUD操作。
我们需要的端点是:
/联系人
方法 | 描述 |
---|---|
得到 | 查找所有联系人 |
开机自检 | 建立新联络人 |
/ contacts /:id
方法 | 描述 |
---|---|
得到 | 通过ID查找单个联系人 |
放 | 更新整个联系人文档 |
删除 | 通过ID删除联系人 |
现在,将路由添加到我们的server.js
文件中:
// CONTACTS API ROUTES BELOW
// Generic error handler used by all endpoints.
function handleError(res, reason, message, code) {
console.log("ERROR: " + reason);
res.status(code || 500).json({"error": message});
}
/* "/contacts"
* GET: finds all contacts
* POST: creates a new contact
*/
app.get("/contacts", function(req, res) {
});
app.post("/contacts", function(req, res) {
});
/* "/contacts/:id"
* GET: find contact by id
* PUT: update contact by id
* DELETE: deletes contact by id
*/
app.get("/contacts/:id", function(req, res) {
});
app.put("/contacts/:id", function(req, res) {
});
app.delete("/contacts/:id", function(req, res) {
});
该代码为上面定义的所有API端点创建框架。
实施API端点
接下来,我们将添加数据库逻辑以正确实现这些端点。
我们将首先为/contacts
实现POST端点,这将使我们能够创建新的联系人并将其保存到数据库。 每个联系人将具有以下架构:
{
"_id": <ObjectId>
"firstName": <string>,
"lastName": <string>,
"email": <string>,
"phoneNumbers": {
"mobile": <string>,
"work": <string>
},
"twitterHandle": <string>,
"addresses": {
"home": <string>,
"work": <string>
}
}
以下代码实现了/contacts
POST请求:
app.post("/contacts", function(req, res) {
var newContact = req.body;
newContact.createDate = new Date();
if (!(req.body.firstName || req.body.lastName)) {
handleError(res, "Invalid user input", "Must provide a first or last name.", 400);
}
db.collection(CONTACTS_COLLECTION).insertOne(newContact, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to create new contact.");
} else {
res.status(201).json(doc.ops[0]);
}
});
});
要测试POST实施,请部署代码:
$ git add package.json
$ git add server.js
$ git commit -m 'first commit'
$ git push heroku master
现在已部署该应用程序。 确保至少有一个应用程序实例正在运行:
$ heroku ps:scale web=1
然后,使用cURL发出POST请求:
curl -H "Content-Type: application/json" -d '{"firstName":"Chris", "lastName": "Chang", "email": "support@mlab.com"}' http://your-app-name.herokuapp.com/contacts
我们尚未创建Web应用程序,但是您可以通过访问mLab 管理门户确认数据已成功保存到数据库。 您的新联系人应显示在“联系人”集合中。
或者,您可以访问https://mlab.com/databases/your-db-name/collections/contacts
并在那里观察新联系人。
这是server.js
文件的最终版本,该文件实现了所有端点:
var express = require("express");
var path = require("path");
var bodyParser = require("body-parser");
var mongodb = require("mongodb");
var ObjectID = mongodb.ObjectID;
var CONTACTS_COLLECTION = "contacts";
var app = express();
app.use(express.static(__dirname + "/public"));
app.use(bodyParser.json());
// Create a database variable outside of the database connection callback to reuse the connection pool in your app.
var db;
// Connect to the database before starting the application server.
mongodb.MongoClient.connect(process.env.MONGODB_URI, function (err, database) {
if (err) {
console.log(err);
process.exit(1);
}
// Save database object from the callback for reuse.
db = database;
console.log("Database connection ready");
// Initialize the app.
var server = app.listen(process.env.PORT || 8080, function () {
var port = server.address().port;
console.log("App now running on port", port);
});
});
// CONTACTS API ROUTES BELOW
// Generic error handler used by all endpoints.
function handleError(res, reason, message, code) {
console.log("ERROR: " + reason);
res.status(code || 500).json({"error": message});
}
/* "/contacts"
* GET: finds all contacts
* POST: creates a new contact
*/
app.get("/contacts", function(req, res) {
db.collection(CONTACTS_COLLECTION).find({}).toArray(function(err, docs) {
if (err) {
handleError(res, err.message, "Failed to get contacts.");
} else {
res.status(200).json(docs);
}
});
});
app.post("/contacts", function(req, res) {
var newContact = req.body;
newContact.createDate = new Date();
if (!(req.body.firstName || req.body.lastName)) {
handleError(res, "Invalid user input", "Must provide a first or last name.", 400);
}
db.collection(CONTACTS_COLLECTION).insertOne(newContact, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to create new contact.");
} else {
res.status(201).json(doc.ops[0]);
}
});
});
/* "/contacts/:id"
* GET: find contact by id
* PUT: update contact by id
* DELETE: deletes contact by id
*/
app.get("/contacts/:id", function(req, res) {
db.collection(CONTACTS_COLLECTION).findOne({ _id: new ObjectID(req.params.id) }, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to get contact");
} else {
res.status(200).json(doc);
}
});
});
app.put("/contacts/:id", function(req, res) {
var updateDoc = req.body;
delete updateDoc._id;
db.collection(CONTACTS_COLLECTION).updateOne({_id: new ObjectID(req.params.id)}, updateDoc, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to update contact");
} else {
res.status(204).end();
}
});
});
app.delete("/contacts/:id", function(req, res) {
db.collection(CONTACTS_COLLECTION).deleteOne({_id: new ObjectID(req.params.id)}, function(err, result) {
if (err) {
handleError(res, err.message, "Failed to delete contact");
} else {
res.status(204).end();
}
});
});
设置Web应用程序的静态文件
现在我们的API已经完成,我们将使用它来创建我们的Web应用程序。 该网络应用程序允许用户通过浏览器管理联系人。
建立public
在项目的根目录文件夹和文件从示例应用程序的拷贝过来公用文件夹 。 该文件夹包括HTML模板和我们的AngularJS代码。
浏览HTML文件时,您可能会注意到其中存在一些非常规的HTML代码,例如index.html文件中的“ ng-view”:
<div class="container" ng-view>
这些扩展是AngularJS模板系统的功能。 模板使我们能够重用代码并为最终用户动态生成视图。
使用AngularJS构建Web应用
我们将使用AngularJS将所有内容捆绑在一起。 AngularJS将帮助我们路由用户请求,呈现不同的视图以及与数据库之间发送数据。
我们的AngularJS代码位于app.js
文件的/public/js
文件夹中。 为简化起见,我们将仅关注请求默认首页路由( /
)时检索和显示联系人所需的代码。 要实现此功能,我们需要:
- 使用AngularJS routeProvider (
index.html
和list.html
)渲染适当的视图和模板。 - 使用AngularJS 服务 (GET
/contacts
)从数据库中获取/contacts
。 - 使用AngularJS 控制器 (
ListController
)将数据从服务传递到视图。
该代码如下所示:
angular.module("contactsApp", ['ngRoute'])
.config(function($routeProvider) {
$routeProvider
.when("/", {
templateUrl: "list.html",
controller: "ListController",
resolve: {
contacts: function(Contacts) {
return Contacts.getContacts();
}
}
})
})
.service("Contacts", function($http) {
this.getContacts = function() {
return $http.get("/contacts").
then(function(response) {
return response;
}, function(response) {
alert("Error retrieving contacts.");
});
}
})
.controller("ListController", function(contacts, $scope) {
$scope.contacts = contacts.data;
});
接下来,我们将介绍代码的每个部分及其作用。
使用AngularJS routeProvider路由用户请求
routeProvider
模块可帮助我们在AngularJS中配置路由。
angular.module("contactsApp", ['ngRoute'])
.config(function($routeProvider) {
$routeProvider
.when("/", {
templateUrl: "list.html",
controller: "ListController",
resolve: {
contacts: function(Contacts) {
return Contacts.getContacts();
}
}
})
})
主页路由包含一些组件:
-
templateUrl
,它指定要显示的模板 -
Contacts
服务,该服务从API服务器请求所有联系人 -
ListController
,它允许我们将数据添加到范围并从视图中访问它。
使用AngularJS服务向API服务器发出请求
AngularJS服务生成一个对象,该对象可被应用程序的其余部分使用。 我们的服务充当我们所有API端点的客户端包装。
主页路由使用getContacts
函数来请求联系人数据。
.service("Contacts", function($http) {
this.getContacts = function() {
return $http.get("/contacts").
then(function(response) {
return response;
}, function(response) {
alert("Error retrieving contacts.");
});
}
我们的服务功能利用内置的AngularJS $http
服务生成HTTP请求。 该模块还返回一个Promise,您可以对其进行修改以添加其他功能(例如日志记录)。
请注意,在$http
服务中,我们使用相对URL路径(例如/contacts
),而不是绝对路径,例如app-name.herokuapp.com/contacts
。
使用AngularJS控制器扩大我们的范围
到目前为止,我们已经配置了路线,定义了要显示的模板,并使用“ Contacts
服务检索了数据。 为了将所有内容捆绑在一起,我们将创建一个控制器。
.controller("ListController", function(contacts, $scope) {
$scope.contacts = contacts.data;
})
我们的控制器将来自我们服务的联系人数据作为名为contacts
的变量添加到主页范围。 这使我们可以直接从模板( list.html
)访问数据。 我们可以使用AngularJS内置的ngRepeat指令遍历联系人数据:
<div class="container">
<table class="table table-hover">
<tbody>
<tr ng-repeat="contact in contacts | orderBy:'lastName'" style="cursor:pointer">
<td>
<a ng-href="#/contact/{{contact._id}}">{{ contact.firstName }} {{ contact.lastName }}</a>
</td>
</tr>
</tbody>
</table>
</div>
完成项目
现在我们已经了解了如何在AngularJS中实现主页路由,其余Web应用程序路由的实现可以在源项目的/public/js/app.js文件中找到 。 它们都需要routeProvider
的路由定义,一个或多个服务函数来发出适当的HTTP请求,以及一个控制器来扩大范围。
完成Angular代码后,再次部署应用程序:
$ git add server.js
$ git add public
$ git commit -m 'second commit'
$ git push heroku master
现在,Web应用程序组件已完成,您可以通过从CLI打开网站来查看您的应用程序:
$ heroku open
摘要
在本教程中,您学习了如何:
- 在Express和Node.js中创建RESTful API服务器。
- 将MongoDB数据库连接到API服务器以查询和持久化数据。
- 使用AngularJS创建一个富Web应用程序。
我们希望您已经看到MEAN堆栈的功能,可以为当今的Web应用程序开发通用组件。
缩放注意事项
如果您在Heroku上运行生产MEAN应用程序,则随着流量的增加和数据大小的增加,您将需要扩展应用程序和数据库。 请参阅优化Node.js应用程序并发性文章,以获取扩展应用程序的最佳实践。 要升级数据库,请参见mLab附加文档 。
可选的后续步骤
正如我们之前提到的,此应用有意省略了您想要包含在实际生产应用中的详细信息。 特别是,我们没有实现用户模型, 用户身份验证或可靠的输入验证。 考虑将这些功能添加为附加练习。 如果您对本教程有任何疑问,请在下面的评论中告诉我们。
From: https://www.sitepoint.com/deploy-rest-api-in-30-mins-mlab-heroku/