Эта статья была впервые опубликована в Центре разработки Heroku
Стек MEAN — это популярный стек веб-разработки, состоящий из MongoDB , Express , AngularJS и Node.js. MEAN приобрел популярность, потому что позволяет разработчикам программировать на JavaScript как на клиенте, так и на сервере. Стек MEAN обеспечивает идеальную гармонию разработки JavaScript Object Notation (JSON): MongoDB хранит данные в JSON-подобном формате, Express и Node.js облегчают создание запросов JSON, а AngularJS позволяет клиенту беспрепятственно отправлять и получать документы JSON.
MEAN обычно используется для создания веб-приложений на основе браузера, поскольку AngularJS (на стороне клиента) и Express (на стороне сервера) являются средами для веб-приложений. Еще одним убедительным примером использования MEAN является разработка RESTful API-серверов. Создание серверов RESTful API становится все более важной и распространенной задачей разработки, поскольку приложениям все чаще требуется изящная поддержка различных устройств конечного пользователя, таких как мобильные телефоны и планшеты. В этом руководстве показано, как использовать MEAN-стек для быстрого создания RESTful-сервера API.
AngularJS, клиентская среда, не является необходимым компонентом для создания сервера API. Вы также можете написать приложение для Android или iOS, которое работает поверх REST API. Мы включили AngularJS в этот учебник, чтобы продемонстрировать, как он позволяет нам быстро создавать веб-приложение, работающее поверх сервера API.
Приложение, которое мы разработаем в этом руководстве, является базовым приложением для управления контактами, которое поддерживает стандартные операции CRUD (создание, чтение, обновление, удаление). Сначала мы создадим сервер API RESTful, который будет служить интерфейсом для запросов и сохранения данных в базе данных MongoDB. Затем мы используем сервер API для создания веб-приложения на основе Angular, предоставляющего интерфейс для конечных пользователей. Наконец, мы развернем наше приложение на Heroku .
Чтобы мы могли сосредоточиться на иллюстрации фундаментальной структуры MEAN-приложения, мы намеренно опустим общие функции, такие как аутентификация , контроль доступа и надежная проверка данных.
Предпосылки
Для развертывания приложения в Heroku вам потребуется учетная запись Heroku . Если вы никогда ранее не развертывали приложение Node.js в Heroku, перед началом работы рекомендуем ознакомиться с руководством « Начало работы с Node.js в Heroku» .
Также убедитесь, что на вашем локальном компьютере установлено следующее:
Структура исходного кода
Исходный код этого проекта доступен на GitHub по адресу https://github.com/sitepoint-editors/mean-contactlist . Хранилище содержит:
-
package.json
— файл конфигурации, содержащий метаданные о вашем приложении. Когда этот файл присутствует в корневом каталоге проекта, Heroku будет использовать пакет сборки Node.js. -
app.json
— формат манифеста для описания веб-приложений. Он объявляет переменные среды, надстройки и другую информацию, необходимую для запуска приложения на Heroku. Требуется создать кнопку «Развернуть в Heroku». -
server.js
— этот файл содержит весь наш серверный код, который реализует наш REST API. Он написан на Node.js с использованием среды Express и драйвера MongoDB 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 remote (называемый heroku) также создается и связывается с вашим локальным git-репозиторием. Heroku также генерирует случайное имя (в данном случае sleepy-citadel-45065) для вашего приложения.
Heroku распознает приложение как Node.js по наличию файла package.json
в корневом каталоге. Создайте файл с именем 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
определяет версию Node.js, которая будет использоваться для запуска вашего приложения в Heroku, а также зависимости, которые должны быть установлены с вашим приложением. Когда приложение развернуто, Heroku читает этот файл и устанавливает соответствующую версию Node.js вместе с зависимостями, используя команду npm install
.
Чтобы подготовить систему к локальному запуску приложения, выполните эту команду в локальном каталоге, чтобы установить зависимости:
$ npm install
После установки зависимостей вы будете готовы запустить ваше приложение локально.
Предоставление базы данных MongoDB
После настройки приложения и каталога файлов создайте экземпляр MongoDB для сохранения данных вашего приложения. Мы будем использовать размещенную базу данных mLab , полностью управляемую службу MongoDB, чтобы легко подготовить новую базу данных MongoDB:
- Зарегистрируйте бесплатный аккаунт mLab .
- Создайте новую одноузловую базу данных 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
, что мы и сделаем позже.
Теперь, когда наша база данных готова, мы можем начать кодирование.
Подключите MongoDB и сервер приложений с помощью драйвера Node.js
Разработчики 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
в глобальной области видимости, чтобы соединение могло использоваться всеми обработчиками маршрутов. - Мы инициализируем приложение только после того, как соединение с базой данных будет готово. Это гарантирует, что приложение не будет аварийно завершать работу или ошибки при попытке выполнить операции с базой данных до установления соединения.
Теперь наше приложение и база данных связаны. Далее мы реализуем сервер API RESTful, сначала определив все конечные точки.
Создайте RESTful API-сервер с Node.js и Express
В качестве первого шага в создании API мы определяем конечные точки (или данные), которые мы хотим предоставить. Наше приложение списка контактов позволит пользователям выполнять операции CRUD над своими контактами.
Нам понадобятся следующие конечные точки:
/ контакты
метод | Описание |
---|---|
ПОЛУЧИТЬ | Найти все контакты |
ПОЧТА | Создать новый контакт |
/ контакты /: идентификатор
метод | Описание |
---|---|
ПОЛУЧИТЬ | Найти один контакт по идентификатору |
ПОЛОЖИЛ | Обновить весь контактный документ |
УДАЛЯТЬ | Удалить контакт по 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
Далее мы добавим логику базы данных, чтобы правильно реализовать эти конечные точки.
Сначала мы реализуем конечную точку POST для /contacts
, что позволит нам создавать и сохранять новые контакты в базе данных. Каждый контакт будет иметь следующую схему:
{ "_id": <ObjectId> "firstName": <string>, "lastName": <string>, "email": <string>, "phoneNumbers": { "mobile": <string>, "work": <string> }, "twitterHandle": <string>, "addresses": { "home": <string>, "work": <string> } }
Следующий код реализует запрос POST /contacts
:
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": "[email protected]"}' http://your-app-name.herokuapp.com/contacts
Мы еще не создали наше веб-приложение, но вы можете подтвердить, что данные были успешно сохранены в базе данных, посетив портал управления 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(); } }); });
Настройка статических файлов для веб-приложения
Теперь, когда наш API завершен, мы будем использовать его для создания нашего веб-приложения. Веб-приложение позволяет пользователям управлять контактами из браузера.
Создайте public
папку в корневом каталоге вашего проекта и скопируйте файлы из общей папки примера приложения. Папка содержит шаблоны HTML и наш код AngularJS.
Просматривая файлы HTML, вы можете заметить, что в файле index.html есть какой-то нетрадиционный код HTML, например «ng-view»:
<div class="container" ng-view>
Эти расширения являются особенностями системы шаблонов AngularJS. Шаблоны позволяют нам повторно использовать код и динамически генерировать представления для конечного пользователя.
Создайте веб-приложение с AngularJS
Мы будем использовать AngularJS, чтобы связать все вместе. AngularJS поможет нам направлять запросы пользователей, отображать различные представления и отправлять данные в базу данных и из нее.
Наш код AngularJS находится в папке /public/js
в файле app.js
Чтобы упростить задачу, мы сосредоточимся исключительно на коде, который необходим для извлечения и отображения контактов при запросе маршрута по умолчанию для домашней страницы ( /
). Реализация этой функциональности требует, чтобы мы:
- Отобразите соответствующий вид и шаблон, используя AngularJS routeProvider (
index.html
иlist.html
). - Получите контакты из базы данных, используя сервис AngularJS (GET
/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 Services для отправки запросов на сервер 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-запросов. Модуль также возвращает обещание, которое можно изменить, чтобы добавить дополнительные функции (например, ведение журнала).
Обратите внимание, что в сервисе $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, реализацию остальных маршрутов веб-приложения можно найти в файле /public/js/app.js исходного проекта. Все они требуют определения маршрута в routeProvider
, одной или нескольких сервисных функций для выполнения соответствующих HTTP-запросов и контроллера для расширения области действия.
После того, как вы завершили Angular-код, снова разверните приложение:
$ git add server.js $ git add public $ git commit -m 'second commit' $ git push heroku master
Теперь, когда компонент веб-приложения завершен, вы можете просмотреть свое приложение, открыв веб-сайт из интерфейса командной строки:
$ heroku open
Резюме
В этом уроке вы узнали, как:
- создать RESTful API-сервер в Express и Node.js.
- подключите базу данных MongoDB к серверу API для запроса и сохранения данных.
- создать богатое веб-приложение, используя AngularJS.
Мы надеемся, что вы узнали о силе стека MEAN, позволяющей разрабатывать общие компоненты для современных веб-приложений.
Примечания по масштабированию
Если вы работаете с производственным приложением MEAN в Heroku, вам необходимо масштабировать как свое приложение, так и базу данных по мере увеличения трафика и увеличения объема данных. Обратитесь к статье « Оптимизация параллелизма приложений Node.js» для получения рекомендаций по масштабированию приложения. Чтобы обновить базу данных, см. Дополнительную документацию mLab .
Необязательные следующие шаги
Как мы упоминали ранее, это приложение намеренно пропускает детали, которые вы хотели бы включить в реальное производственное приложение. В частности, мы не реализуем модель пользователя, аутентификацию пользователя или надежную проверку ввода. Попробуйте добавить эти функции в качестве дополнительного упражнения. Если у вас есть какие-либо вопросы по поводу этого урока, пожалуйста, сообщите нам об этом в комментариях ниже.