В этой статье мы создадим полный веб-сайт с фронтальной клиентской частью, а также панель управления для управления контентом сайта. Как вы можете догадаться, окончательная рабочая версия приложения содержит много разных файлов. Я написал это руководство шаг за шагом, следуя процессу разработки, но я не включал каждый файл, так как это сделало бы его очень долгим и скучным чтением. Тем не менее, исходный код доступен на GitHub, и я настоятельно рекомендую вам взглянуть.
Вступление
Express — одна из лучших платформ для Node . Он имеет отличную поддержку и кучу полезных функций. Есть много отличных статей, которые охватывают все основы. Однако на этот раз я хочу немного углубиться и поделиться своим рабочим процессом по созданию полноценного веб-сайта. В общем, эта статья предназначена не только для Express, но и для использования ее в сочетании с некоторыми другими замечательными инструментами, доступными для разработчиков Node.
Я предполагаю, что вы знакомы с Nodejs, он установлен в вашей системе, и что вы, вероятно, уже создали некоторые приложения с ним.
В основе Express лежит Connect . Это промежуточная платформа, которая поставляется с множеством полезных вещей. Если вам интересно, что же такое промежуточное программное обеспечение, вот небольшой пример:
var connect = require ('connect'), http = require ('http'); var app = connect () .use (функция (req, res, next) { console.log («Это мое первое промежуточное ПО»); следующий(); }) .use (функция (req, res, next) { console.log («Это мое второе промежуточное программное обеспечение»); следующий(); }) .use (функция (req, res, next) { console.log ( "конец"); res.end ("Привет, мир"); }); http.createServer (приложение) .listen (3000);
Промежуточное программное обеспечение — это в основном функция, которая принимает объекты request
и response
и next
функцию. Каждое промежуточное ПО может решить ответить с помощью объекта response
или передать поток следующей функции, вызвав next
обратный вызов. В приведенном выше примере, если вы удалите вызов метода next()
во втором промежуточном программном обеспечении, строка hello world
никогда не будет отправлена в браузер. В общем, так работает Экспресс. Есть несколько предопределенных промежуточных программ, которые, конечно, сэкономят вам много времени. Как, например, Body parser
который анализирует тела запросов и поддерживает application / json, application / x-www-form-urlencoded и multipart / form-data. Или Cookie parser
, который анализирует заголовки cookie и заполняет req.cookies
объектом, ключом которого является имя cookie.
Express на самом деле оборачивает Connect и добавляет некоторые новые функциональные возможности. Как, например, логика маршрутизации, которая делает процесс намного более плавным. Вот пример обработки запроса GET:
app.get ('/ hello.txt', function (req, res) { var body = 'Hello World'; res.setHeader ('Content-Type', 'text / plain'); res.setHeader ('Content-Length', body.length); res.end (тела); });
Настроить
Есть два способа настроить Express. Во-первых, поместив его в файл package.json
и запустив npm install
(есть шутка, что npm
означает, что нет проблем, человек :)).
{ "name": "MyWebSite", "описание": "Мой сайт", «версия»: «0.0.1», "зависимости": { "экспресс": "3.х" } }
Код фреймворка будет помещен в node_modules
и вы сможете создать его экземпляр. Однако я предпочитаю альтернативный вариант, используя инструмент командной строки. Просто установите Express глобально с помощью npm install -g express
. Благодаря этому у вас теперь есть новый инструмент CLI. Например, если вы запустите:
1
|
express —sessions —css less —hogan app
|
Express создаст скелет приложения с несколькими настройками, уже настроенными для вас. Вот варианты использования для команды express(1)
:
01
02
03
04
05
06
07
08
09
10
|
Usage: express [options]
Options:
-h, —help output usage information
-V, —version output the version number
-s, —sessions add session support
-e, —ejs add ejs engine support (defaults to jade)
-J, —jshtml add jshtml engine support (defaults to jade)
-H, —hogan add hogan.js engine support
-c, —css add stylesheet support (less|stylus) (defaults to plain css)
-f, —force force on non-empty directory
|
Как видите, вариантов всего несколько, но для меня их достаточно. Обычно я использую меньше как препроцессор CSS и hogan как движок шаблонов. В этом примере нам также потребуется поддержка сеанса, поэтому аргумент --sessions
решает эту проблему. Когда приведенная выше команда завершается, наш проект выглядит следующим образом:
01
02
03
04
05
06
07
08
09
10
11
|
/public
/images
/javascripts
/stylesheets
/routes
/index.js
/user.js
/views
/index.hjs
/app.js
/package.json
|
Если вы посмотрите файл package.json
, вы увидите, что все необходимые нам зависимости добавлены сюда. Хотя они еще не были установлены. Для этого просто запустите npm install
после чего node_modules
папка node_modules
.
Я понимаю, что вышеуказанный подход не всегда уместен. Возможно, вы захотите поместить ваши обработчики маршрутов в другой каталог или что-то подобное. Но, как вы увидите в следующих нескольких главах, я внесу изменения в уже сгенерированную структуру, что довольно легко сделать. Так что вы должны просто думать о команде express(1)
как о шаблонном генераторе.
Быстрая доставка
Для этого урока я разработал простой сайт фальшивой компании FastDelivery. Вот скриншот полного дизайна:
В конце этого урока у нас будет полное веб-приложение с работающей панелью управления. Идея состоит в том, чтобы управлять каждой частью сайта в отдельных зонах ограниченного доступа. Макет был создан в Photoshop и нарезан на CSS (меньше) и HTML (хоган) файлы. Теперь я не собираюсь рассказывать о процессе нарезки, потому что это не тема этой статьи, но если у вас есть какие-либо вопросы по этому поводу, не стесняйтесь спрашивать. После нарезки у нас есть следующие файлы и структура приложения:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
/public
/images (there are several images exported from Photoshop)
/javascripts
/stylesheets
/home.less
/inner.less
/style.css
/style.less (imports home.less and inner.less)
/routes
/index.js
/views
/index.hjs (home page)
/inner.hjs (template for every other page of the site)
/app.js
/package.json
|
Вот список элементов сайта, которые мы собираемся администрировать:
- Домой (баннер посередине — заголовок и текст)
- Блог (добавление, удаление и редактирование статей)
- Страница услуг
- Страница карьеры
- Страница контактов
конфигурация
Есть несколько вещей, которые мы должны сделать, прежде чем мы сможем начать реальную реализацию. Настройка конфигурации является одним из них. Давайте представим, что наш маленький сайт должен быть развернут в трех разных местах — локальный сервер, промежуточный сервер и рабочий сервер. Конечно, настройки для каждой среды разные, и мы должны реализовать достаточно гибкий механизм. Как вы знаете, каждый скрипт узла запускается как консольная программа. Таким образом, мы можем легко отправить аргументы командной строки, которые будут определять текущую среду. Я завернул эту часть в отдельный модуль, чтобы позже написать тест для него. Вот файл /config/index.js
:
var config = { местный: { режим: «локальный», порт: 3000 }, постановка: { режим: «постановка», порт: 4000 }, производство: { режим: «производство», порт: 5000 } } module.exports = function (mode) { возврат конфигурации [режим || process.argv [2] || «местный»] || config.local; }
Есть только две настройки (на данный момент) — mode
и port
. Как вы можете догадаться, приложение использует разные порты для разных серверов. Вот почему мы должны обновить точку входа сайта, в app.js
... var config = require ('./ config') (); ... http.createServer (приложение) .listen (config.port, function () { console.log («Экспресс-сервер прослушивает порт» + config.port); });
Для переключения между конфигурациями просто добавьте среду в конце. Например:
1
|
node app.js staging
|
Будет производить:
1
|
Express server listening on port 4000
|
Теперь у нас есть все наши настройки в одном месте, и ими легко управлять.
тесты
Я большой поклонник TDD . Я постараюсь охватить все базовые классы, используемые в этой статье. Конечно, наличие тестов для абсолютно всего сделает эту запись слишком долгой, но в целом именно так вы должны действовать при создании своих собственных приложений. Одна из моих любимых платформ для тестирования — это жасмин . Конечно, это доступно в реестре npm:
1
|
npm install -g jasmine-node
|
Давайте создадим каталог tests
который будет содержать наши тесты. Первое, что мы собираемся проверить, это наша настройка конфигурации. Файлы спецификаций должны заканчиваться на .spec.js
, поэтому файл должен называться config.spec.js
.
описать («Настройка конфигурации», функция () { it («должен загружать локальные конфигурации», function (next) { var config = require ('../ config') (); ожидать (config.mode) .toBe ( 'местный'); следующий(); }); it («должен загружать промежуточные конфигурации», function (next) { var config = require ('../ config') ('staging'); ожидать (config.mode) .toBe ( 'постановка'); следующий(); }); it («должен загружать производственные конфигурации», функция (следующая) { var config = require ('../ config') ('production'); ожидать (config.mode) .toBe ( 'производство'); следующий(); }); });
Запустите jasmine-node ./tests
и вы должны увидеть следующее:
1
2
|
Finished in 0.008 seconds
3 tests, 6 assertions, 0 failures, 0 skipped
|
На этот раз я написал сначала реализацию, а затем тест. Это не совсем способ TDD, но в следующих нескольких главах я сделаю обратное.
Я настоятельно рекомендую тратить много времени на написание тестов. Нет ничего лучше, чем полностью протестированное приложение.
Пару лет назад я осознал кое-что очень важное, что может помочь вам создавать лучшие программы. Каждый раз, когда вы начинаете писать новый класс, новый модуль или просто новую логику, спросите себя:
Как я могу проверить это?
Ответ на этот вопрос поможет вам гораздо эффективнее кодировать, создавать лучшие API и размещать все в красиво разделенных блоках. Вы не можете писать тесты для кода спагетти. Например, в файле конфигурации выше ( /config/index.js
) я добавил возможность отправки mode
в конструктор модуля. Вы можете спросить, почему я делаю это, когда основная идея — получить режим из аргументов командной строки? Это просто … потому что мне нужно было это проверить. Давайте представим, что через месяц мне нужно что-то проверить в production
конфигурации, но скрипт узла запускается с staging
параметром. Я не смогу сделать это изменение без этого небольшого улучшения. Этот предыдущий маленький шаг сейчас фактически предотвращает проблемы в будущем.
База данных
Поскольку мы создаем динамический веб-сайт, нам нужна база данных для хранения наших данных. Я решил использовать mongodb для этого урока. Mongo — это база данных документов NoSQL. Инструкции по установке можно найти здесь, и, поскольку я пользователь Windows, я следовал за установкой Windows . Как только вы закончите установку, запустите демон MongoDB, который по умолчанию прослушивает порт 27017. Таким образом, теоретически мы должны иметь возможность подключиться к этому порту и установить связь с сервером mongodb. Чтобы сделать это из скрипта узла, нам нужен модуль / драйвер mongodb . Если вы загрузили исходные файлы для этого урока, модуль уже добавлен в файл package.json
. Если нет, просто добавьте "mongodb": "1.3.10"
в ваши зависимости и запустите npm install
.
Далее мы собираемся написать тест, который проверяет, работает ли сервер mongodb. Файл /tests/mongodb.spec.js
:
описать ("MongoDB", функция () { это («работает ли сервер», function (next) { var MongoClient = require ('mongodb'). MongoClient; MongoClient.connect ('mongodb: //127.0.0.1: 27017 / fastdelivery', функция (err, db) { ожидать (ERR) .toBe (нуль); следующий(); }); }); });
Обратный вызов в методе .connect клиента mongodb получает объект db
. Мы будем использовать его позже для управления нашими данными, а это значит, что нам нужен доступ к ним внутри наших моделей. Не стоит создавать новый объект MongoClient
каждый раз, когда нам нужно сделать запрос к базе данных. Вот почему я перенес работу экспресс-сервера в функцию обратного вызова функции connect
:
MongoClient.connect ('mongodb: //127.0.0.1: 27017 / fastdelivery', функция (err, db) { if (err) { console.log ('Извините, сервер dong mongo не работает.'); } еще { var attachDB = function (req, res, next) { req.db = дБ; следующий(); }; http.createServer (приложение) .listen (config.port, function () { console.log («Экспресс-сервер прослушивает порт» + config.port); }); } });
Более того, поскольку у нас есть настройка конфигурации, было бы неплохо разместить там хост и порт mongodb, а затем изменить URL-адрес подключения на:
'mongodb: //' + config.mongo.host + ':' + config.mongo.port + '/ fastdelivery'
Обратите особое внимание на промежуточное ПО: attachDB
, которое я добавил непосредственно перед вызовом функции http.createServer
. Благодаря этому небольшому дополнению мы .db
свойство .db
объекта запроса. Хорошей новостью является то, что мы можем добавить несколько функций во время определения маршрута. Например:
app.get ('/', attachDB, function (req, res, next) { ... })
Итак, с этим Express заранее вызывает attachDB
чтобы добраться до нашего обработчика маршрута. Как только это произойдет, объект запроса будет иметь свойство .db
и мы сможем использовать его для доступа к базе данных.
MVC
Мы все знаем схему MVC . Вопрос в том, как это относится к Express. Более или менее, это вопрос интерпретации. В следующих нескольких главах я создам модули, которые выступают в качестве модели, вида и контроллера.
модель
Модель — это то, что будет обрабатывать данные, которые находятся в нашем приложении. Он должен иметь доступ к объекту db
, возвращенному MongoClient
. В нашей модели также должен быть метод ее расширения, потому что мы можем захотеть создавать модели различных типов. Например, мы могли бы хотеть BlogModel
или ContactsModel
. Поэтому нам нужно написать новую спецификацию: /tests/base.model.spec.js
, чтобы протестировать эти две функциональные возможности. И помните, определяя эти функциональные возможности до того, как мы начнем кодировать реализацию, мы можем гарантировать, что наш модуль будет делать только то, что мы хотим, чтобы он делал.
var Model = require ("../ models / Base"), dbMockup = {}; Описание ("Модели", функция () { it («должен создать новую модель», function (next) { var model = новая модель (dbMockup); ожидать (model.db) .toBeDefined (); ожидать (model.extend) .toBeDefined (); следующий(); }); it («должно быть расширяемым», function (next) { var model = новая модель (dbMockup); var OtherTypeOfModel = model.extend ({ myCustomModelMethod: function () {} }); var model2 = new OtherTypeOfModel (dbMockup); ожидать (model2.db) .toBeDefined (); ожидать (model2.myCustomModelMethod) .toBeDefined (); следующий(); }) });
Вместо реального объекта db
я решил передать объект макета. Это потому, что позже я могу захотеть проверить что-то конкретное, что зависит от информации, поступающей из базы данных. Намного проще будет определить эти данные вручную.
Реализация метода module.exports
немного сложнее, потому что мы должны изменить прототип module.exports
, но все же оставить оригинальный конструктор. К счастью, у нас уже есть хороший тест, который доказывает, что наш код работает. Версия, которая проходит выше, выглядит следующим образом:
module.exports = function (db) { this.db = дБ; }; module.exports.prototype = { extended: function (properties) { var Child = module.exports; Child.prototype = module.exports.prototype; for (введите ключ в свойствах) { Child.prototype [ключ] = свойства [ключ]; } вернуть ребенка; }, setDB: function (db) { this.db = дБ; }, collection: function () { if (this._collection) возвращает this._collection; return this._collection = this.db.collection ('fastdelivery-content'); } }
Здесь есть два вспомогательных метода. Установщик для объекта db
и получатель для нашей collection
баз данных.
Посмотреть
Представление будет отображать информацию на экране. По сути, представление — это класс, который отправляет ответ браузеру. Express предоставляет короткий способ сделать это:
res.render ('index', {title: 'Express'});
Объект ответа — это оболочка, которая имеет приятный API, облегчающий нашу жизнь. Тем не менее, я бы предпочел создать модуль, который будет инкапсулировать эту функциональность. Каталог views
умолчанию будет изменен на templates
и будет создан новый каталог, в котором будет размещен класс Base
представления. Это небольшое изменение теперь требует другого изменения. Мы должны уведомить Express, что наши файлы шаблонов теперь находятся в другом каталоге:
app.set ('views', __dirname + '/ templates');
Сначала я определю, что мне нужно, напишу тест, а после этого напишу реализацию. Нам нужен модуль, соответствующий следующим правилам:
- Его конструктор должен получить объект ответа и имя шаблона.
- Он должен иметь метод
render
который принимает объект данных. - Это должно быть расширяемым.
Вы можете удивиться, почему я расширяю класс View
. Разве это не просто вызов метода response.render
? На практике есть случаи, когда вы захотите отправить другой заголовок или, возможно, каким-либо образом манипулировать объектом response
. Как, например, обслуживание данных JSON:
Response.ContentType ( 'приложения / JSON'); response.send (JSON.stringify (данные));
Вместо того, чтобы делать это каждый раз, было бы неплохо иметь класс JSONView
класс JSONView
. Или даже класс XMLView
для отправки данных XML в браузер. Просто лучше, если вы создаете большой веб-сайт, чтобы обернуть эти функции, а не копировать один и тот же код снова и снова.
Вот спецификация для /views/Base.js
:
var View = require ("../ views / Base"); описать («Базовый вид», функция () { it («создать и визуализировать новый вид», function (next) { var responseMockup = { render: function (template, data) { ожидать (data.myProperty) .toBe ( 'значение'); ожидать (шаблон) .toBe ( 'шаблонный файл'); следующий(); } } var v = new View (responseMockup, 'template-file'); v.render ({myProperty: 'value'}); }); it («должно быть расширяемым», function (next) { var v = new View (); var OtherView = v.extend ({ render: function (data) { ожидать (data.prop) .toBe ( 'да'); следующий(); } }); var otherViewInstance = new OtherView (); ожидать (otherViewInstance.render) .toBeDefined (); otherViewInstance.render ({prop: 'yes'}); }); });
Чтобы проверить рендеринг, мне нужно было создать макет. В этом случае я создал объект, который имитирует объект ответа Express. Во второй части теста я создал другой класс View, который наследует базовый класс и применяет пользовательский метод рендеринга. Вот класс /views/Base.js
.
module.exports = function (response, template) { this.response = ответ; this.template = template; }; module.exports.prototype = { extended: function (properties) { var Child = module.exports; Child.prototype = module.exports.prototype; for (введите ключ в свойствах) { Child.prototype [ключ] = свойства [ключ]; } вернуть ребенка; }, render: function (data) { if (this.response && this.template) { this.response.render (this.template, data); } } }
Теперь у нас есть три спецификации в нашем каталоге tests
и если вы запустите jasmine-node ./tests
результат должен быть:
1
2
|
Finished in 0.009 seconds
7 tests, 18 assertions, 0 failures, 0 skipped
|
контроллер
Помните маршруты и как они были определены?
app.get ('/', rout.index);
Знак '/'
после маршрута, который в приведенном выше примере на самом деле является контроллером. Это просто функция промежуточного программного обеспечения, которая принимает request
, response
и next
.
exports.index = function (req, res, next) { res.render ('index', {title: 'Express'}); };
Выше, как должен выглядеть ваш контроллер в контексте Express. express(1)
командной строки express(1)
создает каталог с именем routes
, но в нашем случае лучше называть его controllers
, поэтому я изменил его, чтобы отразить эту схему именования.
Поскольку мы не просто создаем маленькое крошечное приложение, было бы разумно, если бы мы создали базовый класс, который мы можем расширить. Если нам когда-нибудь понадобится передать какую-то функциональность всем нашим контроллерам, этот базовый класс будет идеальным местом. Опять же, сначала я напишу тест, поэтому давайте определим, что нам нужно:
- он должен иметь метод
extend
, который принимает объект и возвращает новый дочерний экземпляр - дочерний экземпляр должен иметь метод
run
, который является старой функцией промежуточного программного обеспечения - должно быть свойство
name
, которое идентифицирует контроллер - мы должны иметь возможность создавать независимые объекты, основанные на классе
Так что сейчас несколько вещей, но мы можем добавить больше функциональности позже. Тест будет выглядеть примерно так:
var BaseController = require ("../ controllers / Base"); описать («Базовый контроллер», функция () { it («должен иметь метод extension, который возвращает дочерний экземпляр», function (next) { ожидать (BaseController.extend) .toBeDefined (); var child = BaseController.extend ({имя: «мой дочерний контроллер»}); ожидать (child.run) .toBeDefined (); ожидаем (child.name) .toBe («мой дочерний контроллер»); следующий(); }); it («должен иметь возможность создавать разных потомков», function (next) { var childA = BaseController.extend ({name: "child A", customProperty: 'value'}); var childB = BaseController.extend ({name: "child B"}); ожидать (childA.name) .not.toBe (childB.name); ожидать (childB.customProperty) .not.toBeDefined (); следующий(); }); });
А вот и реализация /controllers/Base.js
:
var _ = require ("underscore"); module.exports = { название: "база", extend: function (child) { return _.extend ({}, this, child); }, run: function (req, res, next) { } }
Конечно, каждый дочерний класс должен определять свой собственный метод run
вместе со своей собственной логикой.
FastDelivery Сайт
Хорошо, у нас есть хороший набор классов для нашей архитектуры MVC, и мы покрыли наши недавно созданные модули тестами. Теперь мы готовы продолжить работу с сайтом нашей фальшивой компании FastDelivery
. Давайте представим, что сайт состоит из двух частей — интерфейсной панели и панели администрирования. Внешний интерфейс будет использоваться для отображения информации, записанной в базе данных, нашим конечным пользователям. Панель администратора будет использоваться для управления этими данными. Давайте начнем с нашей панели администратора (управления).
Панель управления
Давайте сначала создадим простой контроллер, который будет служить страницей администрирования. Файл /controllers/Admin.js
:
var BaseController = require ("./ Base"), View = require ("../ views / Base"); module.exports = BaseController.extend ({ имя: "Админ", run: function (req, res, next) { var v = новый вид (res, 'admin'); v.render ({ название: «Администрация», содержание: «Добро пожаловать в панель управления» }); } });
Используя предварительно написанные базовые классы для наших контроллеров и представлений, мы можем легко создать точку входа для панели управления. Класс View
принимает имя файла шаблона. Согласно приведенному выше коду файл должен называться admin.hjs
и должен быть помещен в /templates
. Содержание будет выглядеть примерно так:
01
02
03
04
05
06
07
08
09
10
11
12
|
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<link rel=’stylesheet’ href=’/stylesheets/style.css’ />
</head>
<body>
<div class=»container»>
<h1>{{ content }}</h1>
</div>
</body>
</html>
|
(Чтобы этот учебник был достаточно коротким и в удобном для чтения формате, я не буду показывать каждый шаблон представления. Я настоятельно рекомендую вам загрузить исходный код с GitHub.)
Теперь, чтобы сделать контроллер видимым, мы должны добавить маршрут к нему в app.js
:
var Admin = require ('./ controllers / Admin'); ... var attachDB = function (req, res, next) { req.db = дБ; следующий(); }; ... app.all ('/ admin *', attachDB, function (req, res, next) { Admin.run (req, res, next); });
Обратите внимание, что мы не отправляем метод Admin.run
напрямую как промежуточное ПО. Это потому, что мы хотим сохранить контекст. Если мы сделаем это:
app.all ('/ admin *', Admin.run);
слово this
в Admin
будет указывать на что-то еще.
Защита панели администрирования
Каждая страница, которая начинается с /admin
должна быть защищена. Для этого мы будем использовать промежуточное программное обеспечение Express: Sessions . Он просто присоединяет объект к запросу, называемому session
. Теперь мы должны изменить наш контроллер администратора, чтобы сделать две дополнительные вещи:
- Следует проверить, есть ли доступный сеанс. Если нет, то отобразите форму входа.
- Он должен принять данные, отправленные формой входа в систему и авторизовать пользователя, если имя пользователя и пароль совпадают.
Вот небольшая вспомогательная функция, которую мы можем использовать для достижения этой цели:
authorize: function (req) { возвращение ( требование && req.session.fastdelivery && req.session.fastdelivery === правда ) || ( req.body && req.body.username === this.username && req.body.password === this.password ); }
Во-первых, у нас есть оператор, который пытается распознать пользователя через объект сеанса. Во-вторых, мы проверяем, была ли отправлена форма. Если это так, данные из формы доступны в объекте request.body
который заполняется промежуточным программным обеспечением bodyParser
. Затем мы просто проверяем, совпадают ли имя пользователя и пароль.
А теперь вот метод run
контроллера, который использует наш новый помощник. Мы проверяем, авторизован ли пользователь, отображая либо саму панель управления, либо мы отображаем страницу входа:
run: function (req, res, next) { if (this.authorize (req)) { req.session.fastdelivery = true; req.session.save (function (err) { var v = новый вид (res, 'admin'); v.render ({ название: «Администрация», содержание: «Добро пожаловать в панель управления» }); }); } еще { var v = новый вид (res, 'admin-login'); v.render ({ title: 'Пожалуйста, войдите' }); } }
Управление контентом
Как я указывал в начале этой статьи, у нас есть много вещей для администрирования. Чтобы упростить процесс, давайте хранить все данные в одной коллекции. Каждая запись будет иметь title
, text
, picture
и type
свойства. Свойство type
будет определять владельца записи. Например, странице «Контакты» потребуется только одна запись с type: 'contacts'
, а странице блога потребуется больше записей. Итак, нам нужны три новые страницы для добавления, редактирования и отображения записей. Прежде чем мы перейдем к созданию новых шаблонов, стилей и добавлению новых элементов в контроллер, мы должны написать наш класс модели, который находится между сервером MongoDB и нашим приложением и, конечно, предоставляет значимый API.
// /models/ContentModel.js var Model = require ("./ Base"), crypto = require ("crypto"), модель = новая модель (); var ContentModel = model.extend ({ insert: function (data, callback) { data.ID = crypto.randomBytes (20) .toString ('hex'); this.collection (). insert (data, {}, callback || function () {}); }, update: function (data, callback) { this.collection (). update ({ID: data.ID}, данные, {}, обратный вызов || function () {}); }, getlist: function (callback, query) { this.collection (). find (query || {}). toArray (callback); }, удалить: функция (идентификатор, обратный вызов) { this.collection (). findAndModify ({ID: ID}, [], {}, {remove: true}, обратный вызов); } }); module.exports = ContentModel;
Модель заботится о создании уникального идентификатора для каждой записи. Нам понадобится, чтобы позже обновить информацию.
Если мы хотим добавить новую запись для нашей страницы контактов, мы можем просто использовать:
var model = new (require ("../ models / ContentModel")); model.insert ({ название: «Контакты», текст: "...", тип: "контакты" });
Итак, у нас есть хороший API для управления данными в нашей коллекции mongodb. Теперь мы готовы написать пользовательский интерфейс для использования этой функциональности. Для этой части контроллер Admin
нужно будет немного изменить. Чтобы упростить задачу, я решил объединить список добавленных записей и форму для их добавления / редактирования. Как вы можете видеть на скриншоте ниже, левая часть страницы зарезервирована для списка, а правая часть для формы.
Наличие всего на одной странице означает, что мы должны сосредоточиться на части, которая отображает страницу, или, если быть более точным, на данных, которые мы отправляем в шаблон. Вот почему я создал несколько вспомогательных функций, которые объединяются, например:
var self = this; ... var v = новый вид (res, 'admin'); self.del (req, function () { self.form (req, res, function (formMarkup) { self.list (function (listMarkup) { v.render ({ название: «Администрация», содержание: «Добро пожаловать в панель управления», list: listMarkup, form: formMarkup }); }); }); });
Это выглядит немного некрасиво, но работает так, как я хотел. Первый помощник — это метод del
который проверяет текущие параметры GET и, если он находит action=delete&id=[id of the record]
, он удаляет данные из коллекции. Вторая функция называется form
и отвечает главным образом за отображение формы в правой части страницы. Он проверяет, отправлена ли форма и правильно ли обновляет или создает записи в базе данных. В конце метод list
извлекает информацию и подготавливает таблицу HTML, которая затем отправляется в шаблон. Реализация этих трех помощников можно найти в исходном коде этого руководства.
Здесь я решил показать вам функцию, которая обрабатывает загрузку файла:
handleFileUpload: function (req) { if (! req.files ||! req.files.picture ||! req.files.picture.name) { return req.body.currentPicture || ''; } var data = fs.readFileSync (req.files.picture.path); var fileName = req.files.picture.name; var uid = crypto.randomBytes (10) .toString ('hex'); var dir = __dirname + "/../public/uploads/" + uid; fs.mkdirSync (dir, '0777'); fs.writeFileSync (dir + "/" + fileName, data); return '/ uploads /' + uid + "/" + fileName; }
Если файл отправлен, свойство .files
сценария .files
объекта запроса заполняется данными. В нашем случае у нас есть следующий HTML-элемент:
1
|
<input type=»file» name=»picture» />
|
Это означает, что мы можем получить доступ к отправленным данным через req.files.picture
. В приведенном выше фрагменте кода req.files.picture.path
используется для получения необработанного содержимого файла. Позже те же данные записываются во вновь созданный каталог, и в конце возвращается правильный URL. Все эти операции являются синхронными, но рекомендуется использовать асинхронную версию readFileSync
, mkdirSync
и writeFileSync
.
Внешний интерфейс
Тяжелая работа теперь завершена. Панель администрирования работает, и у нас есть класс ContentModel
, который дает нам доступ к информации, хранящейся в базе данных. Теперь нам нужно написать внешние контроллеры и связать их с сохраненным содержимым.
Вот контроллер для домашней страницы — /controllers/Home.js
module.exports = BaseController.extend ({ название: "Дом", содержание: ноль, run: function (req, res, next) { model.setDB (req.db); var self = this; this.getContent (function () { var v = новый вид (res, 'home'); v.render (self.content); }) }, getContent: function (callback) { var self = this; this.content = {}; model.getlist (function (err, records) { ... сохранение данных в объекте содержимого model.getlist (function (err, records) { ... сохранение данных в объекте содержимого Перезвони(); }, {type: 'blog'}); }, {type: 'home'}); } });
Домашней странице нужна одна запись с типом домашней страницы и четыре записи с типом blog
. После завершения работы контроллера нам просто нужно добавить маршрут к нему в app.js
:
app.all ('/', attachDB, function (req, res, next) { Home.run (req, res, next); });
Опять же, мы присоединяем объект db
к request
. Практически тот же рабочий процесс, что и в панели администрирования.
Другие страницы для нашего интерфейса (на стороне клиента) практически идентичны, так как все они имеют контроллер, который извлекает данные с использованием класса модели и, конечно, определенного маршрута. Есть две интересные ситуации, которые я хотел бы объяснить более подробно. Первый связан со страницей блога. Нужно уметь показывать все статьи, а также представлять только одну. Итак, нам нужно зарегистрировать два маршрута:
app.all ('/ blog /: id', attachDB, function (req, res, next) { Blog.runArticle (req, res, next); }); app.all ('/ blog', attachDB, function (req, res, next) { Blog.run (req, res, next); });
Они оба используют один и тот же контроллер: Blog
, но вызывают разные методы run
. Обратите внимание на строку /blog/:id
. Этот маршрут будет соответствовать URL-адресам, таким как /blog/4e3455635b4a6f6dccfaa1e50ee71f1cde75222b
и длинный хеш будет доступен в req.params.id
. Другими словами, мы можем определить динамические параметры. В нашем случае это идентификатор записи. Получив эту информацию, мы можем создать уникальную страницу для каждой статьи.
Вторая интересная часть — это то, как я создал страницы «Услуги», «Карьера» и «Контакты». Понятно, что они используют только одну запись из базы данных. Если бы нам пришлось создавать разные контроллеры для каждой страницы, нам пришлось бы копировать / вставлять один и тот же код и просто менять поле type
. Есть лучший способ добиться этого, имея только один контроллер, который принимает type
в своем методе run
. Итак, вот маршруты:
app.all ('/ services', attachDB, function (req, res, next) { Page.run ('services', req, res, next); }); app.all ('/ careers', attachDB, function (req, res, next) { Page.run («Карьера», req, res, далее); }); app.all ('/ contacts', attachDB, function (req, res, next) { Page.run («контакты», req, res, далее); });
И контроллер будет выглядеть так:
module.exports = BaseController.extend ({ название: «Страница», содержание: ноль, run: function (type, req, res, next) { model.setDB (req.db); var self = this; this.getContent (type, function () { var v = new View (res, 'inner'); v.render (self.content); }); }, getContent: function (type, callback) { var self = this; this.content = {} model.getlist (function (err, records) { if (records.length> 0) { self.content = records [0]; } Перезвони(); }, {type: type}); } });
развертывание
Развертывание веб-сайта на основе Express на самом деле аналогично развертыванию любого другого приложения Node.js:
- Файлы размещены на сервере.
- Процесс узла должен быть остановлен (если он запущен).
- Команда
npm install
должна быть запущена для установки новых зависимостей (если есть). - Основной сценарий должен быть запущен снова.
Имейте в виду, что Node все еще довольно молод, поэтому не все может работать так, как вы ожидали, но есть улучшения, которые делаются постоянно. Например, навсегда гарантирует, что ваша программа Nodejs будет работать непрерывно. Вы можете сделать это, выполнив следующую команду:
1
|
forever start yourapp.js
|
Это то, что я использую на своих серверах. Это хороший маленький инструмент, но он решает большую проблему. Если вы запускаете свое приложение только с помощью node yourapp.js
, то, как только ваш скрипт неожиданно завершает работу, сервер node yourapp.js
. forever
, просто перезапускает приложение.
Сейчас я не системный администратор, но я хотел поделиться своим опытом интеграции приложений для узлов с Apache или Nginx , потому что я думаю, что это как-то является частью рабочего процесса разработки.
Как вы знаете, Apache обычно работает на порту 80, что означает, что если вы откроете http://localhost
или http://localhost:80
увидите страницу, обслуживаемую вашим сервером Apache, и, скорее всего, ваш скрипт узла прослушивает другой порт. Итак, вам нужно добавить виртуальный хост, который принимает запросы и отправляет их на нужный порт. Например, предположим, что я хочу разместить сайт, который мы только что создали, на моем локальном сервере Apache под expresscompletewebsite.dev
адресом. Первое, что нам нужно сделать, это добавить наш домен в hosts
файл.
1
|
127.0.0.1 expresscompletewebsite.dev |
После этого мы должны отредактировать httpd-vhosts.conf
файл в каталоге конфигурации Apache и добавить
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
# expresscompletewebsite.dev <VirtualHost *:80> ServerName expresscompletewebsite.dev ServerAlias www.expresscompletewebsite.dev ProxyRequests off <Proxy *> Order deny,allow Allow from all </Proxy> <Location /> ProxyPass http://localhost:3000/ ProxyPassReverse http://localhost:3000/ </Location> </VirtualHost> |
Сервер по-прежнему принимает запросы на порт 80, но перенаправляет их на порт 3000, где узел прослушивает.
Настройка Nginx намного проще и, честно говоря, это лучший выбор для размещения приложений на базе Nodejs. Вам все еще нужно добавить доменное имя в ваш hosts
файл. После этого просто создайте новый файл в /sites-enabled
каталоге под установкой Nginx. Содержимое файла будет выглядеть примерно так:
1
2
3
4
5
6
7
8
|
server {
listen 80;
server_name expresscompletewebsite.dev location / {
proxy_pass http://127.0.0.1:3000; proxy_set_header Host $http_host; }
}
|
Имейте в виду, что вы не можете запустить Apache и Nginx с указанными выше настройками хостов. Это связано с тем, что им обоим требуется порт 80. Кроме того, вы можете провести небольшое дополнительное исследование о лучшей конфигурации сервера, если планируете использовать приведенные выше фрагменты кода в производственной среде. Как я уже сказал, я не эксперт в этой области.
Вывод
Express — это отличный фреймворк, который дает вам хорошую отправную точку для начала разработки ваших приложений. Как видите, вопрос о том, как вы будете расширять его, и что вы будете использовать для его создания, зависит от выбора. Он упрощает скучные задачи благодаря использованию нескольких замечательных промежуточных программ и оставляет разработчикам забавные детали.
Если вы хотите ускорить свою работу по разработке, вы можете найти множество полезных скриптов и приложений для JavaScript на Envato Market.
Исходный код
Исходный код этого примера сайта, который мы создали, доступен на GitHub — https://github.com/tutsplus/build-complete-website-expressjs . Не стесняйтесь раскошелиться и играть с ним. Вот шаги для запуска сайта.
- Скачать исходный код
- Перейти в
app
каталог - Бегать
npm install
- Запустить демон mongodb
- Бегать
node app.js