Вы находитесь на странице: 1из 432

2-е издание

вдеис
Алекс Янг
Брэдли Мек
Майк Кантелон
А также
Тим Оксли
Марк Хартер
Т. Дж. Головайчук
Натан Райлих

I I H A N N IN G ПИТЕР
Node.js in Action
S e c o n d E d it io n

ALEXYOUNG
BRADLEY MECK
MIKE CANTELON
WITH
TIM OXLEY
MARC HARTER
T.J. HOLOWAYCHUK
NATHAN RAJLICH

■1
MANNI NG
S helter Islan d
Алекс Янг, Брэдли Мек, Майк Кантелон
А также Тим Оксли, М арк Хартер, Т. Дж. Головайчук, Натан Райлих

в действии
2-е издание

Санкт-Петербург •Москва •Екатеринбург •Воронеж


Нижний Новгород •Ростов-на-Дону
Самара •Минск
2018
ББК 32.988-02-018
УДК 004.738.5
Я60

Я нг А., М ек Б., Кантелон М.


Я60 Node.js в действии. 2-е изд. — СПб.: Питер, 2018. — 432 с.: ил. — (Серия «Для
профессионалов»).
ISBN 978-5-496-03212-4
Второе издание «Node.js в действии» было полностью переработано, чтобы отражать реалии, с ко­
торыми теперь сталкивается каждый Node-разработчик. Вы узнаете о системах построения интерфейса
и популярных веб-фреймворках Node, а также научитесь строить веб-приложения на базе Express
с нуля. Теперь вы сможете узнать не только о Node и JavaScript, но и получить всю информацию,
включая системы построения фронтэнда, выбор веб-фреймворка, работу с базами данных в Node,
тестирование и развертывание веб-приложений.
Технология Node все чаще используется в сочетании с инструментами командной строки и на­
стольными приложениями на базе Electron, поэтому в книгу были включены главы, посвященные
обеим областям.
16+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 4Э6-ФЗ.)

ББК 32.988-02-018
УДК 004.738.5

Права на издание получены по соглашению с Manning. Все права защищены. Никакая часть данной книги не
может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских
прав.

Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как на­
дежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может
гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные
ошибки, связанные с использованием книги.

ISBN 978-1617292576 англ. © 2017 by Manning Publications Co. All rights reserved
978-5-496-03212-4 © Перевод на русский язык ООО Издательство «Питер», 2018
© Издание на русском языке, оформление ООО Издательство «Питер», 2018
© Серия «Для профессионалов», 2018
Краткое содержание

Часть I. Знакомство с Node......................................................................21


Глава 1. Знакомство с N o d e .js............................................................................................ 22
Глава 2. Основы программирования N o d e .................................................................... 41
Глава 3. Что представляет собой веб-приложение N ode?.......................................... 76

Часть II. Веб-разработка с использованием Node ............................. 93


Глава 4. Системы построения фронтэнда....................................................................... 94
Глава 5. Ф реймворки на стороне сервера...................................................................... 110
Глава 6. Connect и E x p re ss.................................................................................................142
Глава 7. Ш аблонизация веб-приложений..................................................................... 201
Глава 8. Хранение данных в прилож ениях....................................................................228
Глава 9. Тестирование приложений Node...................................................................... 278
Глава 10. Развертывание и обеспечение доступности приложений N ode............308

Часть III. За пределами веб-разработки ............................................325


Глава 11. Написание приложений командной строки ............................................... 326
Глава 12. Разработка настольных приложений с использованием Electron ......339

Приложения............................................................................................. 359
A. Установка Node ............................................................................................................... 360
Б. Автоматизированное извлечение веб-данны х........................................................365
B. Официально поддерживаемые промежуточные ком пон ен ты .......................... 378
Глоссарий....................................................................................................................... 418
Оглавление

Предисловие ....................................................................................................................15
Благодарности ................................................................................................................ 16
О книге.............................................................................................................................. 17
Структура ......................................................................................................................... 17
Правила оформления и загрузка примеров ко д а................................................... 18
Об авторах....................................................................................................................... 19
Алекс Янг ...................................................................................................................19
Брэдли Мек ...............................................................................................................19
Иллюстрация на обложке ........................................................................................... 20
От издательства ............................................................................................................. 20

Часть I. Знакомство с Node......................................................................21


Глава 1. Знакомство с Node .js .................................................................................... 22
1.1. Типичное веб-приложение Node ....................................................................... 22
1.1.1. Неблокирующий ввод/вывод ....................................................................23
1.1.2. Цикл событий ............................................................................................... 25
1.2. ES2015, Node и V8 .................................................................................................. 26
1.2.1. Node и V 8 ........................................................................................................... 29
1.2.2. Работа с функциональными гр у п п ам и .................................................... 30
1.2.3. График выпуска версий Node ....................................................................31
1.3. Установка Node ........................................................................................................ 31
1.4. Встроенные средства N o d e ................................................................................... 32
1.4.1. npm ...................................................................................................................33
1.4.2. Базовые модули ........................................................................................... 34
1.4.3. О тл ад ч и к...........................................................................................................35
1.5. Три основных типа программ N ode.................................................................... 36
1.5.1. Веб-приложения ........................................................................................... 37
1.5.2. Средства командной строки и д е м о н ы .................................................... 38
1.5.3. Настольные приложения ........................................................................... 39
1.5.4. Приложения, хорошо подходящие для Node ........................................ 39
1.6. Заключение ............................................................................................................. 40
Оглавление 7

Глава 2. Основы программирования N o d e ................................................................41


2.1. Структурирование и повторное использование функциональности
N o d e ..................................................................................................................................... 41
2.2. Создание нового проекта Node .......................................................................... 44
2.2.1. Создание модулей........................................................................................... 44
2.3. Настройка создания модуля с использованием m odule.exports................ 47
2.4. Повторное использование модулей с папкой node_m odules..................... 48
2.5. Потенциальные проблемы ................................................................................... 48
2.6. Средства асинхронного программирования ................................................... 51
2.7. Обработка одноразовых событий в обратных вызовах .............................. 52
2.8. Обработка повторяющихся событий с генераторами событий ................ 56
2.8.1. Пример генератора собы тий ........................................................................56
2.8.2. Реакция на событие, которое должно происходить только
один раз .......................................................................................................................58
2.8.3. Создание генераторов событий: п убл и кац и я/п одп и ска.....................58
2.8.4. Доработка генератора событий: отслеживание содержимого
ф ай л о в ...........................................................................................................................62
2.9. Проблемы с асинхронной разработкой ............................................................ 64
2.10. Упорядочение асинхронной логики................................................................. 65
2.11. Когда применяется последовательный поток в ы п о л н ен и я..................... 67
2.12. Реализация последовательного потока вы п о л н ен и я................................. 68
2.13. Реализация параллельного потокавы п олн ен и я.......................................... 71
2.14. Средства, разработанные в сообществе ......................................................... 73
2.15. Заклю чение............................................................................................................. 75

Глава 3 . Что представляет собой веб-приложение N o d e ? .................................... 76


3.1. Структура веб-приложения N o d e....................................................................... 77
3.1.1. Создание нового веб -п ри л ож ен и я............................................................77
3.1.2. Сравнение с другими платформами ........................................................79
3.1.3. Что дальше? ................................................................................................... 79
3.2. Построение R EST-совместимой веб-службы ................................................ 80
3.3. Добавление базы д а н н ы х ...................................................................................... 83
3.3.1. Проектирование собственного API м одели ............................................ 84
3.3.2. Преобразование статей в удобочитаемую форму и их сохранение
для чтения в будущем ........................................................................................... 87
3.4. Добавление пользовательского интерфейса ................................................... 88
3.4.1. Поддержка разных форматов ....................................................................89
3.4.2. Визуализация шаблонов ........................................................................... 89
3.4.3. Использование npm для зависимостей на стороне клиента .............90
3.5. Заключение ............................................................................................................. 92
8 Оглавление

Часть II. Веб-разработка с использованием Node ............................. 93


Глава 4. Системы построения фронтэнда ................................................................94
4.1. Ф ронтэнд-разработка с использованием Node ............................................. 94
4.2. Использование npm для запуска сценариев ................................................... 95
4.2.1. Создание специализированных сценариев n p m .................................... 97
4.2.2. Настройка средств построения ф р о н тэн д а............................................ 98
4.3. Автоматизация с использованием G u lp ............................................................ 98
4.3.1. Добавление Gulp в п р о ек т............................................................................99
4.3.2. Создание и выполнение задач Gulp .................................................... 100
4.3.3. Отслеживание и зм ен ен и й ........................................................................ 102
4.3.4. Использование отдельных файлов в больших проектах................. 102
4.4. Построение веб-приложений с использованием webpack.......................... 104
4.4.1. Пакеты и плагины ....................................................................................... 104
4.4.2. Настройка и запуск w ebpack.................................................................... 105
4.4.3. Использование сервера для разработки w e b p a c k ............................. 106
4.4.4. Загрузка модулей и активов CommonJS ............................................ 107
4.5. Заклю чение............................................................................................................... 109

Глава 5 . Фреймворки на стороне сервера ............................................................ 110


5.1. Персонажи ............................................................................................................... 110
5.1.1. Фил: штатный разработчик .................................................................... 111
5.1.2. Надин: разработчик открытого к о д а .................................................... 111
5.1.3. Элис: разработчик п родукта.................................................................... 112
5.2. Что такое ф рейм ворк?........................................................................................... 112
5.3. K o a .............................................................................................................................. 113
5.3.1. Настройка ................................................................................................... 115
5.3.2. Определение маршрутов ........................................................................ 116
5.3.3. REST A P I....................................................................................................... 116
5.3.4. Сильные сторон ы ....................................................................................... 117
5.3.5. Слабые стороны........................................................................................... 117
5.4. K ra k e n ........................................................................................................................ 117
5.4.1. Настройка ................................................................................................... 118
5.4.2. Определение маршрутов ........................................................................ 118
5.4.3. REST A P I....................................................................................................... 119
5.4.4. Сильные сторон ы ....................................................................................... 119
5.4.5. Слабые стороны........................................................................................... 120
5.5. h a p i.............................................................................................................................. 120
5.5.1. Настройка ................................................................................................... 121
5.5.2. Определение маршрутов ........................................................................ 121
5.5.3. Плагины ....................................................................................................... 122
Оглавление 9

5.5.4. REST A P I....................................................................................................... 123


5.5.5. Сильные сторон ы ....................................................................................... 124
5.5.6. Слабые стороны........................................................................................... 124
5.6. Sails.js ........................................................................................................................ 124
5.6.1. Настройка ................................................................................................... 125
5.6.2. Определение маршрутов ........................................................................ 126
5.6.3. REST A P I....................................................................................................... 126
5.6.4. Сильные сторон ы ....................................................................................... 127
5.6.5. Слабые стороны........................................................................................... 127
5.7. DerbyJS ..................................................................................................................... 127
5.7.1. Настройка ................................................................................................... 128
5.7.2. Определение маршрутов ........................................................................ 129
5.7.3. REST A P I....................................................................................................... 130
5.7.4. Сильные сторон ы ....................................................................................... 130
5.7.5. Слабые стороны........................................................................................... 130
5.8. Flatiron.js .................................................................................................................. 131
5.8.1. Настройка ................................................................................................... 131
5.8.2. Определение маршрутов ........................................................................ 132
5.8.3. REST A P I....................................................................................................... 133
5.8.4. Сильные сторон ы ....................................................................................... 133
5.8.5. Слабые стороны........................................................................................... 134
5.9. LoopBack .................................................................................................................. 134
5.9.1. Настройка ................................................................................................... 135
5.9.2. Определение маршрутов ........................................................................ 137
5.9.3. REST A P I....................................................................................................... 137
5.9.4. Сильные сторон ы ....................................................................................... 138
5.9.5. Слабые стороны........................................................................................... 138
5.10. С равнение............................................................................................................... 138
5.10.1. Серверы H T T P и маршруты ................................................................ 140
5.11. Написание модульного кода ............................................................................ 140
5.12. Выбор персонажей ..............................................................................................141
5.13. Заклю чение............................................................................................................ 141

Глава 6 . Connect и E xpress........................................................................................ 142


6.1. Connect ..................................................................................................................... 142
6.1.1..Настройка приложения C o n n ect............................................................ 143
6.1.2. Как работают промежуточные компоненты Connect ..................... 143
6.1.3. Объединение промежуточных компонентов .................................... 144
6.1.4. Упорядочение ком п он ен тов.................................................................... 145
6.1.5. Создание настраиваемых промежуточных компонентов ............. 146
10 Оглавление

6.1.6. Использование промежуточных компонентов


для обработки ошибок ....................................................................................... 148
6.2. Express........................................................................................................................ 151
6.2.1. Генерирование заготовки п р и л о ж ен и я................................................ 152
6.2.2. Настройка конфигурации Express и п рилож ени я............................. 157
6.2.3. Визуализация представлений ................................................................ 159
6.2.4. Знакомство с маршрутизацией в E xpress............................................ 165
6.2.5. Аутентификация п ользователей ............................................................ 173
6.2.6. Регистрация новых пользователей........................................................ 179
6.2.7. Вход для зарегистрированных пользователей ................................ 185
6.2.8. Промежуточный компонент для загрузки пользовательских
дан ны х....................................................................................................................... 189
6.2.9. Создание открытого REST API ............................................................ 191
6.2.10. Согласование контента............................................................................ 197
6.3. Заклю чение............................................................................................................... 200
Глава 7 . Шаблонизация веб-приложений ............................................................ 201
7.1. Поддержка чистоты кода путем ш аблонизации............................................ 201
7.1.1. Ш аблонизация в действии........................................................................203
7.1.2. Визуализация H TM L без ш аблона........................................................ 205
7.2. Ш аблонизация с E JS ..............................................................................................207
7.2.1. Создание шаблона....................................................................................... 207
7.2.2. Интеграция шаблонов EJS в приложение ........................................ 209
7.2.3. Использование EJS в клиентских приложениях ............................. 210
7.3. Использование язы ка M ustache с шаблонизатором H ogan........................211
7.3.1. Создание шаблона....................................................................................... 212
7.3.2. Теги M ustache............................................................................................... 212
7.3.3. Тонкая настройка Hogan............................................................................215
7.4. Ш аблоны Pug ..........................................................................................................215
7.4.1. Основные сведения о P u g ........................................................................217
7.4.2. Программная логика в шаблонах Pug ................................................ 219
7.4.3. Организация шаблонов P u g ....................................................................223
7.5. Заклю чение............................................................................................................... 227
Глава 8 . Хранение данных в приложениях............................................................ 228
8.1. Реляционные базы д ан н ы х .................................................................................. 228
8.2. P ostgreS Q L ............................................................................................................... 228
8.2.1. Установка и настройка............................................................................... 229
8.2.2. Создание базы данных............................................................................... 229
8.2.3. Подключение к Postgres из N ode............................................................230
8.2.4. Определение таблиц ............................................................................... 230
Оглавление 11

8.2.5. Вставка д ан н ы х ........................................................................................... 231


8.2.6. Обновление дан ны х................................................................................... 231
8.2.7. Запросы на выборку данны х....................................................................232
8.3. Knex ........................................................................................................................... 232
8.3.1. jQ uery для баз данны х............................................................................... 233
8.3.2. Подключение и выполнение запросов в K nex.................................... 234
8.3.3. Переход на другую базу д а н н ы х ............................................................236
8.3.4. Остерегайтесь ненадежных абстракций ............................................ 236
8.4. M ySQL и P o stg re S Q L ........................................................................................... 237
8.5. Гарантии ACID .......................................................................................................238
8.5.1. Атомарность ............................................................................................... 238
8.5.2. Согласованность ....................................................................................... 238
8.5.3. И зо л яц и я....................................................................................................... 239
8.5.4. У стойчивость............................................................................................... 239
8.6. N o S Q L ........................................................................................................................ 239
8.7. Распределенные базы д а н н ы х ............................................................................ 240
8.8. M o ngoD B .................................................................................................................. 241
8.8.1. Установка и настройка............................................................................... 242
8.8.2. Подключение к M ongoD B ........................................................................242
8.8.3. Вставка докум ентов................................................................................... 243
8.8.4. Получение инф орм ации............................................................................243
8.8.5. Идентификаторы M ongoD B ....................................................................245
8.8.6. Реплицированные н аборы ........................................................................247
8.8.7. Уровень з а п и с и ........................................................................................... 248
8.9. Хранилища «ключ-значение» ............................................................................ 250
8.10. Redis ........................................................................................................................ 251
8.10.1. Установка и н астр о й к а............................................................................252
8.10.2. Выполнение и н и ц и ал и зац и и ................................................................252
8.10.3. Работа с парами «ключ-значение» .................................................... 253
8.10.4. Работа с ключами ................................................................................... 254
8.10.5. Кодирование и типы д а н н ы х ................................................................254
8.10.6. Работа с х еш ам и ....................................................................................... 256
8.10.7. Работа со списками ............................................................................... 257
8.10.8. Работа со множествами............................................................................258
8.10.9. Реализация паттерна «публикация/подписка» на базе каналов 259
8.10.10. Повышение быстродействия Redis.................................................... 260
8.11. Встроенные базы данных .................................................................................. 260
8.12. LevelDB .................................................................................................................. 261
8.12.1. LevelUP и LevelDOW N............................................................................262
8.12.2. Установка ................................................................................................... 262
12 Оглавление

8.12.3. Обзор A P I ................................................................................................... 263


8.12.4. И нициализация ....................................................................................... 263
8.12.5. Кодирование ключей и зн а ч е н и й ........................................................ 264
8.12.6. Чтение и запись пар «ключ-значение» ............................................ 264
8.12.7. Заменяемые подсистемы базы данных ............................................ 265
8.12.8. М одульная база данных ........................................................................267
8.13. Затратные операции сериализации и десериализации............................. 268
8.14. Хранение данных в б р ау зер е............................................................................ 269
8.14.1. Веб-хранилище: localStorage и sessionStorage ................................ 269
8.14.2. Чтение и запись значений ....................................................................270
8.14.3. localForage................................................................................................... 273
8.14.4. Чтение и запись ....................................................................................... 273
8.15. Виртуальное х ран ен и е........................................................................................274
8.15.1. S 3 ...................................................................................................................275
8.16. Какую базу данных выбрать?............................................................................ 276
8.17. Заклю чение............................................................................................................ 276
Глава 9 . Тестирование приложений Node ............................................................ 278
9.1. Модульное тестирование..................................................................................... 279
9.1.1..Модуль a s s e rt............................................................................................... 280
9.1.2. Mocha ...........................................................................................................284
9.1.3. Vows ...............................................................................................................289
9.1.4. Chai ...............................................................................................................292
9.1.5. Библиотека should.js ............................................................................... 293
9.1.6. Ш пионы и заглушки в Sinon.JS ............................................................296
9.2. Ф ункциональное тестирование......................................................................... 298
9.2.1. Selenium ....................................................................................................... 299
9.3. Ошибки при прохождении тестов ................................................................... 302
9.3.1. Получение более подробных журналов................................................ 303
9.3.2. Получение расширенной трассировки стека .................................... 305
9.4. Заклю чение............................................................................................................... 307
Глава 10 . Развертывание и обеспечение доступности приложений Node . . . . 308
10.1. Хостинг Node-прилож ений............................................................................... 308
10.1.1. Платформа как с е р в и с ............................................................................309
10.1.2. С ерверы .......................................................................................................311
10.1.3. К о н тей н ер ы ............................................................................................... 312
10.2. Основы развертывания ..................................................................................... 314
10.2.1. Развертывание из репозитория G i t .................................................... 314
10.2.2. Поддержание работы N ode-приложения ........................................ 315
10.3. М аксимизация времени доступности и производительности
п рилож ений..................................................................................................................... 317
Оглавление 13

10.3.1. Поддержание доступности приложения с U p s ta r t.........................318


10.3.2. Кластерный A P I ....................................................................................... 320
10.3.3. Хостинг статических файлов и представительство.........................322
10.4. Заклю чение............................................................................................................ 324

Часть III. За пределами веб-разработки ............................................325


Глава 11. Написание приложений командной строки..........................................326
11.1. Соглашения и философия.................................................................................. 326
11.2. Знакомство с parse-json....................................................................................... 328
11.3. Аргументы командной строки ......................................................................... 328
11.3.1. Разбор аргументов командной строки................................................ 328
11.3.2. Проверка аргументов ............................................................................329
11.3.3. Передача stdin в виде файла ................................................................330
11.4. Использование программ командной строки с npm................................... 331
11.5. Связывание сценариев с каналами .................................................................332
11.5.1. Передача данных parse-json....................................................................332
11.5.2. Ошибки и коды завершения ................................................................333
11.5.3. Использование каналов в N o d e ............................................................335
11.5.4. Каналы и последовательность выполнения команд .....................336
11.6. Интерпретация реальных сц ен ари ев..............................................................337
11.7. Заклю чение............................................................................................................ 338
Глава 12 . Разработка настольных приложений с использованием
Electron...........................................................................................................................339
12.1. Знакомство с Electron ........................................................................................339
12.1.1. Технологический стек E le c tro n ............................................................340
12.1.2. Проектирование и н тер ф ей са................................................................341
12.2. Создание приложения Electron ...................................................................... 342
12.3. Построение полнофункционального настольного п ри л ож ен и я............344
12.3.1. Исходная настройка React и Babel .................................................... 345
12.3.2. Установка зависимостей ........................................................................345
12.3.3. Настройка webpack ............................................................................... 346
12.4. Приложение React ..............................................................................................348
12.4.1. Определение компонента Request .................................................... 349
12.4.2. Определение компонента R esp o n se.................................................... 352
12.4.3. Взаимодействие между компонентами React ................................ 354
12.5. Построение и распространение ...................................................................... 355
12.5.1. Построение приложений с использованием Electron Packager .. 356
12.5.2. Упаковка ................................................................................................... 357
12.6. Заклю чение............................................................................................................ 358
14 Оглавление

Приложения............................................................................................. 359
Приложение А. Установка N o d e ..............................................................................360
А.1. Установка Node с использованием программы установки ........................360
А.1.1. Программа установки для m a c O S ........................................................ 360
А.1.2. Программа установки для W in d o w s.................................................... 362
A.2. Другие способы установки N o d e ...................................................................... 363
A.2.1. Установка Node из исходного кода .................................................... 363
A.2.2. Установка Node из менеджера п акетов................................................ 363
Приложение Б . Автоматизированное извлечение веб-данных ...................... 365
Б.1. Извлечение веб-данных........................................................................................365
Б.1.1. Применение извлечения веб-данных ................................................ 366
Б.1.2. Необходимые инструм енты ....................................................................367
Б.2. Простейшее извлечение веб-данных с использованием cheerio...............368
Б.3. Обработка динамического контента с jsd o m ..................................................371
Б.4. Обработка «сырых» д ан н ы х ............................................................................... 374
Б.5. Заключение ............................................................................................................ 377
Приложение В . Официально поддерживаемые промежуточные
компоненты 378
B.1. Разбор cookie, тел запросов и строк информационных зап росов............378
B.1.1. cookie-parser: разбор H T T P-cookie........................................................ 379
В.1.2. Разбор строк запросов ............................................................................383
В.1.3. body-parser: разбор тел запросов............................................................384
В.1.4. Сжатие ответов........................................................................................... 391
В.2. Реализация базовых функций веб-приложения ......................................... 393
В.2.1. morgan: ведение журнала запросов .................................................... 393
В.2.2. serve-favicon: значки адресной строки и закладки .........................397
В.2.3. m ethod-override — имитация методов H T T P .................................... 398
В.2.4. vhost: виртуальный хости н г....................................................................401
В.2.5. express-session: управление сеансами ................................................ 402
В.3. Безопасность веб-приложений ......................................................................... 407
В.3.1. basic-auth: базовая H T T P -аутентификация .................................... 408
В.3.2. csurf: защита от атак CSRF ....................................................................410
В.3.3. errorhandler: — обработка ошибок при разработке........................... 412
В.4. Предоставление статических файлов ..............................................................414
В.4.1. serve-static — автоматическое предоставление статических
файлов браузеру ................................................................................................... 414
В.4.2. serve-index: генерирование списков содержимого каталогов........416

Глоссарий 418
Предисловие

С момента публикации первого издания «Node.js в действии» проект Node объ­


единился с io.js, а модель управления радикально изменилась. Менеджер пакетов
Node выделился в успешную новую компанию npm, а такие технологии, как Babel
и Electron, изменили панораму разработки.
Тем не менее в базовых библиотеках Node изменений не так уж много. Сам язык
JavaScript изменился: многие разработчики теперь используют возможности ES2015,
поэтому исходные листинги были переписаны для «стрелочных» функций, констант
и деструктуризации. При этом библиотеки и встроенные инструменты Node все еще
очень похожи на свои аналоги Node до выхода версий 4.x, поэтому мы обратились
к сообществу за идеями обновления этого издания.
Чтобы отразить реалии, с которыми теперь сталкивается N ode-разработчик, мы
изменили структуру книги. Express и Connect уделено меньше внимания, и книга
в большей степени ориентирована на ш ирокий спектр технологий. Вы найдете
в книге всю информацию, необходимую для разработки в полном технологиче­
ском стеке, включая системы построения фронтэнда, выбор веб-фреймворка, ра­
боту с базами данных в Node, написание тестов и развертывание веб-приложений.
Помимо разработки веб-приложений в книгу также были добавлены главы о на­
писании приложений командной строки и приложений Electron. Они помогут вам
извлечь максимум практической пользы из ваших навыков использования Node
и JavaScript.
Node и экосистема этой технологии —не единственная тема книги. Я по возможности
постарался добавить историческую справку о том, что повлияло на развитие Node.
Наряду с обычными темами Node и JavaScript рассматриваются такие вопросы,
как философия Unix и корректное, безопасное использование баз данных. Хочется
верить, что у вас сформируется достаточно широкая картина Node и JavaScript,
которая поможет вам в поиске ваших собственных решений современных задач.
Алекс Янг
Благодарности

Эта книга основана на материале авторов предыдущего издания и очень многим


обязана их труду: это Майк Кантелон (M ike Cantelon), Марк Хартер (M arc Harter),
Т. Дж. Головайчук (T J. Holowaychuk) и Натан Райлих (Nathan Rajlich). Это издание
увидело свет только благодаря активной помощи рабочей группы из издательства
M anning. Синтия Кейн (C ynthia Kane), руководитель проекта, помогала мне не
отвлекаться от сути во время долгого процесса обновления исходного материала.
Без тщательной научной редактуры, проведенной Дугом Уорреном (D oug Warren),
книга и примеры кода не были бы и наполовину так хороши. Наконец, мы хотим по­
благодарить многих рецензентов, поделившихся своим мнением во время написания
и разработки: Остин Кинг (Austin King), Карл Хоуп (C arl Hope), Крис Солч (Chris
Salch), Кристофер Рид (C hristopher Reed), Дейл Ф рэнсис (D ale Francis), Хафиз
Вахид уд дин (Hafiz W aheed ud din), Харинат М аллепалли (H arinath Mallepally),
Д ж еф ф Смит (Jeff Sm ith), М арк-Ф илипп Хуге (M arc-P hilippe H uget), Мэттью
Бертони (M atthew Bertoni), Ф илипп Ш арьер (Philippe Charriere), Рэнди Камрадт
(R andy Kamradt), Сандер Россел (Sander Rossel), Скотт Дирбек (S cott Dierbeck)
и Уильям Уилер (W illiam W heeler).
Алекс Янг
О книге

П ервое издание книги «Node.js в действии» было посвящ ено разработке веб­
приложений, при этом особое внимание уделялось веб-фреймворкам C onnect
и Express. Второе издание «Node.js в действии» было переработано в соответствии
с изменившимися требованиями в области разработки Node. Вы узнаете о системах
построения интерфейса и популярных веб-фреймворках Node, а также научитесь
строить веб-приложения на базе Express с нуля. Также в книге рассказано о том,
как строить автоматизированные тесты и развертывать веб-приложения Node.
Технология Node все чаще используется в сочетании с инструментами командной
строки и настольными прилож ениями на базе Electron, поэтому в книгу были
включены главы, посвященные обеим областям.
Предполагается, что читатель знаком с основными концепциями программирования.
В первой главе приведен краткий обзор JavaScript и ES2015 для читателей, которые
только начинают вникать в тонкости современного JavaScript.

Структура
Книга состоит из трех частей.
В части I рассматриваются основы Node.js и фундаментальные методики, исполь­
зуемые для разработки приложений на этой платформе. В главе 1 описываются
характеристики JavaScript и Node, с приведением примеров кода. Глава 2 проведет
вас поэтапно через фундаментальные концепции программирования Node.js. Глава 3
представляет собой полное руководство по построению веб-приложений с нуля.
Часть II — самая большая — посвящ ена разработке веб-приложений. В главе 4
рассеиваются некоторые заблуждения по поводу frond-end систем сборки; если
вы когда-либо использовали webpack или Gulp в проекте, но не понимали эти тех­
нологии в полной мере, эта глава написана для вас. В главе 5 описаны некоторые
популярные фреймворки стороны сервера для Node, а в главе 6 технологии Connect
и Express рассматриваются более подробно.
Глава 7 посвящена языкам сценариев, которые могут значительно повысить эф ­
фективность вашей работы при написании кода на стороне сервера. Большинству
веб-приложений нужна база данных, поэтому в главе 8 рассматриваются многие
18 О книге

разновидности баз данных, которые могут использоваться с Node, от реляционных


до баз данных NoSQL. В главах 9 и 10 рассматриваются процессы тестирования
и разработки, здесь также упоминается развертывание в облаке.
Часть III выходит за рамки разработки веб-приложений. Глава 11 посвящена по­
строению приложений командной строки с использованием Node, чтобы вы могли
создавать текстовые интерфейсы, удобные для разработчика. Если вас интересуют
перспективы построения настольных приложений на базе Node (например, Atom),
обращайтесь к главе 12 — она полностью посвящена Electron.
Также в книгу включены три подробных приложения. В приложении А приведены
инструкции по установке Node для систем MacOS и Windows. Приложение Б содер­
жит подробное руководство по извлечению веб-данных (web scraping), а в приложе­
нии В представлены все компоненты среднего звена, официально поддерживаемые
для веб-фреймворка Connect.

Правила оформления и загрузка примеров кода


Примеры кода, приведенные в книге, оформляются в соответствии со стандартным
соглашением по оформлению JavaScript-кода. Для создания отступов в коде вместо
символов табуляции применяются пробелы. Существует ограничение на длину
строки кода, равное 80 символам. Код, приведенный в листингах, сопровождается
комментариями, которые поясняют ключевые концепции.
Каждая инструкция занимает отдельную строку и завершается точкой с запятой.
Блоки кода, содержащие несколько инструкций, заключены в фигурные скобки.
Л евая фигурная скобка находится в первой (открывающей) строке блока. Правая
фигурная скобка закрывает блок кода и выравнивается по вертикали с открыва­
ющей скобкой.
Примеры кода, используемые в книге, можно загрузить с веб-сайта www.manning.
com/books/node-js-in-action-second-edition.
Об авторах

Алекс Янг
Алекс — веб-разработчик, ж ивущ ий в Лондоне; автор книги Node.js in Practice
(M anning, 2014). Алекс ведет популярный блог DailyJS по тематике JavaScript.
В настоящее время работает на Sky старшим разработчиком для NOW TV. Он есть
на G itH ub ( https://github.com/alexyoung) и в Twitter (@alex_young).

Брэдли Мек
Брэдли — участник TC39 и Node.js Foundation. В свободное время он разрабаты­
вает инструментарий JavaScript, занимается садоводством и преподает. До работы
в G oD addy он долго использовал Node.js на благо других компаний, таких как
NodeSource и Nodejitsu. Всегда готовый обучать и объяснять, он старается под­
держивать у людей мотивацию, потому что учиться для него так же сложно, как
и для всех остальных.
Иллюстрация на обложке

Иллю страция на обложке второго издания книги называется «M an about Town»


(«Прожигатель жизни») и была позаимствована из изданного в XIX веке во Ф ран­
ции четырехтомного каталога Сильвена М арешаля (Sylvain M arechal). В каталоге
представлена одежда, характерная для разных регионов Франции. Каждая иллюстра­
ция красиво нарисована и раскрашена от руки. Иллюстрации из каталога Марешаля
напоминают о культурных различиях между городами и весями мира, имевшими
место почти двести лет назад. Люди, проживавшие в изолированных друг от друга
регионах, говорили на разных языках и диалектах. По одежде человека можно было
определить, в каком городе, поселке или поселении он проживает.
С тех пор дресс-код сильно изменился, да и различия между разными регионами
стали не столь явно выраженными. В наше время довольно трудно узнать жите­
лей разных континентов, не говоря уже о жителях разных городов или регионов.
Возможно, мы отказались от культурных различий в пользу более разнообразной
личной жизни, и конечно, в пользу более разнообразной и стремительной техно­
логической жизни.
Сейчас, когда все компьютерные книги похожи друг на друга, издательство Manning
стремится к разнообразию и помещает на обложки книг иллюстрации, показываю­
щие особенности жизни в разных регионах Ф ранции два века назад.

От издательства
Ваши замечания, предложения, вопросы отправляйте по адресу comp@piter.com
(издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию о на­
ших книгах.
Знакомство с Node

В наши дни Node уже можно назвать зрелой платформой веб-разработки.


В главах 1-3 рассматриваются основные возможности Node, включая
использование базовых модулей и npm. Также вы узнаете, как Node ис­
пользует современные версии JavaScript и как построить веб-приложение
с нуля. После чтения этих глав у вас сформируется хорошее понимание
того, что может делать Node и как создавать ваши собственные проекты.
Знакомство с Node.js

Node.js — асинхронная уп равляем ая собы тиям и исп олни тельная платф орм а
JavaScript с мощной, но компактной стандартной библиотекой. Ее сопровождением
и поддержкой занимается Node.js Foundation — отраслевой консорциум с откры­
той моделью управления. Существует две активно поддерживаемые версии Node:
текущ ая (C u rren t) и пользующаяся долгосрочной поддержкой (LTS, Long Term
Support). Если вы захотите больше узнать о том, как осуществляется управление
Node, на официальном веб-сайте имеется достаточно подробная документация
(https://nodejs.org).
С момента появления Node.js в 2009 году язык JavaScript прошел долгий путь от еле
сносного браузерного язы ка до одного из важнейших языков во всех областях раз­
работки программного обеспечения. Отчасти это изменение связано с появлением
спецификации ECM AScript 2015, устранившей ряд важных недостатков в преды­
дущих версиях языка. Node использует JavaS cript-ядро Google V8, основанное на
шестой версии стандарта ECM AScript (иногда она называется ES6 и обозначается
сокращением ES2015). Также на ситуацию повлияли такие инновационные техно­
логии, как Node, React и Electron; они позволяют применять JavaScript буквально
повсеместно: от сервера до браузера и в платформенных мобильных приложениях.
Самые крупные компании постепенно принимают JavaScript, а компания Microsoft
даже внесла свой вклад в успех Node.
В этой главе мы расскаж ем о технологии Node, о ее неблокирую щ ей модели,
управляемой событиями, а также о некоторых факторах, благодаря которым язык
JavaScript стал отличным языком программирования общего назначения. Д ля на­
чала давайте рассмотрим типичное веб-приложение Node.

1.1. Типичное веб-приложение Node


Одна из сильных сторон Node и JavaScript вообще — однопоточная модель про­
граммирования. Программные потоки (threads) являются стандартным источником
1.1. Типичное веб-приложение Node 23

ошибок, и хотя некоторые из недавно появивш ихся языков программирования,


включая Go и Rust, пытаются предоставить безопасные инструменты параллельного
программирования, Node работает с моделью, используемой в браузере. Браузерный
код представляет собой последовательность команд, которые выполняются одна за
одной; код не выполняется параллельно. Д ля пользовательских интерфейсов такая
модель не имеет смысла: пользователь не хочет дожидаться завершения медленных
операций (например, обращений к данным по сети или к файлам). Д ля решения
этой проблемы в браузерах используются события: когда пользователь щелкает на
кнопке, инициируется событие, и выполняется функция, которая была определена
ранее, но еще не выполнялась. Тем самым предотвращаются некоторые проблемы,
встречающиеся в многопоточном программировании, включая взаимные блоки­
ровки (deadlocks) ресурсов и состояния гонки (race conditions).

1.1.1. Неблокирующий ввод/вывод


Что это означает в контексте программирования на стороне сервера? Ситуация
аналогична: запросы ввода/вы вода (например, обращения к диску или сетевым
ресурсам) также выполняются относительно медленно, поэтому исполнительная
среда не должна блокировать выполнение бизнес-логики во время чтения файлов
или передачи сообщений по сети. Д ля этого в Node используются три концепции:
события, асинхронные API, и неблокирующий ввод/вывод. Неблокирующий ввод/
вывод — низкоуровневый термин с точки зрения программиста Node. Он означает,
что программа может обратиться с запросом к сетевому ресурсу и заняться чем-то
другим. А потом, когда сетевая операция будет завершена, выполняется функция
обратного вызова, которая обработает результат.
На рис. 1.1 изображено типичное веб-приложение Node, использующее библиотеку
веб-программирования Express для обработки заказов в магазине. Браузеры выда­
ют запросы на приобретение продукта; приложение проверяет текущее состояние
складских запасов, создает учетную запись для пользователя, отправляет квитанцию
по электронной почте и возвращает H T T P -ответ в формате JSO N . Одновременно
могут происходить другие операции: квитанция отправляется по электронной почте,
а база данных обновляется информацией от пользователя и данными заказа. По сути,
перед нами прямолинейный императивный код JavaScript, но исполнительная среда
работает параллельно, потому что она использует неблокирующий ввод/вывод.
На рис. 1.1 приложение обращается к базе данных по сети. В Node сетевые операции
выполняются без блокировки, потому что Node при помощи библиотеки libuv (h ttp ://
libuv.org/) использует неблокирующие сетевые вызовы операционной системы. Эта
функциональность по-разному реализована для Linux, macOS и Windows, но вам
придется иметь дело только с удобной библиотекой JavaScript для работы с базами
данных. Хотя вы пишете команды типа d b .in se rt(q u e ry J e r r => {}), Node во внутрен­
ней реализации выполняет оптимизированные неблокирующие сетевые операции.
го
Ваше приложение Node и Express

Ответ Браузер

Глава 1. Знакомство с Node.js


в формате
Проверка Тело запроса: информация о заказе
Маршрутизатор JSON
складских
HTTP
запасов

SQL INSERT
Регистрация
пользователя

Неблокирующий
сетевой
Квитанция
по электронной ввод/вывод
почте Загрузка шаблона
вызывается с диска
next
(error) Ответ
HTTP

Обработчик
ошибки Квитанция
Объект ошибки по электронной
JavaScript почте

libuv

Рис. 1.1. Асинхронные и неблокирующие компоненты в приложениях Node


1.1. Типичное веб-приложение Node 25

Обращения к диску происходят примерно так же, но, как ни странно, полного совпа­
дения нет. Когда приложение генерирует квитанцию, отправляемую по электронной
почте, и шаблон сообщения читается с диска, libuv использует пул потоков для
создания иллю зии использования неблокирующего вызова. Управление пулом
потоков — довольно тяжелое дело, но понять команду e m a il.s e n d ('te m p la te .e js ',
( e r r , html) => {}) определенно намного проще.
Истинное преимущество использования асинхронных A PI с неблокирующими
операциями ввода/вывода заключается в том, что Node может заниматься други­
ми делами во время выполнения относительно медленных процессов. И хотя вы ­
полняться может только однопоточное и однопроцессное веб-приложение Node,
в любой момент времени оно может обрабатывать сразу несколько подключений
от тысяч потенциальных посетителей сайта. Чтобы понять, как это происходит,
необходимо познакомиться с циклом событий.

1.1.2. Цикл событий


А теперь сосредоточимся на одном конкретном аспекте рис. 1.1: обработке запросов
браузера. В этом приложении встроенная библиотека H TTP-сервера Node —базовый
модуль с именем h t t p . Server — обрабатывает запрос с использованием потоков, со­
бытий и парсера H T T P -запросов Node, который содержит платформенный код. При
этом инициируется выполнение функции обратного вызова в вашем приложении,
которая была добавлена средствами библиотеки веб-приложений Express (h ttp s://
expressjs.com/). Выполняемая функция обратного вызова выдает запрос к базе дан­
ных, и в конечном итоге приложение отвечает данными JS O N с использованием
HTTP. Весь процесс использует как минимум три неблокируемых сетевых вызова:
один для запроса, один для базы данных и один для ответа. Как Node планирует
все эти неблокирующие сетевые операции? Ответ: при помощи цикла событий. На
рис. 1.2 показано, как цикл событий используется для этих трех сетевых операций.
Цикл событий работает в одном направлении (он реализуется очередью FIFO —
«первым пришел, первым вышел») и проходит через несколько фаз. На рис. 1.2
показана упрощ енная последовательность важнейш их фаз, выполняемых при
каждой итерации цикла. Сначала выполняются таймеры, выполнение которых за­
планировано функциями JavaScript setTimeout и s e tIn te r v a l. Затем выполняются
обратные вызовы ввода/вывода, поэтому, если один из неблокирующих сетевых
вызовов вернул какой-либо ввод/вывод, в этот момент будет инициирован ваш
обратный вызов. В фазе опроса читаются новые события ввода/вывода, а в конце
инициируются обратные вызовы, запланированные функцией setIm m ediate. Это
особый случай, потому что он позволяет запланировать немедленное выполнение
обратных вызовов после текущих обратных вызовов ввода/вывода, уже находя­
щихся в очереди. Пока это звучит абстрактно, но сейчас вы должны уяснить одно:
Node использует однопоточную модель, но при этом предоставляет необходимые
средства для написания эффективного и масштабируемого кода.
26 Глава 1. Знакомство с Node.js

1. Вызов 2. Вызов 3. Вызов


Цикл событий запроса базы данных ответа

Выполнение таймеров
Активизация Отправка
незавершенных Функция: данных
Создание
обратных вызовов отправка/ JSON
пользователя
Опрос ввода/вывода заказ браузеру
База данных

Рис. 1.2. Цикл событий

Обратите внимание на то, что в примерах используются «стрелочные» функции


ES2015. Node поддерживает много новых функций JavaScript, и прежде чем дви­
гаться дальше, мы рассмотрим некоторые новые языковые средства, используемые
для написания более эффективного кода.

1.2. ES2015, Node и V8


Если вы когда-либо работали с Jav a S c rip t и вас огорчило отсутствие классов
и странные правила видимости — радуйтесь: в Node многие проблемы были реше­
ны! Теперь вы можете создавать классы и использовать конструкции видимости
co n st и l e t (вместо var). В Node 6 можно использовать параметры функций по
умолчанию , оставш иеся параметры (rest param eters), оператор sp read , циклы
fo r...o f, ш аблонны е строки, деструктуризацию , генераторы и многое другое.
Замечательная сводка новых возможностей ES2015 в Node доступна по адресу
http://node.green.
Начнем с классов. В ES5 и более ранних версиях для создания конструкций, напо­
минающих классы, использовались объекты-прототипы:
function User() {
/ / Конструктор
}
User.prototype.method = function() {
/ / Метод
};
1.2. ES2015, Node и V8 27

В Node 6 и ES2015 тот же код можно написать с использованием классов:


class User {
constructor() {}
method() {}
}

Код получается более компактным и лучше читается. Но это еще не все: Node также
поддерживает субклассирование, su p er и статические методы. Д ля пользователей
с опытом работы на других языках поддержка синтаксиса классов делает техноло­
гию Node более доступной по сравнению с временами, когда мы были ограничены
возможностями ES5.
Другая важная возможность Node 4 и выше — добавление конструкций const и le t.
В ES5 все переменные создавались с ключевым словом var. Проблема в том, что
var определяет переменные в области видимости функции или глобальной области
видимости, поэтому вы не могли определить переменную уровня блока в команде
i f , f o r или в другом блоке.

CONST ИЛИ LET?

Когда вы колеблетесь между const и let, почти всегда следует сделать выбор
в пользу const. Так как в большей части вашего кода будут использоваться
экземпляры ваших собственных классов, объектные литералы или значе­
ния, которые не должны изменяться, почти во всех случаях уместно const.
Даже экземпляры объектов с изменяемыми свойствами могут объявляться
с ключевым словом const — ведь const означает лишь то, что ссылка доступна
только для чтения, а не неизменность самого значения.

В Node такж е поддерж иваю тся обещ ания (prom ises) и генераторы. Обещания
поддерживаются многими библиотеками, что позволяет писать асинхронный код
в стиле динамических интерфейсов (fluent interface). Вероятно, вы уже знакомы
с динамическими интерфейсами: их видел каждый, кто когда-либо использовал
такие API, как jQuery, или хотя бы массивы JavaScript. Следующий короткий при­
мер демонстрирует применение сцепленных вызовов для манипуляций с массивами
в JavaScript:

[1, 2, 3]
.map(n => n * 2)
.f ilte r ( n => n > 3);

Генераторы нужны для использования синхронного стиля программирования


с асинхронны м вводом /вы водом . Если вас интересует практический пример
использования генераторов в Node, обратитесь к библиотеке веб-приложений
28 Глава 1. Знакомство с Node.js

Koa (h ttp ://ko a js.com /). При использовании обещаний или других генераторов
с Koa вы можете прибегнуть к y ie ld со значениями вместо вложения обратных
вызовов.
Еще одна полезная возможность ES2015 в Node — шаблонные строки. В ES5 стро­
ковые литералы не поддерживали интерполяцию или деление на несколько строк.
Теперь при использовании символа обратного апострофа ( ' ) можно вставлять
значения и использовать разбиения по строкам. Д анная возможность особенно
полезна при вставке коротких фрагментов H TM L в веб-приложениях:
this.body = '
<div>
<h1>Hello from Node</h1>
<p>Welcome, ${user.name}!</p>
</div>

В ES5 этот пример пришлось бы записывать следующим образом:


this.body = '\ n ';
this.body += '<div>\n';
this.body += ' <h1>Hello from Node</h1>\n';
this.body += ' <p>Welcome, ' + user.name + '</p>\n';
this.body += '<div>\n';

Старый стиль не только занимает больше места, но и повышает риск случайных


ошибок. П оследняя новая возможность, особенно важ ная для программистов
Node, — стрелочные функции, которые способствую т упрощ ению синтаксиса.
Например, если вы пишете функцию обратного вызова, которая получает один
аргумент и возвращ ает значение, трудно представить себе более компактный
синтаксис:

[1, 2, 3].map(v => v * 2);

В Node обычно используются два аргумента, потому что первым аргументом функ­
ции обратного вызова часто является объект ошибки. В этом случае аргументы
должны заключаться в круглые скобки:

const fs = re q u ire ('fs ');


fs.readFile('package.json',
(err, text) => console.log('Length:', text.length)
);

Если тело функции должно содержать более одной строки, необходимо использовать
фигурные скобки. Полезность стрелочных функций не ограничивается упрощением
синтаксиса; она также имеет отношение к областям видимости JavaScript. В ES5
и ранее при определении функций внутри других функций ссылка t h i s становится
1.2. ES2015, Node и V8 29

глобальным объектом. И з-за этого в следующем классе в стиле ES5 присутствует


скрытая ошибка:
function User(id) {
/ / Конструктор
th is .id = id;
}
User.prototype.load = function() {
var self = th is;
var query = 'SELECT * FROM users WHERE id = ?';
sql.query(query, th is .id , function(err, users) {
self.name = users[0].name;
});
};

В строке, в которой присваивается значение self.nam e, невозможно использовать


запись this.nam e, потому что t h i s внутри функции будет глобальным объектом.
Когда-то применялось обходное решение с сохранением значения t h i s в точке
входа родительской функции или метода. Со стрелочными функциями связывание
выполняется правильно. В ES2015 предыдущий пример можно записать в гораздо
более естественном виде:
class User {
constructor(id) {
th is .id = id;
}
load() {
const query = 'SELECT * FROM users WHERE id = ?';
sql.query(query, th is .id , (e rr, users) => {
this.name = users[0].name;
});
}
}

Вы не только можете использовать c o n st для более эффективного моделирования


запроса к базе данных, но и отпадает необходимость в неуклюжей переменной se lf.
В ES2015 появилось много новых замечательных возможностей, благодаря которым
код Node лучше читается; но давайте посмотрим, на чем строится их реализация
в Node и как они связаны с уже рассмотренными средствами неблокирующего
ввода/вывода.

1.2.1. Node и V8

Технология Node основана на ядре JavaScript V8, разработанном проектом Chromium


для Google Chrome. К отличительным особенностям V8 относится прямая компиля­
ция в машинный код и средства оптимизации, обеспечивающие высокую скорость
30 Глава 1. Знакомство с Node.js

работы Node. В разделе 1.1.1 был упомянут еще один из встроенных компонентов
Node —libuv. В этой части речь идет о вводе/выводе; V8 обеспечивает интерпретацию
и выполнение кода JavaScript. Чтобы использовать libuv с V8, следует применить
связующую прослойку C++. На рис. 1.3 изображены все программные компоненты,
из которых состоит Node.

Ваше замечательное приложение app.js

Код JavaScript платформы Node.js,


библиотеки С и C++
г
Базовые модули
JavaScript для Node
V У
V8
Связующие прослойки C++
/
libuv c-ares http
ч

Операционная система

Рис. 1.3. Программный стек Node

Таким образом, конкретная функциональность JavaScript, доступная для Node,


определяется набором возможностей, поддерживаемых V8. Управление этой под­
держкой осуществляется через функциональные группы.

1.2.2. Работа с функциональными группами


Node включает функциональность ES2015, основанную на возможностях, поддер­
живаемых V8. Функциональность группируется по категориям: завершенные, за ­
планированные и находящиеся в разработке. Завершенные возможности включаются
по умолчанию, но для включения запланированных и находящихся в разработке
возможностей используются флаги командной строки. Если вы хотите использовать
запланированные возможности, которые почти готовы, но не считаются завершен­
ными группой V8, запустите Node с флагом --harmony. Возможности, находящиеся
в разработке, менее стабильны, а для их включения используются специальные
флаги. Д ля получения списка всех возможностей Node, находящихся в разработке,
в документации Node рекомендуется провести поиск по строке «in progress»:
node --v8-options | grep "in progress"

Список будет изменяться в зависимости от выпусков Node. У Node также имеется


график управления версиями, определяющий доступные API.
1.3. Установка Node 31

1.2.3. График выпуска версий Node


Выпуски Node делятся на пользую щ иеся долгосрочной поддержкой, или LTS
(Long-Term Support), текущие (C u rren t) и ежедневные (N ightly). Д ля выпусков
LTS обеспечивается 18-месячная поддержка с последующей 12-месячной под­
держкой сопровождения. Д ля выпусков прим еняется система семантического
управления версиями (SemVer). В этой системе выпуску назначается основной
(m ajor), дополнительный (m inor) и оперативный (patch) номера версии. Н апри­
мер, у выпуска 6.9.1 имеется основной номер версии 6, дополнительный номер 9
и оперативный 1. Каждый раз, когда у Node изменяется основной номер версии,
это может означать, что некоторые API стали несовместимыми с вашими проекта­
ми и вам придется заново протестировать их для новой версии Node. Кроме того,
в терминологии выпуска Node увеличение основного номера версии означает по­
явление новой текущей версии. Ежедневные сборки автоматически генерируются
каждые 24 часа с новейшими изменениями, но, как правило, используются только
для тестирования новой функциональности Node.
Выбор версии зависит от проекта и организации. Одни предпочитают LTS из-за
более редких обновлений: этот вариант хорошо подходит для крупных организа­
ций, в которых частые обновления создают проблемы. Но если вас интересуют
новейшие усовершенствования в области быстродействия и функциональности,
выбирайте текущую версию.

1.3. Установка Node


Простейший способ установить Node — использовать программу установки с сайта
https://nodejs.org. Новейшая текущая версия (6.5 на момент написания книги) уста­
навливается средствами Mac или Windows. Загрузите исходный код самостоятельно
или выполните установку при помощи менеджера пакетов вашей операционной
системы. Такие пакеты существуют для Debian, U buntu, Arch, Fedora, FreeBSD,
G entoo и SUSE. Также имеются пакеты для Hom ebrew и Sm artOS. Если пакета
для вашей операционной системы нет, проведите построение из исходного кода.

ПРИМЕЧАНИЕ
Дополнительная информация об установке Node приведена в приложении А.

Полный список пакетов находится на сайте Node (https://nodejs.org/en/dow nload/


package-manager/), а исходный код доступен на G itH ub (https://github.com /nodejs/
node). Исходный код также пригодится вам в том случае, если вы хотите заняться
изучением кода Node без его загрузки.
После того как платформа Node будет установлена, вы можете немедленно проверить
результат — введите команду node -v в терминале. Команда должна вывести номер
32 Глава 1. Знакомство с Node.js

только что загруженной и установленной версии Node. Создайте файл с именем


hello.js, содержимое которого должно выглядеть так:
console.log("hello from Node");

Сохраните файл и запустите его командой node h e l l o .j s . Поздравляем! Теперь вы


готовы к тому, чтобы заняться написанием собственных приложений на базе Node.

БЫСТРОЕ НАЧАЛО РАБОТЫ В WINDOWS, LINUX И MACOS

Если вы недавно занялись программированием и у вас еще нет любимого


текстового редактора, для Node хорошо подойдет Visual Studio Code (h ttp s ://
code.visualstudio.com /). Этот редактор создан компанией Microsoft, но он
распространяется с открытым кодом, доступен для бесплатной загрузки
и поддерживает Windows, Linux и macOS.
Начинающие программисты оценят такие возможности Visual Studio Code,
как цветовое выделение синтаксиса JavaScript и автозавершение для базовых
модулей Nodе; ваш код JavaScript будет выглядеть более четко, и вы будете
видеть списки поддерживаемых методов и объектов в процессе ввода. Вы
также можете открыть интерфейс командной строки, в котором Node можно
вызвать простым вводом команды Node. Этот способ особенно полезен для
выполнения Node и команд npm. Возможно, пользователи W indows пред­
почтут его использованию cmd.exe. Все листинги были протестированы
в W indows и Visual Studio Code, поэтому для выполнения примеров ничего
особенного вам не потребуется.
Возможно, в начале работы вам стоит познакомиться с учебником по Node.js
для Visual Studio Code ( https://code.visualstudio.com /docs/runtim es/nodejs).

Когда вы установите Node, в вашем распоряж ении также окажутся некоторые


бесплатные инструменты. Node — не просто интерпретатор: это целое семейство
инструментов, образующих платформу Node. В следующем разделе инструменты,
включенные в поставку Node, рассматриваются более подробно.

1.4. Встроенные средства Node


В поставку Node входит встроенный менеджер пакетов, базовые модули JavaScript
для поддержки самых разнообразных функций, от файлового и сетевого ввода/вы ­
вода до сжатия zlib, а также отладчик. Менеджер пакетов npm является важнейшим
компонентом инфраструктуры, поэтому мы рассмотрим его более подробно.
Если вы хотите убедиться в том, что установка Node была выполнена правильно,
выполните команды node -v и npm -v в командной строке. Эти команды выводят
номера только что установленных версий Node и npm.
1.4. Встроенные средства Node 33

1.4.1. npm
Чтобы запустить программу командной строки npm, введите команду npm. П ро­
грамма может использоваться для установки пакетов из центрального реестра npm,
но также и для поиска и организации совместного использования ваших проектов
с открытым или закрытым кодом. У каждого пакета npm в реестре существует сайт,
на котором хранится Readme-файл, информация об авторе и статистика о загрузках.
Впрочем, это описание нельзя назвать исчерпывающим. Программой npm занимает­
ся npm, Inc. — компания, обеспечивающая работу сервиса npm и предоставляющая
услуги коммерческим организациям. Сюда входит хостинг приватных пакетов
npm; вы также можете вносить ежемесячную плату за хостинг исходного кода,
принадлежащего вашей компании, чтобы разработчики JavaS cript могли легко
устанавливать его с использованием npm.
При установке пакетов командой npm i n s t a l l вам придется решить, хотите ли вы до­
бавить их в свой текущий проект или установить их глобально. Глобальная установка
пакетов обычно используется для служебных программ —чаще всего это программы,
запускаемые из командной строки. Хорошим примером служит пакет gulp-cli.
Чтобы использовать npm, создайте файл package.json в каталоге, содержащем проект
Node. Проще всего поручить создание файла package.json менеджеру npm.
Введите следующую команду в командной строке:
mkdir example-project
cd example-project
npm in it -y

Открыв файл package.json, вы увидите простые данные в формате JSO N с описанием


вашего проекта. Если теперь установить модуль с сайта www.npmjs.com с ключом
--save, npm автоматически обновит файл package.json. Попробуйте сами — введите
команду npm i n s t a l l , или сокращенно npm i:
npm i --save express

Если теперь открыть файл package.json, вы увидите, что в свойстве dependencies до­
бавился пакет express. Кроме того, в папке node_modules появился каталог express. Он
содержит только что установленную версию Express. Вы также можете установить
модули глобально с ключом - -g lo b al. Старайтесь по возможности использовать
локальные модули, но глобальные модули могут быть полезны для инструментов
командной строки, которые должны использоваться за пределами JavaS cript-кода
Node. В качестве примера средства командной строки, устанавливаемого из npm,
можно привести ESLint (http://eslint.org/).
Начиная работать с Node, вы часто будете пользоваться пакетами из npm. Node
поставляется с множеством полезных встроенных библиотек, которые обычно на­
зываются базовыми модулями. Рассмотрим их более подробно.
34 Глава 1. Знакомство с Node.js

1.4.2. Базовые модули


Базовые модули Node напоминают стандартные библиотеки других языков; эти
инструменты понадобятся вам для написания кода JavaScript на стороне сервера.
Сами стандарты JavaScript не включают никаких средств для работы с сетью —
и даже файлового ввода/вывода в том виде, в котором его представляет большин­
ство разработчиков серверного кода. Чтобы Node можно было использовать для
серверного программирования, в эту платформу необходимо было добавить средства
для работы с файлами и сетевых операций T C P/IP.

Файловая система
В поставку Node включается библиотека файловой системы (fs, path), клиенты
и серверы T C P (n et), поддержка H T T P (h ttp и h ttp s) и разреш ения доменных
имен (dns). Также имеется полезная библиотека, которая используется в основном
для написания тестов (assert), и библиотека операционной системы для запроса
информации о платформе (os).
Node также содержит ряд уникальны х библиотек. М одуль events — небольшая
библиотека для работы с событиями — используется в качестве основы для многих
API Node. Например, модуль stream использует модуль events для предоставления
абстрактных интерфейсов для работы с потоками данных. Поскольку все потоки
данных в Node используют одни и те же API, вы можете легко составлять программ­
ные компоненты; если у вас имеется объект чтения файлового потока, вы можете
направить его через преобразование zlib, которое выполняет сжатие данных, а затем
через объект записи в файловый поток для записи данных в файл.
В следующем листинге продемонстрировано использование модуля Node fs для
создания потоков чтения и записи, которые могут направляться через другой поток
(gzip) для преобразования данных — в данном случае их сжатия.

Листинг 1.1. Использование базовых модулей и потоков


const fs = re q u ire ('fs ');
const zlib = re q u ire ('z lib ');
const gzip = zlib.createG zip();
const outStream = fs.createW riteStream ('output.js.gz');

fs.createReadStream ('./node-stream .js')


.pi pe(gz ip)
.pipe(outStream);

Сетевые операции
Существует распространенное мнение, что создание простого сервера H T T P было
настоящим примером программы «Hello World» для Node. Чтобы построить сервер
на базе Node, необходимо загрузить модуль http и передать ему функцию. Функция
1.4. Встроенные средства Node 35

получает два аргумента: входной запрос и выходной ответ. В листинге 1.2 приведен
пример, который вы можете выполнить в терминале.

Листинг 1.2. Программа «Hello World» с использованием модуля http для Node
const http = re q u ire ('h ttp ');
const port = 8080;

const server = http.createServer((req, res) => {


res.end('H ello, w orld.');
});
serv er.listen (p o rt, () => {
console.log('Server listening on: h ttp ://lo calh o st:% s', port);
});

Сохраните листинг 1.2 в файле hello.js и запустите его командой node h e ll o .j s . О т­


крыв адрес http://localhost:8080, вы увидите сообщение из строки 4.
Базовые модули Node предоставляют минимальную функциональность, но при
этом они достаточно мощны. Часто многие задачи удается решить простым ис­
пользованием этих модулей, даже без установки дополнительных пакетов из npm.
З а дополнительной информацией о базовых модулях обращайтесь по адресу http s://
nodejs.org/api/.
Последний встроенный инструмент Node — отладчик. В следующем разделе рас­
сматривается пример его практического применения.

1.4.3. Отладчик
В поставку Node вклю чается отладчик с поддержкой пошагового выполнения
и R EPL (R ead-E val-P rint Loop). Работа отладчика основана на взаимодействии
с вашей программой по сетевому протоколу. Чтобы запустить программу в отлад­
чике, укажите аргумент debug в командной строке.
Предположим, вы отлаживаете код из листинга 1.2:
node debug h ello .js

Результат должен выглядеть так:


< Debugger listen in g on [::]:5858
connecting to 127.0.0.1:5858 . . . ok
break in node-h ttp .js:1
> 1 const http = re q u ire ('h ttp ');
2 const port = 8080;
3

Среда Node запускает вашу программу и активизирует ее отладку подключением


к порту 5858. Теперь вы можете ввести команду help, чтобы просмотреть список
36 Глава 1. Знакомство с Node.js

доступных команд, а затем команду c для продолжения выполнения программы.


Node всегда запускает программу в прерванном состоянии, поэтому вам всегда при­
дется продолжать ее выполнение, прежде чем делать что-либо другое.
Вы можете прервать выполнение отладчика, включив команду debugger в любую
позицию кода. При обнаружении команды debugger отладчик останавливается,
и вы можете вводить команды. Представьте, что вы написали REST API для соз­
дания учетных записей новых пользователей, а код создания не сохраняет хеш
пароля нового пользователя в базе данных. Включите команду debugger в метод
save класса User, выполните в пошаговом режиме каждую команду и посмотрите,
что происходит.

ИНТЕРАКТИВНАЯ ОТЛАДКА

Node поддерживает отладочный протокол Chrome. Чтобы отладить сценарий


с использованием средств разработчика Chrome, укажите флаг --inspect при
запуске программы:
node --inspect --debug-brk

Node запускает отладчик и преры вает вы полнение в первой строке. На


консоль выводится U R L-адрес; откройте его в Chrome для использования
встроенного отладчика Chrome. Отладчик Chrome позволяет выполнять код
строку за строкой, при этом он выводит значения всех переменных и объектов.
Этот способ намного удобнее ввода команды console log.

Отладка более подробно рассматривается в главе 9. Если вы захотите опробовать


ее прямо сейчас, лучш е всего начать с описания отладки в документации Node
(https://nodejs.org/api/debugger.html).
До настоящего момента мы говорили о том, как работает платформа Node и какие
возможности она предоставляет разработчикам. Вероятно, вы с нетерпением ждете
рассказа о том, что можно сделать с помощью Node на практике. В следующем раз­
деле рассматриваются разные типы программ, создаваемых на платформе Node.

1.5. Три основных типа программ Node


Программы Node делятся на три основных типа: веб-приложения, средства ко ­
мандной строки и демоны и настольные приложения. К категории веб-приложений
относятся простые приложения, предоставляющие одностраничные приложения,
микрослужбы REST и веб-приложения полного стека. Возможно, вы уже исполь­
зовали средства командной строки, написанные с использованием Node, например
npm, Gulp и webpack. Демоны (daemons) представляют собой фоновые службы. Хо­
рошим примером служит менеджер процессов PM2 (www.npmjs.com/package/pm2).
1.5. Три основных типа программ Node 37

Настольные приложения обычно строятся с применением фреймворка Electron


(http://electron.atom .io/), использующего Node во внутренней реализации для на­
стольных веб-приложений. Примеры такого рода — текстовые редакторы Atom
(h ttp s://atom .io/) и Visual Studio Code (https://code.visualstudio.com/).

1.5.1. Веб-приложения
Технология Node предназначена для работы с JavaScript на стороне сервера, по­
этому ее выбор в качестве платформы для построения веб-приложений выглядит
логично. Выполнение JavaScript на стороне клиента и сервера открывает возмож­
ности для повторного использования кода в разных средах. Веб-приложения Node
обычно пишутся с применением таких фреймворков, как Express (http://expressjs.
com/). В главе 6 рассматриваются основные серверные фреймворки, доступные для
Node. Глава 7 посвящена Express и Connect, а темой главы 8 станет шаблонизация
веб-приложений.
Чтобы создать простейшее веб-приложение Express, создайте новый каталог и уста­
новите модуль Express:
mkdir hello_express
cd hello_express
npm in it -y
npm i express --save

Теперь включите следующий код JavaScript в файл с именем server.js.

Листинг 1.3. Веб-приложение Node


const express = req uire('express');
const app = express();

a p p .g e t('/', (req, res) => {


res.send('H ello World!');
});
app.listen(3000, () => {
console.log('Express web app on localhost:3000');
});

Введите команду npm s t a r t , и в вашей системе появляется веб-сервер Node, рабо­


тающий на порту 3000. Открыв адрес http://localhost:3000 в браузере, вы увидите
текст из строки res.send.
Node также играет важную роль в мире разработки интерфейсов, потому что это
основной инструмент, используемый при транспиляции других языков (например,
TypeScript в JavaScript). Транспиляторы преобразуют код, написанный на одном
высокоуровневом языке, на другой язык; в этом отношении они отличаются от тра­
диционных компиляторов, которые преобразуют код с высокоуровневого язы ка на
38 Глава 1. Знакомство с Node.js

низкоуровневый. Глава 4 посвящена системам фронтэнда; в ней рассматриваются


сценарии npm, Gulp и webpack.
Не вся веб-разработка связана с построением веб-приложений. Иногда разработ­
чику приходится решать такие задачи, как извлечение данных со старого сайта,
которые должны использоваться при его обновлении. Приложение Б полностью
посвящено извлечению веб-данных для демонстрации возможностей исполни­
тельной среды JavaScript в Node с моделью DOM (D ocum ent O bjet Model), а также
использования Node за пределами привы чной «ком фортной зоны» типичны х
веб-приложений Express. Если же вы просто хотите быстро создать простейшее
веб-приложение, в главе 3 приведено обучающее руководство по построению
веб-приложений Node.

1.5.2. Средства командной строки и демоны


Node используется для написания средств командной строки, таких как менеджеры
процессов и транспиляторы JavaScript, используемые разработчиками JavaScript.
Однако Node также может стать удобной платформой для написания удобных
средств командной строки, которые выполняют другие операции, включая преоб­
разование графики, и сценариев для управления воспроизведением мультимедий­
ных материалов.
Н иже приведен простой пример, который вы можете опробовать. Создайте новый
файл с именем cli.js и добавьте в него следующие строки:
const [nodePath, scriptPath, name] = process.argv;
console.log('H ello', name);

Запустите сценарий командой c l i . j s yourName; вы увидите сообщ ение H ello


yourName. В сценарии функциональность деструктуризации ES2015 используется
для извлечения третьего аргумента из p ro c e s s .a rg v . Объект p ro c ess доступен
в любой программе; с его помощью программа получает аргументы, заданные
пользователем при запуске.
У программ командной строки Node также имеются полезные возможности. Если
добавить в начало программы строку, которая начинается с #!, и предоставить фай­
лу разрешение на исполнение (chmod +x c l i . j s ) , вы сможете заставить командный
интерпретатор использовать Node при запуске программы. После этого программы
Node можно будет запускать точно так же, как любые другие сценарии командного
интерпретатора. Для систем семейства Unix добавляется следующая строка:
#!/usr/bin/env node

При таком использовании Node вы сможете заменить свои сценарии командного


интерпретатора командами Node. Это означает, что Node можно будет исполь­
зовать с любыми другими инструментами командной строки, включая фоновые
1.5. Три основных типа программ Node 39

программы. Программы Node могут запускаться при помощи cron или работать
в фоновом режиме как демоны.
Если все эти термины вам незнакомы, не беспокойтесь: глава 11 познакомит вас
с написанием программ командной строки, и вы узнаете, как программы этого
типа помогают Node проявить свои сильные стороны. Например, в программах
командной строки широко используются потоки данных (stream s) как универсаль­
ный API, а потоки также относятся к числу самых полезных возможностей Node.

1.5.3. Настольные приложения


Если вы работали с текстовым редактором Atom или Visual Studio Code — знайте,
что вы все это время использовали Node. Фреймворк Electron использует Node для
реализации своих операций, поэтому каждый раз, когда требуется выполнить ввод/
вывод с диском или сетью, Electron обращается к Node. Также Electron использует
Node для управления зависимостями; это означает, что вы сможете добавлять па­
кеты из npm в проекты Electron.
Чтобы быстро опробовать Electron на практике, клонируйте репозиторий Electron
и запустите приложение:
g it clone https://github.com /electron/electron-quick-start
cd electron-quick-start
npm in s ta ll && npm sta rt
curl localhost:8081

О том, как написать приложение на базе Electron, рассказано в главе 12.

1.5.4. Приложения, хорошо подходящие для Node


М ы рассмотрели некоторы е разновидности прилож ений, которы е можно п о­
строить с Node, но сущ ествуют некоторые типы приложений, в которых Node
особенно зам етно превосходит конкурентов. N ode обычно и спользуется для
создания веб-прилож ений реального времени; к этой категории мож ет отно­
ситься что угодно, от пользовательских прилож ений (скажем, чат-серверов) до
внутренних систем сбора аналитики. Так как ф ункции в Jav a S c rip t являю тся
полноправными объектами, а в Node имеется встроенная модель событий, на­
писание асинхронных программ реального времени проходит более естественно,
чем в других язы ках сценариев.
Если вы строите традиционные веб-приложения MVC (M odel-V iew -Controller),
Node хорошо подходит и для них. На базе Node строятся многие популярные си­
стемы ведения блогов, например Ghost (https://ghost.org/); платформа Node хорошо
зарекомендовала себя для подобных веб-приложений. Стиль разработки отличается
от системы WordPress, построенной с использованием PHP, но Ghost поддерживает
40 Глава 1. Знакомство с Node.js

многие аналогичные возможности, включая шаблоны и многопользовательские


средства администрирования.
Node также может делать то, что в других языках делается намного сложнее. Node
базируется на JavaScript, поэтому в Node возможно выполнение браузерного кода
JavaScript. Сложные клиентские приложения могут адаптироваться для выполнения
на сервере Node, что позволяет серверам заранее генерировать веб-приложения; это
сокращает время визуализации страниц в браузере и упрощает работу поисковых
систем.
Наконец, если вы занимаетесь построением настольных или мобильных приложе­
ний, опробуйте фреймворк Electron, работающий на базе Node. В наши дни, когда
веб-интерфейсы пользователя не уступают настольным, настольные приложения
Electron могут конкурировать с платформенными веб-приложениями при сокращен­
ном времени разработки. Electron также поддерживает три основные платформы,
поэтому ваш код будет работать в Windows, Linux и macOS.

1.6. Заключение
О Node — неблокирующая, управляемая событиями платформа для построения
приложений JavaScript.
О В качестве исполнительной среды JavaScript используется ядро V8.
О Библиотека libuv обеспечивает быстрый кросс-платформенный неблокирующий
ввод/вывод.
О Node содержит небольшую стандартную библиотеку — базовые модули, которые
добавляют в JavaScript поддержку сетевого и дискового ввода/вывода.
О В поставку Node входит отладчик и менеджер зависимостей (npm).
О Node используется для построения веб-приложений, средств командной строки
и даже настольных приложений.
Основы программирования
Node

В отличие от многих платформ с открытым кодом, Node легко настраивается и не


предъявляет особых требований к памяти и дисковому пространству. Программи­
рование не требует применения сложных интегрированных средств разработки
или систем построения. Тем не менее некоторые фундаментальные знания сильно
помогут вам в начале работы. В этой главе рассматриваются две основные проблемы,
с которыми сталкиваются начинающие разработчики Node:
О организация кода;
О асинхронное программирование.
В этой главе будут представлены важные приемы асинхронного программирова­
ния, которые позволят вам держать под контролем процесс выполнения вашего
приложения. Вы научитесь:
О реагировать на одноразовые события;
О реагировать на повторяющиеся события;
О определять последовательность отработки асинхронной логики.
Но для начала посмотрим, как для решения проблемы организации кода исполь­
зуются модули — основной механизм Node для организации кода и его упаковки
для удобства повторного использования.

2.1. Структурирование и повторное


использование функциональности Node
При создании приложения, с использованием Node или без, часто наступает момент,
когда хранить весь код в одном файле становится слишком неудобно. В традицион­
ном решении этой проблемы, представленном на рис. 2.1, разработчик брал файл
с большим объемом кода и разбивал его на отдельные файлы.
42 Глава 2. Основы программирования Node

Служебные функции

і index.js 1----- ► lib/utilityFunctions.js


Служебные функции |

Команды 1 Команды

index.js lib/commands.js
__________________________ L.

/ /
Весь код в одном файле Взаимосвязанная логика
группируется в разных файлах

Рис. 2.1. В коде удобнее ориентироваться, если он разбит по разным каталогам


и файлам, а не хранится в одном огромном файле

В реализациях некоторых языков (таких как P H P и Ruby) внедрение логики из


другого файла (включаемого) означает, что вся логика, выполняемая в файле, влияет
на глобальную область видимости. Все создаваемые переменные и все функции,
объявленные во включаемом файле, заменяют переменные и функции, созданные
и объявленные приложением.
Предположим, вы программируете на PHP, и ваше приложение содержит следу­
ющую логику:
function uppercase_trim($text) {
return trim (strtoupper($text));
}
include('string_handlers.php');

Если файл string_handlers.php также попытается определить функцию uppercase_trim,


вы получите сообщение об ошибке:
Fatal error: Cannot redeclare uppercase_trim()

В P H P эта проблема решается за счет использования пространств имен; аналогич­


ную функциональность Ruby предлагает в виде модулей. Однако Node в принципе
избегает эту потенциальную проблему, не предоставляя простых возможностей для
случайного загрязнения глобального пространства имен.

ПРОСТРАНСТВА ИМЕН PHP, МОДУЛИ RUBY

Пространства имен P H P описаны в руководстве по язы ку P H P по адресу


http://php .n et/m anual/en/language.nam espaces.php . М одули Ruby рассма­
триваются в документации Ruby: http://ruby-doc.org/core-2.3.1/M odule.htm l.
2.1. Структурирование и повторное использование функциональности Node 43

Модули Node упаковывают код для повторного использования, но при этом не и з­


меняют глобальную область видимости. Предположим, вы разрабатываете систему
управления контентом (C M S) с открытым кодом на языке P H P и хотите исполь­
зовать стороннюю библиотеку API, которая не использует пространства имен.
Библиотека может содержать класс с таким же именем, как у вашего приложения,
и этот класс нарушит работу вашего приложения, если только вы не переименуете
свой класс в приложении или библиотеке. Однако изменение имени класса в при­
ложении может создать проблемы у других разработчиков, которые пользуются
вашей CM S-системой в своих проектах. После переименования класса в библиотеке
вам придется помнить о необходимости повторять этот трюк каждый раз, когда вы
обновляете библиотеку в дереве исходного кода вашего приложения. Конфликты
имен — проблема, которую лучше избегать.
Имена модулей позволяют выбрать, какие функции и переменные из включенного
ф айла будут доступны для приложения. Если модуль возвращает более одной
функции или переменной, модуль может указать их заданием свойств объекта
с именем e x p o rts . Если модуль возвращ ает одну ф ункцию или переменную,
вместо этого можно задать свойство m odule.exports. Н а рис. 2.2 показано, как
работает эта схема.

J
Логика модуля заполняет
module.exports или exports

Рис. 2.2. Заполнение свойства module.exports или объекта exports позволяет модулю
выбрать, какая информация должна быть доступна приложению

Если описание кажется запутанным, не беспокойтесь; мы разберем некоторые при­


меры в этой главе. Избегая загрязнения глобальной области видимости, система
модулей Node предотвращает конфликты имен или упрощает повторное использо­
вание кода. После этого модули могут быть опубликованы в реестре npm (менеджер
пакетов), сетевой подборке готовых модулей Node, и распространяться в сообществе
Node. При этом пользователям модулей не нужно беспокоиться о том, что модуль
случайно заменит переменные и функции другого модуля.
44 Глава 2. Основы программирования Node

Чтобы помочь вам в организации логики по модулям, мы рассмотрим следующие


вопросы:
О как создаются модули;
О как модули хранятся в файловой системе;
О о чем нужно знать при создании и использовании модулей.
Чтобы с ходу взяться за изучение системы модулей Node, мы создадим новый про­
ект Node, а затем определим для него простой модуль.

2.2. Создание нового проекта Node


Создать новый проект Node несложно: создайте папку и выполните команду npm
i n i t . И все! Команда npm задаст несколько вопросов, вы можете ответить на все
вопросы утвердительно. Полный пример:
mkdir my_module
cd my_module
npm in it -y

Ф лаг -y означает подтверждение (yes). Таким образом, npm создаст файл package.
json со значениями по умолчанию. Если вы хотите полностью управлять процессом
создания проекта, опустите флаг -y; npm задаст ряд вопросов о лицензии проекта,
имени автора и т. д. После того как все будет сделано, просмотрите содержимое
файла package.json. Вы можете отредактировать его вручную, но помните: файл
должен содержать корректную разметку JSON.
Пустой проект успешно создан, теперь можно переходить к созданию модуля.

2.2.1. Создание модулей


Модуль может представлять собой как отдельный файл, так и каталог с одним или
несколькими файлами (рис. 2.3). Если модуль оформлен в виде каталога, основному
файлу в этом каталоге, который будет обрабатываться, обычно присваивается имя
index.js (хотя и его можно переопределить: см. раздел 2.5).

Ё m y_m odule.Js ▼ сЭ m y_m odule

Рис. 2.3. Модули Node могут создаваться либо в виде файлов (пример 1),
либо в виде каталогов (пример 2)

Чтобы создать типичный модуль, создайте файл, определяющий свойства объекта


ex p o rts с любыми данными (строками, объектами, функциями и т. д.).
2.2. Создание нового проекта Node 45

Чтобы продемонстрировать, как создаются базовые модули, мы добавим простей­


шую функциональность перерасчета валют в файл currency.js. Этот файл, представ­
ленный в следующем листинге, содержит две функции для пересчета канадских
долларов в американские, и наоборот.

Листинг 2.1. Определение модуля Node (currency.js)


const canadianDollar = 0.91;
Функция canadianToUS определяется
function roundTwo(amount) { Bexports, чтобыона могла использоваться
return Math. round(amount * 100) / 100; в коде, требующемэтот модуль.
exports.canadianToUS = canadian => roundTwo(canadian * canadianDollar);-<—
exports.USToCanadian = us => roundTwo(us / canadianDollar); -<■ Функция USToCanadian
также определяется
в exports.

Обратите внимание: задаются только два свойства объекта exports. Таким обра­
зом, для приложения, включающего модуль, будут доступны только две функции:
canadianToUS и USToCanadian. Переменная can ad ian D o llar закрыта от внешнего
доступа: она влияет на логику работы canadianToUS и USToCanadian, но приложение
не сможет обратиться к ней напрямую.
Ч тобы использовать новы й модуль в программе, восп ользуй тесь ф ункцией
Node re q u ire и передайте путь к нужному модулю в аргументе. Node выполняет
синхронны й поиск м одуля и загруж ает его содержимое. С начала N ode ищет
файлы среди базовых модулей, затем в текущем каталоге, и наконец, в каталоге
node modules.

REQUIRE И СИНХРОННЫЙ ВВОД/ВЫВОД

R equire — одна из немногих синхронных операций ввода/вы вода в Node.


Так как модули часто используются и обычно включаются в начале файла,
синхронность require помогает сохранить чистоту, упорядоченность и удо­
бочитаемость кода.
Старайтесь избегать использования require в частях вашего приложения, вы­
полняющих интенсивный ввод/вывод. Любой синхронный вызов блокирует
работу, не позволяя Node делать что-либо до завершения вызова. Например,
если вы запустили H T T P -сервер, выполнение require для каждого входного
запроса снизит быстродействие приложения. Как правило, именно по этой
причине require и другие синхронные операции используются только при
начальной загрузке приложения.

В файле test-currency.js (листинг 2.2) модуль currency.js включается вызовом require.


46 Глава 2. Основы программирования Node

Листинг 2.2. Включение модуля (test-currency.js)


Использует функциюcanadianToUS
модуля currency. Впути означает, что модуль находится в одном
каталоге со сценарием приложения.
const currency = req u ire('./cu rren cy '); < -
console.log('50 Canadian dollars equals th is amount of US d o lla rs :');
►console.log(currency.canadianToUS(50));
console.log('30 US dollars equals th is amount of Canadian d o lla rs :');
console.log(currency.USToCanadian(30)); -<----
Использует функциюUSToCanadian
модуля currency.

Включение модуля с путем, начинающимся с . / , означает, что если вы создаете


свой сценарий приложения с именем test-currency.js в каталоге currency_app, то файл
модуля currency.js (рис. 2.4) также должен существовать в каталоге currency_app. При
необходимости расширение .js подставляется по умолчанию, так что при желании
его можно опустить. Если расширение .js не указано, Node также проверит файл
.json с заданным именем. Ф айлы JS O N загружаются как объекты JavaScript.

Рис. 2.4. Если аргумент require начинается с ./, Node проверяет тот каталог,
в котором находится выполняемый файл

После того как Node найдет и обработает ваш модуль, функция req u ire возвращает
содержимое объекта e x p o rts, определенного в модуле. После этого вы сможете
использовать две функции, возвращаемые модулем, для пересчета сумм в другую
валюту.
Если вы захотите определить структуру модулей, разместите модули в подкаталогах.
Если, например, вы хотите хранить модуль currency в папке lib/, приведите строку
re q u ire к следующему виду:
const currency = re q u ire ('./lib /c u rre n c y ');

Заполнение объекта exports модуля предоставляет простой механизм распределе­


ния повторно используемого кода по разным файлам.
2.3. Настройка создания модуля с использованием module.exports 47

2.3. Настройка создания модуля


с использованием module.exports
Хотя заполнение объекта exports функциями и переменными хорошо подходит для
большинства случаев, время от времени требуется создать модуль с отклонением
от этой модели.
Например, модуль пересчета валют из предыдущего раздела можно переписать
таким образом, чтобы он возвращал одну функцию-конструктор Currency вместо
объекта, содержащего функции. Объектно-ориентированная реализация может
выглядеть примерно так:
const Currency = req u ire('./cu rren cy ');
const canadianDollar = 0.91;
const currency = new Currency(canadianDollar);
console.log(currency.canadianToUS(50));

Возвращение функции из re q u ire (вместо объекта) сделает ваш код более элегант­
ным — если это единственное, что требуется от модуля.
Казалось бы, чтобы создать модуль, возвращающий одну переменную или ф унк­
цию, нужно присвоить exports то, что вы хотите вернуть. Однако такое решение
не сработает, потому что Node не ожидает, что exports будет присваиваться любой
другой объект, функция или переменная. Код модуля в следующем листинге пы ­
тается присвоить exports функцию (листинг 2.3).

Листинг 2.3. Модуль работать не будет


class Currency {
constructor(canadianDollar) {
this.canadianDollar = canadianDollar;
}
roundTwoDecimals(amount) {
return Math.round(amount * 100) / 100;
}
canadianToUS(canadian) {
return this.roundTwoDecimals(canadian * this.canadianDollar)
}
USToCanadian(us) {
return this.roundTwoDecimals(us / this.canadianDollar);
}
}
exports = Currency; -<------- Ошибка; Node не позволяет переписывать exports.

Чтобы приведенный код модуля работал так, как ожидается, нужно заменить exports
на module .exports. Механизм module .exports позволяет экспортировать одну перемен­
ную, функцию или объект. Если вы создаете модуль, который заполняет как exports,
так и m odule.exports, то возвращается m odule.exports, а exports игнорируется.
48 Глава 2. Основы программирования Node

ЧТО В ДЕЙСТВИТЕЛЬНОСТИ ЭКСПОРТИРУЕТСЯ

В конечном итоге в вашем прилож ении экспортируется m odule.exports.


exports задается как глобальная ссылка на module.exports — изначально это
пустой объект, к которому можно добавлять свойства. exports.myFunc — со­
кращенная запись для module.exports.myFunc.
В результате присваивание exports другой ссылки разрывает связь между
module.exports и exports. Так как экспортируется module.exports, exports ра­
ботает не так, как ожидается, — он уже не ссылается на module.exports. Чтобы
сохранить эту связь, снова включите в module.exports ссылку на exports:
module.exports = exports = Currency;
Использовав exports или module.exports в зависимости от ваших потребно­
стей, вы сможете распределить функциональность по модулям и избежать
проблем с постоянно растущими сценариями приложения.

2.4. Повторное использование модулей


с папкой node_modules
Включение модулей с указанием их местонахождения в файловой системе от­
носительно прилож ения полезно для организации кода, специфического для
данного прилож ения. И наче дело обстоит с кодом, предназначенны м для и с­
пользован и я в нескольких прилож ени ях или распространения среди других
разработчиков. Node имеет уникальны й механизм повторного использования
кода, которы й позволяет вклю чать модули без точной инф орм ации об их м е­
стонахождении в файловой системе. Этот механизм основан на использовании
каталогов node_modules.
В приведенном ранее примере вклю чался модуль ./c u r r e n c y . Если убрать . /
и включить просто currency, Node начинает искать этот модуль по схеме, пред­
ставленной на рис. 2.5.
Переменная окружения NODE_PATH позволяет выбрать альтернативные каталоги для
хранения модулей Node. Если переменная NODE_PATH используется, ей должен быть
присвоен список каталогов, разделенных символом точки с запятой (;) в Windows
или двоеточием (:) в других операционных системах.

2.5. Потенциальные проблемы


Хотя система модулей Node устроена достаточно прямолинейно, вы должны учи­
тывать два обстоятельства.
2.5. Потенциальные проблемы 49

Рис. 2.5. Последовательность поиска модуля


50 Глава 2. Основы программирования Node

Во-первых, если модуль представляет собой каталог, ф айл в каталоге модуля,


который будет обработан, должен назы ваться index.js, если только обратное не
указано в файле package.json в каталоге модуля. Чтобы задать другой файл вме­
сто index.js, файл package.json должен содержать данные JS O N (JavaScript Object
N otation), определяющие объект с ключом main и значением, которое определяет
путь к основному файлу в каталоге модуля. Эти правила обобщены в блок-схеме
на рис. 2.6.

Рис. 2.6. Файл package.json в каталоге модуля позволяет определить модуль


с использованием файла, отличного от index.js

Пример файла package.json, который назначает основным файлом currency.js:

{
"main": "currency.js"
}

Другое обстоятельство, о котором следует помнить, —это способность Node кэширо­


вать модули как объекты. Если два файла в приложении включают один и тот же мо­
дуль, то данные, возвращенные для первого вызова require, будут сохранены в памяти,
поэтому второму вызову require не нужно будет проверять исходные файлы модуля.
А это означает, что загрузка модуля с вызовом req u ire в том же процессе вернет тот
же объект. Представьте, что вы создаете веб-приложение MVC, у которого имеется
2.6. Средства асинхронного программирования 51

основной объект приложения. Вы можете настроить этот объект, экспортировать его,


а затем включить в любой точке проекта вызовом require. Если в объект приложения
были добавлены полезные данные конфигурации, вы сможете обратиться к ним из
других файлов — при условии, что проект имеет следующую структуру каталогов:
проект
app.j s
models
p o st.js

На рис. 2.7 показано, как работает эта схема.

app.js models/post.js

Рис. 2.7. Общий объект приложения в веб-приложении

Лучший способ освоиться с системой модулей Node — поэкспериментировать с ней


и самостоятельно проверить поведение, описанное в этом разделе. Теперь, когда вы
в общих чертах понимаете, как работают модули, можно переходить к средствам
асинхронного программирования.

2.6. Средства асинхронного программирования


Если вы когда-либо занимались разработкой фронтэнда, в которой события (н а­
пример, щелчки мышью) инициируют выполнение логики, — значит, вы уже за­
нимались асинхронным программированием. Асинхронное программирование на
стороне сервера работает по тем же принципам: происходят события, по которым
срабатывает ответная логика. В мире Node для управления логикой ответов и с­
пользуются две популярные модели: обратные вызовы и слушатели событий.
Обратные вызовы обычно определяют логику для одноразовых ответов. Например,
при выполнении запроса к базе данных можно назначить функцию обратного вы ­
зова, определяющую, что нужно сделать с результатами запроса. Ф ункция обрат­
ного вызова может вывести результаты, выполнить с ними некие вычисления или
выполнить другую функцию обратного вызова, использовав результаты запроса
в качестве аргумента.
52 Глава 2. Основы программирования Node

Слушатели событий представляют собой обратные вызовы, связанные с концеп­


туальной сущностью (событием). Скажем, щелчок кнопкой мыши — это событие,
которое обрабатывается в браузере. А код Node в сервере H TTP генерирует событие
req u est при выдаче запроса HTTP. Вы можете прослушать событие req u est и до­
бавить логику ответа. В следующем примере функция handleRequest будет вызы­
ваться каждый раз, когда генерируется событие request; для этого вызов метода
E v en tE m itter.p ro to ty p e.o n связывает слушателя события с сервером:
serv er.o n ('req u est', handleRequest)

Экземпляр сервера H TTP в Node является примером генератора событий — класса


(E ventE m itter), который может использоваться при наследовании и который до­
бавляет возможность генерирования и обработки событий. Многие аспекты базовой
функциональности Node наследуются от EventEm itter; вы также можете создать
собственный генератор событий.
Итак, мы выяснили, что логика ответа обычно организуется в Node одним из двух
способов. Теперь можно перейти непосредственно к асинхронному программиро­
ванию; будут рассмотрены следующие темы:
О как обрабатывать одноразовые события с обратными вызовами;
О как реагировать на повторяющиеся события при помощи слушателей событий;
О как преодолеть некоторые трудности асинхронного программирования.
Начнем с одного из самых распространенных способов организации асинхронного
кода: функций обратного вызова.

2.7. Обработка одноразовых событий


в обратных вызовах
Ф ункция обратного вызова (callback) — функция, которая передается в аргументе
асинхронной функции и описывает, что нужно делать по завершении асинхронной
операции. Функции обратного вызова часто применяются в разработке Node —чаще,
чем генераторы событий, и с ними проще работать.
Чтобы продемонстрировать использование обратных вызовов в приложении, по­
смотрим, как создать простой сервер HTTP, который:
О асинхронно читает заголовки последних сообщений, хранящиеся в файле JSON;
О асинхронно читает базовый шаблон HTML;
О строит страницу HTM L с заголовками;
О отправляет страницу HTM L пользователю.
Примерный результат показан на рис. 2.8.
2.7. Обработка одноразовых событий в обратных вызовах 53

Файл JSO N (titles.json), приведенный в листинге 2.4, отформатирован в виде массива


с заголовками сообщений.

• D localhost:8000 х _ Alex

f- -> С О lo c a lh o st:8 0 0 0 :

Latest Posts
• Kazakhstan is a huge country... what goes on there?
• This weather is making me craaazy
• My neighbor sort o f howls at night

Рис. 2.8. HTML-ответ от веб-сервера, который читает заголовки


из файла JSON и возвращает результаты в виде веб-страницы

Листинг 2.4. Список заголовков сообщений


[
"Kazakhstan is a huge country... what goes on there?",
"This weather is making me craaazy",
"My neighbor sort of howls at night"
]

Ф айл шаблона H TM L (template.html) из листинга 2.5 включает только базовую


структуру для вставки заголовков сообщений.

Листинг 2.5. Базовый шаблон HTML для вывода заголовков


<!doctype html>
<html>
<head></head>
<body>
<h1>Latest Posts</h1>
<ul><li>%</li></ul> < ------- %заменяется данными заголовка.
</body>
</html>

Код чтения данных из файла JSO N и построения веб-страницы приведен в листин­


ге 2.6 (файл blog_recent.js).

Листинг 2.6. Использование обратных вызовов в простом приложении


Создает сервер HTTPи использует обратный
вызовдля определения логики ответа.
const http = re q u ire ('h ttp ');
const fs = re q u ire ('fs '); Читает файл JSON и использует обратный
http.createServer((req, res) => { < ­ вызовдля определения того, как поступить
i f (req.url == ' / ' ) { с его содержимым.
f s .r e a d F ile ( './title s .js o n ', (err, data) => {
54 Глава 2. Основы программирования Node

i f (err) { Если возникает ошибка, зарегистрировать ее


console.error(err); и вернуть клиентусообщение «Server Error».
res.end('Server E rror');
} else {
const t i t l e s = JSON.parse(data.toString()); ■<— Разбирает данные из текста JSON.
fs.rea d F ile('./tem p late.h tm l', (err, data) => { Читает шаблон HTML
i f (err) { и использует обратный
console.error(err); вызов призавершении
res.end('Server E rror'); загрузки.
} else {
const tmpl = data.toString();
const html = tm pl.replace('% ', title s .jo in ( '< /li> < li> ') ) ;
Собирает
res.writeHead(200, { 'Content-Type': 'text/htm l' });
страницу HTML
res.end(html); <■ Отправляет страницу с заголовками
} HTMLпользователю. сообщений.
});
}
});
}
} ).liste n '127. 3.1');

В этом примере используются три уровня вложенности обратных вызовов:


http.createServer((req, res) => { ...
f s .r e a d F ile ( './title s .js o n ', (err, data) => { ...
fs.rea d F ile('./tem p late.h tm l', (err, data) => { ...

В трехуровневой структуре нет ничего плохого, но чем больше уровней вы и с­


пользуете, тем более громоздко выглядит ваш код и тем больше проблем возникает
с его рефакторингом и тестированием, поэтому глубину влож ения желательно
ограничить. Создавая именованные функции для обработки отдельных уровней, вы
можете выражать ту же логику способом, который требует больше строк кода — но
этот код будет проще в сопровождении, тестировании и рефакторинге. Следующий
листинг функционально эквивалентен листингу 2.6.

Листинг 2.7. Сокращение вложенности за счет определения


промежуточных функций
const http = re q u ire ('h ttp '); Клиентский запрос
const fs = re q u ir e ('f s '); поступает здесь.
http.createServer((req, res) => { < ----
g etT itles(res); < ---- Управление передается getTitles.
}).listen(8000, '127.0.0.1');

function getT itles(res) {


f s .r e a d F ile ( './title s .js o n ', (err, data) => { getTitles извлекает
i f (err) { заголовки и передает
hadError(err, res); управление getTemplate.
} else {
getTemplate(JSON.parse(data.toString()), res);
}
2.7. Обработка одноразовых событий в обратных вызовах 55

});
}
function getTem plate(titles, res) { getTemplate читает файл
f s . 2ead F ile('./tem plate.htm l', (e rr, data) => { шаблона и передает
i f (err) { управление formatHtml.
hadE22 o r(e 22 , res);
} else {
formatHtm l(titles, data.toS tring(), res); formatHtml читает заголовки
} и шаблон, после чего строит ответ
}); для клиента.
}
function formatHtm l(titles, tmpl, res) {
const html = tm pl.replace('% ', title s .jo in ( '< /li> < li> ') ) ;
2es.w2ІteHead(200, {'Content-Type': 'tex t/h tm l'});
res.end(html);
}
function hadE rror^rr, res) { < - Если возникает ошибка,
C0ns0le.e2202(e22); hadError выводит ее на
res.end('Server E rror'); консоль и возвращает клиенту
} сообщение «Server Error».

Уровень вложенности также можно сократить блоками i f / e l s e с другой идиомой


в разработке Node — ранним возвратом из функции. Следующий листинг ф унк­
ционально эквивалентен предыдущему, но избегает дальнейш его влож ения за
счет раннего возврата управления. Кроме того, он явно указывает, что функция не
должна продолжать выполнение.

Листинг 2.8. Сокращение вложения за счет раннего возврата


const http = re q u ire ('h ttp ');
const fs = re q u ire ('fs ');
http.createS erver^req, res) => {
g etT itles(res);
}).listen(8000, '127.0.0.1');
function getT itles(res) {
f s .r e a d F ile ( './title s .js o n ', (err, data) => {
i f (err) return hadE rror^rr, res); Вместо того чтобысоздавать ветвь else,
getTemplatepSON.parse^ata.toStringQ), res); вывозвращаете управление, потому что
}); вслучае возникновения ошибки
} продолжать выполнение этой функции
function getTem plate(titles, res) { не нужно.
fs.rea d F ile('./tem p late.h tm l', (e rr, data) => {
i f (err) return hadError(err, res);
formatH tm l(titles, data.toS tring(), res);
});
}
function formatHtm l(titles, tmpl, res) {
const html = tm pl.replace('% ', title s .jo in ( '< /li> < li> ') ) ;
res.writeHead(200, { 'Content-Type': 'tex t/h tm l'} );
res.end(html);
}
function hadE rror^rr, res) {
56 Глава 2. Основы программирования Node

console.error(err);
res.end('Server E rror');
}

Итак, теперь вы знаете, как использовать обратные вызовы для обработки одно­
разовых событий для таких задач, как определение ответов при чтении файлов
и запросов веб-серверов, и мы можем перейти к организации событий с использо­
ванием генераторов событий.

СОГЛАШЕНИЯ NODE ДЛЯ АСИНХРОННЫХ ОБРАТНЫХ ВЫЗОВОВ

Б ольш инство встроенны х модулей N ode использует обратны е вызовы


с двумя аргументами: первый аргумент предназначен для ошибки (если
она возникнет), а второй — для результатов. Аргумент ошибки error часто
сокращается до err.
Типичный пример сигнатуры функции:
const fs = re q u ire ('fs ');
f s .r e a d F ile ( './title s .js o n ', (err, data) => {
i f (err) throw err;
/ / Если ошибки не было, что-то сделать с данными
});

2.8. Обработка повторяющихся событий


с генераторами событий
Генераторы событий инициируют события и включают возможность обработки
этих событий при их инициировании. Некоторые важные компоненты Node API —
серверы HTTP, серверы T C P и потоки — реализую тся как генераторы событий.
Разработчик также может создавать собственные генераторы.
Как упоминалось ранее, события обрабатываются при помощи слушателей. Слуша­
тель (listener) представляет связь события с функцией обратного вызова, которая
выполняется каждый раз при возникновении события. Например, у сокета TCP
в Node имеется событие с именем data, которое инициируется каждый раз при по­
явлении новых данных в сокете:
sock et.o n ('d ata', handleData);

Посмотрим, как использовать события data для создания эхо-сервера.

2.8.1. Пример генератора событий


Простой пример повторяющихся событий встречается в эхо-сервере. Когда вы от­
правляете данные эхо-серверу, он просто выводит полученные данные (рис. 2.9).
2.8. Обработка повторяющихся событий с генераторами событий 57

П П 2. Shell О

Рис. 2.9. Эхо-сервер повторяет отправленные ему данные

В листинге 2.9 приведен код, необходимый для реализации эхо-сервера. Каждый


раз при подключении клиента создается сокет. Сокет представляет собой генератор
событий, к которому можно добавить слушателя методом on для реакции на со­
бытия data. Эти события данных генерируются каждый раз при появлении новых
данных в сокете.

Листинг 2.9. Использование метода on для реакции на события

const net = i^ q ijii^ C i^ t'); События data обрабатываются каждый


const server = net.createServer^ocket => { раз при чтении н°выхдашых.
so cket.on('data', data => { -<----
socket.w rite(data); <-
Данные записываются
на сторону клиента.
server.listen(8888);

Эхо-сервер запускается следующей командой:


node echo_se2 ve2 .]s

После того как эхо-сервер будет запущен, вы можете подключиться к нему следу­
ющей командой:
teln et 127.0.0.1 8888

Каждый раз, когда подключенный сеанс teln et отправляет данные серверу, эти
данные будут продублированы в сеансе telnet.

TELNET В СИСТЕМЕ WINDOWS


Если вы используете операционную систему Microsoft Windows, поддержка telnet
может не устанавливаться по умолчанию, и вам придется устанавливать ее само­
стоятельно. Инструкции для различных версий Windows доступны на Technet: http://
mng.bz/egzr.
58 Глава 2. Основы программирования Node

2.8.2. Реакция на событие, которое должно происходить


только один раз
Слушатели могут определяться для многократной реакции на события, как в пре­
дыдущем примере, или же для однократной реакции. В следующем листинге,
использующем метод once, приведенный ранее пример с эхо-сервером и зм еня­
ется так, чтобы он воспроизводил только первый фрагмент отправленных ему
данных.

Листинг 2.10. Использование метода once для реакции на одно событие


const net = re q u ire ('n e t');
const server = net.createServer(socket => {
socket.once('data', data => { < ------- Событие data будет обработано всего один раз.
socket.w rite(data);
});
});
server.listen(8888);

2.8.3. Создание генераторов событий:


публикация/подписка

В предыдущем примере использовался встроенный Node API, использующий ге­


нераторы событий. Однако встроенный модуль событий Node позволяет создавать
собственные генераторы событий.
Следующий код определяет генератор событий channel с одним слушателем, ко­
торый реагирует на присоединение пользователя к каналу. Обратите внимание на
использование on (или в более длинной альтернативной форме ad d L isten er) для
добавления слушателя к генератору событий:
const EventEmitter = require('events').EventEm itter;
const channel = new EventEmitter();
channel.on('join', () => {
console.log('Welcome!');
});

Однако функция обратного вызова jo in никогда не будет вызвана, потому что ника­
кие события еще не генерируются. Добавьте в листинг строку, которая инициирует
событие функцией emit:
channel.em it('join');

ИМЕНА СОБЫТИЙ
События — ключи, с которыми может быть связано любое строковое значение: data,
join и вообще произвольное длинное имя. Только одно событие с именем error играет
особую роль; вскоре мы рассмотрим его.
2.8. Обработка повторяющихся событий с генераторами событий 59

Давайте посмотрим, как реализовать собственную логику публикации/подписки


с использованием EventEm itter для создания канала передачи информации. З а ­
пустив сценарий из листинга 2.11, вы получите простой чат-сервер. Канал чат-
сервера реализуется как генератор событий, который реагирует на события jo in ,
генерируемые клиентами. Когда клиент присоединяется к каналу, логика слушателя
jo in в свою очередь добавляет дополнительного слушателя для данного клиента
к каналу широковещательного события, который будет записывать все рассылаемые
сообщения в клиентский сокет. Имена типов событий (такие, как jo in и broadcast)
выбираются абсолютно произвольно. При желании вы можете использовать другие
имена для этих типов событий.

Листинг 2.11. Простая система «публикация/подписка» с использованием


генератора событий
const events = req u ire('ev en ts');
const net = re q u ire ('n e t');
const channel = new events.EventEmitter(); Добавляет для события join
channel.clients = {}; слушателя, который сохраняет
channel.subscriptions = {}; объект client, чтобы приложение
channel.on('join', function(id, client) { могло отправитьданные
th is .c lie n ts [id ] = client; обратно пользователю.
th is.su b scriptions[id] = (senderld, message) => {
i f (id != senderld) { <-
this.clients[id].w rite(m essage); Игнорирует данные, если они передаются
напрямуюв широковещательной рассылке.
}
};
th is.o n ('b ro ad cast', th is.su b scrip tio n s[id ]); ■<— Добавляет слушателя,
}); относящегося ктекущему
const server = net.createServer(client => { пользователю, для события
const id = '${client.remoteAddress}:${client.remotePort} broadcast.
channel.em it('join', id, clie n t); -<- Генерирует событие join при подключении пользователя
c lie n t.o n ('d a ta ', data => { ксерверу, с указанием идентификатора пользователя
data = data.toString(); иобъекта client.
channel.em it('broadcast', id, data);
}); Генерирует событие
}); ш ироковещательной рассылки
server.listen(8888); в канале с указанием идентификатора
пользователя исообщения при отправке
данныхлюбым пользователем.

После того как чат-сервер заработает, откройте новое окно командной строки
и введите следующий код для входа в чат:
teln et 127.0.0.1 8888

Если вы открыли несколько окон командной строки, вы увидите, что все данные,
вводимые в одном окне, воспроизводятся в других.
У этого чат-сервера есть серьезная проблема: при закрытии подключений и вы ­
ходе пользователя из чат-комнаты остается слушатель, который будет пытаться
60 Глава 2. Основы программирования Node

записы вать данные клиенту, который уже не подключен. Конечно, произойдет


ошибка. Чтобы решить ее, необходимо добавить слушателя в генератор событий
channel (листинг 2.12) и добавить логику в слушателя события close для генериро­
вания события leave канала. Событие leave удаляет слушателя broadcast, который
был изначально добавлен для клиента.

Листинг 2.12. Создание слушателя для завершения при отключении клиента


Создает слушателя
для события leave.
channel.on('leave', function(id) {
channel.removeListener(
'broadcast', this.subscriptions[id]
);
channel.em it('broadcast', id, '${id} has le f t the chatroom.\n'); •<—
}); ^ server = net.createServer(client
const ^ ^ , / ! • - . . =>
. {г Удаляет широковещ
r ательного
слушателя для конкретного
c lie n t.o n ('c lo se ', () => { клиента.
channel.em it('leave', id);
Генерирует события leave
}); для отключения клиента.
});
server.listen(8888);

Если по какой-то причине вы захотите отключить чат без завершения сервера, вы


также можете использовать метод генератора события rem o v eA llL isten ers для
удаления всех слушателей заданного типа. Следующий код показывает, как эта
возможность реализуется для нашего чат-сервера:
channel.on('shutdown', () => {
channel.em it('broadcast', ' ' , 'The server has shut down.\n');
channel.removeAllListeners('broadcast');
});

Затем можно добавить поддержку команды чата, завершающей общение. Д ля этого


измените слушателя для события data в следующем коде:
c lie n t.o n ('d a ta ', data => {
data = data.toString();
if (data === 'shutdown\r\n') {
channel.emit('shutdown');
}
channel.em it('broadcast', id, data);
});

Теперь при вводе любым участником в чате команды shutdown все участники будут
отключены.
2.8. Обработка повторяющихся событий с генераторами событий 61

ОБРАБОТКА ОШИБОК

Стандартный прием, часто применяемый при создании генераторов собы­


тий, — генерирование события типа error вместо прямого инициирования
ошибки. Это позволяет вам определять специальную логику реакции на
события посредством назначения одного или нескольких слушателей для
данного типа события.
Следующий код показывает, как слушатель событий обрабатывает сгенери­
рованную ошибку, выводя информацию на консоль:
const events = re q u ire^ ev e n ts');
const myEmitter = new events.EventEmitter();
m yEm itter.on^error', err => {
console.log('ERROR: S^rr.m essage}');
});
m yEm itter.em it^error', new Error^Something is wrong.'));
Если слушатель для этого типа события не определен при генерировании
типа события error, генератор события выводит трассировку стека (список
команд программы, выполнявшихся до точки возникновения ошибки) и пре­
рывает выполнение. В трассировке стека обозначен тип ошибки, заданный
вторым аргументом вызова emit. Это поведение присуще исключительно
событиям типа error; при генерировании других типов событий, не имеющих
слушателей, ничего не происходит.
Если событие типа error генерируется без передачи объекта error во втором
аргументе, в трассировке стека будет указано «неперехваченное неопреде­
ленное событие ошибки ‘error’», и приложение аварийно завершается. С у­
ществует устаревший механизм, который может применяться для обработки
таких ошибок, — вы можете определить собственную реакцию, определив
глобальный обработчик в следующем коде:
process.on('uncaughtException', err => {
co n so le.erro r^ rr.stac k );
p ro cess.ex it(l);
});
Были разработаны некоторые альтернативы для такого решения — например,
домены ( h ttp ://nodejs.org/api/dom ain.htm l), но они не считаются готовыми
для применения в реально эксплуатируемом коде.

Если вы захотите предоставить пользователям, подключающимся к чату, счетчик


пользователей, активных в настоящий момент, можно использовать следующий
метод l i s t e n e r s , который возвращ ает массив слуш ателей для заданного типа
события:
62 Глава 2. Основы программирования Node

channel.on('join', function(id, client) {


const welcome = '
Welcome!
Guests online: ${th is.listen ers('b ro ad cast').len g th }
;
client.write('${welcom e}\n');

Чтобы увеличить количество слушателей у генератора событий и чтобы избежать


предупреждений, которые Node выдает при более 10 слушателях, также можно
воспользоваться методом setM ax L isten ers. Если взять наш генератор событий
в качестве примера, для увеличения количества разрешенных слушателей можно
использовать следующий код:
channel.setMaxListeners(50);

2.8.4. Доработка генератора событий:


отслеживание содержимого файлов

Чтобы расширить поведение генератора событий, вы можете создать новый класс


JavaScript, наследующий от генератора событий. Например, вы можете создать
класс Watcher для обработки файлов, находящихся в заданном каталоге файловой
системы. После этого класс может использоваться для создания программ, отсле­
живающих содержимое каталога (все файлы, помещенные в каталог, переимено­
вываются с приведением символов к нижнему регистру, после чего копируются
в отдельный каталог).
После создания объекта Watcher необходимо дополнить методы, унаследованные
от EventEm itter, двумя новыми методами из листинга 2.13.

Листинг 2.13. Расширение функциональности генератора событий


const fs = re q u ire ('fs ');
const events = req u ire('ev en ts');

class Watcher extends events.EventEmitter { <- Расширяет объект


constructor(watchDir, processedDir) { EventEmitter методом,
super(); обрабатывающимфайлы.
this.watchDir = watchDir;
this.processedDir = processedDir;
}
watch() {
f s . re ad d ir(th is.watchDir, (e rr., file s ) => { ■< I Обрабатывает каждый файл
i f (err) throw err; I в отслеживаемом каталоге.
for (var index in file s ) {
th is.em it('p ro c ess', files[index]);
}
});
2 .{3. Обработка повторяющихся событий с генераторами событий 63

} Добавляет метод для


s ta 2 t ( ) { начала отслеживания.
fs.w atchF ile(this.w atchD i 2 , () => {
th is.w a tc h ();
});
}
}
module.exports = Watcher;

М етод watch перебирает содержимое каталога и обрабатывает все найденные файлы.


М етод s t a r t запускает отслеж ивани е каталога. Д л я отслеж ивани я используется
ф у н к ц и я N ode f s .w a t c h F i l e ; когда в катал о ге что-то происходит, срабаты вает
метод watch, которы й перебирает содерж им ое каталога и выдает собы тие p ro cess
д ля каж дого обнаруж енного файла.

После того как класс Watcher будет определен, его можно использовать для создания
объекта W atcher следую щ ей командой:

const watcher = new W atcher^atchD ir, processedDir);

Д л я созданного объекта W atcher вы зы вается метод on, унаследованны й от класса


генератора собы тия; он задает логику обработки каж дого файла:

w a tc h er.o n ^p ro cess', ( f ile ) => {


const watchFile = '${w atchD ir}/${file}';
const processedFile = ^ { p ro cessed D ir^S file.to L o w erC aseQ } ';
fs.renam e(w atchFile, processedFile, e rr => {
i f (e rr) throw e rr;
});
});

В ся необходи м ая л о ги ка готова. З а п у с т и т е отслеж и ван и е каталога следую щ им


кодом:

w a tc h e r.s ta rt();

П осле р азм ещ ен и я кода W atcher в сцен ар и и и созд ан и я каталогов watch и done


зап усти те сцен ари й с и сп о льзо ван и ем N o d e и скоп и руй те ф ай л ы в катал ог о т ­
слеж иван ия — вы увидите, что ф айлы появляю тся (с переим енованием в ниж ний
регистр) в итоговом каталоге. Этот прим ер показывает, как использовать генератор
собы тий д ля создания новы х классов.

Н аучивш ись использовать обратные вы зовы для определения асинхронной логики


и использовать генераторы собы тий для многократного срабаты вания асинхронной
логики, вы приблизитесь на ш аг к умению управлять при лож ениям и Node. Однако
отдельны й обратны й вы зов и ли слуш атель генератора собы тий мож ет вклю чать
логику вы полнения дополнительны х асинхронных задач. Если порядок выполнения
64 Глава 2. Основы программирования Node

этих задач важен, вы м ож ете столкнуться с новой проблемой: как управлять тем,
когда им енно будет вы полняться каж дая задача в серии асинхронны х задач?

П реж де чем зан яться управлением потоком вы полнен ия (эта тем а будет рассм а­
триваться в разделе 2.10), стоить рассм отреть некоторы е проблемы, с которы м и вы
с больш ой вероятностью столкнетесь при нап исании асинхронного кода.

2.9. Проблемы с асинхронной разработкой


П ри создании асинхронны х при лож ений необходимо вним ательно следить за п о ­
током вы полнен ия при лож ения и состоянием прилож ения: услови ям и ци кл а со­
бытий, перем енны ми при лож ения и лю бы м и другими ресурсами, изм еняю щ им ися
в ходе вы полнен ия л огики программы.

Ц и к л собы тий N ode, наприм ер, о тслеж ивает асинхрон ную логику, вы полнен ие
которой еще не заверш илось. П ока остается незаверш енная асинхронная логика, вы ­
ход из процесса N ode не происходит. П оведение постоянно вы полняемого процесса
N ode хорош о подходит д ля такого прилож ения, как веб-сервер, но неж елательно
д л я процессов, которы е долж ны завер ш и ться по истечении некоторого периода
вр ем ени (н ап р и м ер , програм м ком ан дной строки ). Ц и к л собы тий отслеж ивает
все подклю чения к базе данных, пока они не будут закры ты , не п озволяя N ode з а ­
верш ить работу прилож ения.

П ерем енны е при лож ения такж е могут неож иданно изм еняться при недостаточном
соблю дении осторож ности. В ли сти н ге 2.14 приведен прим ер того, как порядок
вы п о л н ен и я асинхронного кода м ож ет вы звать путаницу. Е сли бы прим ер кода
вы п о л н ял ся синхронно, м ож но предполож ить, что было бы выведено сообщ ение
«T he color is blue». Н о поскольку прим ер вы полняется асинхронно, значение пере­
м енной c o lo r и зм ен яется до вы п о л н ен и я c o n s o le .lo g , и вы вод и тся сообщ ение
«T he color is green».

Листинг 2.14. Ошибки, вызванные изменением области видимости


function asyncFunction(callback) {
setTim eout(callback, 200);
}
l e t color = 'b lu e ';
asyncFunction(() => {
console.log('The color is $ {co lo r} '); < ------ Выполняется в последнюю очередь (на 200 мс позже).
});
color = 'g re e n ';

Чтобы «заморозить» содержимое переменной color, можно изменить логику и исполь­


зовать замыкание (closure) JavaScript. В листинге 2.15 вызов asyncFunction заключен
в анонимную функцию, которая получает аргумент color. Затем анонимная функция,
которой отправляется текущее содержимое co lo r, немедленно выполняется. Так как
2.10. Упорядочение асинхронной логики 65

c o lo r становится аргументом анонимной функции, значение становится локальным


для области видимости этой функции, и при изменении значения c o lo r за пределами
анонимной функции локальная копия остается без изменений.

Листинг 2.15. Использование анонимной функции для сохранения значения


глобальной переменной
function asyncFunction(callback) {
setTim eout(callback, 200);
}
le t color = 'b lu e ';

(color => {
asyncFunction(() => {
console.log('T he color i s ' , color);
});
} )(c o lo r);

color = 'g re e n ';

Это всего лиш ь один из м ногочисленны х приемов из области програм м ирования


на Jav aS crip t, которы е встретятся вам в ходе разработки Node.

ЗАМЫКАНИЯ
За дополнительной информацией о замыканиях обращайтесь к документации JavaScript
от Mozilla: https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures.

Теперь вы знаете, как использовать зам ы кан и я д ля уп равлени я состоянием п р и ­


л о ж ен и я , и м ы м ож ем п ер ей ти к тем е у п о р я д о ч е н и я ас и н х р о н н о й л о ги к и д ля
уп равлени я потоком вы полнен ия прилож ения.

2.10. Упорядочение асинхронной логики


В процессе вы полнен ия асинхронной програм мы некоторы е задачи могут в ы п ол ­
н яться в лю бой м ом ент независим о от того, что происходит в остальном коде п ро­
граммы, без каких-либо проблем. О днако некоторы е задачи долж ны вы полняться
только до и ли после других задач.

В сообщ естве N ode концепцию упорядочени я групп асинхронны х задач принято


назы вать «потоком вы полнения». С ущ ествует две разнови дн ости потока вы п ол ­
нения: последовательны й и параллельны й (рис. 2.10).

Зад ачи , которы е д олж ны в ы п о л н яться одна после другой, назы ваю тся последо­
вательными. П ростой при м ер — задачи со зд ан и я каталога и сохранени я ф ай л а
в каталоге. Ф ай л невозм ож но сохранить до того, как каталог будет создан.
66 Глава 2. Основы программирования Node

Serial execution Parallel execution

Рис. 2.10. Последовательное выполнение асинхронных задач концептуально сходно


с синхронной логикой: задачи выполняются одна за другой. С другой стороны,
параллельные задачи могут выполняться одновременно друг с другом

Задачи, которые не обязаны вы полняться одна за другой, назы ваю тся параллельны­
ми. О тносительны й порядок запуска и остановки этих задач мож ет быть неважен,
но все эти задачи долж ны бы ть заверш ен ы до вы п ол н ен и я дальн ейш ей логики.
П рим ер — загрузка нескольких ф айлов, которы е позднее будут сж аты в zip-архив.
Ф ай л ы могут загруж аться одновременно, но все загрузки долж ны быть заверш ены
до создания архива.

У п р авлен и е п о сл ед о в ател ьн ы м и п ар ал л ел ь н ы м потоком в ы п о л н ен и я требует


некоторы х служ ебны х операций на програм м ном уровне. П ри реализаци и после­
довательного потока вы полнен ия приходится отслеж ивать задачу, вы полняем ую
в н астоящ и й момент, и ли ж е поддерж ивать очередь н евы полн енны х задач. П ри
реализаци и параллельного вы полнен ия необходимо хранить инф орм ацию о том,
сколько задач отработало до заверш ения.

С редства у п равлени я потоком вы полнен ия берут на себя рутинны е операции, что


упрощ ает группировку асинхронны х последовательны х или параллельны х задач.
В сообщ естве было создано достаточно много дополнений, предназначенны х для
упорядочения асинхронной логики, сам остоятельная реализация этих средств сн и ­
м ает с них покров тайны и помогает вам глубж е понять, как реш аю тся проблемы
асинхронного програм м ирования.
2.11. Когда применяется последовательный поток выполнения 67

В следую щ их разделах мы покажем:

О когда использовать последовательны й поток вы полнения;

О как реализовать последовательны й поток вы полнения;

О как реализовать параллельны й поток вы полнения;

О как использовать сторонние м одули д ля у п равлени я потоком вы полнения.

Д л я начала стоит разобраться, когда и как в асинхронном м ире при м ен яется п о ­


следовательны й поток вы полнения.

2.11. Когда применяется последовательный


поток выполнения
Д л я последовательного вы полнения нескольких асинхронных задач можно восполь­
зоваться обратны м и вы зовами. Н о если таких задач достаточно много, их придется
как-то организовать. Если этого не сделать, код получится слиш ком гром оздким
из-за чрезм ерного влож ени я обратны х вызовов.

Н иж е приведен пример последовательного вы полнения задач с применением обрат­


ны х вызовов. В этом примере ф ункц ия setTim eout используется для моделирования
задач, вы полнение которы х требует времени: первая задача вы полняется за одну
секунду, вторая — за половину секунды, и последняя — за десятую долю секунды.
Вы зов se tT im e o u t — и скусственн ая модель; реал ьн ы й код мож ет читать ф айлы ,
вы давать запросы H T T P и т. д. И хотя код этого прим ера достаточно короткий, он
не отли чается элегантностью и не позволяет легко добавить еще одну задачу на
програм м ном уровне.

setTimeout(() => {
c o n so le .lo g ('I execute f i r s t . ') ;
setTimeout(() => {
c o n so le .lo g ('I execute n e x t.') ;
setTim eout(() => {
c o n so le .lo g ('I execute l a s t . ') ;
}, 100);
}, 500);
}, 1000);

Также для вы полнен ия этих задач м ож но воспользоваться средствам и управления


потоком вы полнен ия — например, A sync (http://caolan.github.io/async/). М одуль
A sync прост в и сп о л ь зо в ан и и , а среди его д о стои н ств м ож н о вы д ел и ть малую
кодовую базу (всего 837 байт после м и н и ф и к а ц и и и сж ати я). У становка Async
устан авли вается следую щ ей командой:

npm in s t a l l async
68 Глава 2. Основы программирования Node

К од в следую щ ем листинге заново реализует приведенны й вы ш е ф рагм ент с п о ­


следовательны м потоком вы полнения.

Листинг 2.16. Организация последовательного выполнения с использованием


дополнений, разработанных в сообществе
const async = re q u ire ('a s y n c ');
asy n c .se rie s([ -<-------- Массив функций, которые должны быть выполненыAsync - одна за другой.
callback => {
setTimeout(() => {
c o n so le .lo g ('I execute f i r s t . ' ) ;
callb ack ();
}, 1000);
},
callback => {
setTimeout(() => {
c o n so le .lo g ('I execute n e x t.') ;
callb ack ();
}, 500);
},
callback => {
setTimeout(() => {
c o n so le .lo g ('I execute l a s t . ') ;
callb ack ();
}, 100);
}
]);

Х отя реализаци я, использую щ ая поток вы полнения, повы ш ает количество строк


кода, обы чно она создает м еньш е проблем с чтением и сопровож дением . С корее
всего, вы не будете использовать это реш ение постоянно, но, когда вы столкнетесь
с ситуацией, в которой потребуется избеж ать влож ени я обратны х вы зовов, этот
прием пом ож ет вам получить более удобочитаем ы й код.

И так, мы рассм о тр ел и при м ер о р ган и зац и и последовательного у п р ав л ен и я п о ­


током с и сп о л ьзо в ан и ем с п ец и ал и зи р о в ан н ы х средств. Теперь посм отрим , как
реализовать его с нуля.

2.12. Реализация последовательного


потока выполнения
Ч тобы организовать последовательное вы полнение нескольких асинхронных задач
с использованием явного упр авл ен и я потоком, сначала нуж но пом естить задачи
в массив в ж елательном порядке их вы полнения. К ак видно из рис. 2.11, этот массив
определяет очередь: при завершении одной задачи из массива извлекается следующая.

К аж дая задача представлена в массиве функцией. П ри заверш ении задача долж на


вы звать ф ункцию -обработчик для указания статуса ош ибки и результатов. Ф унк-
2.12. Реализация последовательного потока выполнения 69

Задачи хранятся в желательном порядке выполнения


_____________________________________ ,л,____________________________________ .
г >

\ Задача выполняет свою функцию,


\ после чего вызывает функцию
диспетчеризации для выполнения
следующей задачи из очереди

Рис. 2.11. Управление последовательным потоком выполнения

ция-обработчик в этой реализации преры вает вы полнение в случае ош ибки. Если


ош ибки не было, обработчик извлекает из очереди следующую задачу и выполняет ее.

Ч тобы продем онстрировать реализацию у п равлени я последовательны м потоком


вы полнения, мы создадим простое прилож ение, которое вы водит заголовок одной
статьи и U R L -адрес случайно выбранного канала RSS. Список возм ож ны х каналов
RSS храни тся в текстовом ф айле. В ы вод п р и лож ения вы глядит прим ерно так:

Of Course ML Has Monads!


http://lam bda-the-ultim ate.org/node/4306

В наш ем пр и м ер е и сп о л ьзу ю тся два в сп о м о гател ьн ы х м од ул я из реестра npm.


О ткройте окно ком андной строки и введите следую щ ие команды, чтобы создать
каталог для прим ера и установить вспом огательны е модули:

mkdir listing_217
cd listing_217
npm i n i t -y
npm in s t a l l --save request@2.60.0
npm in s t a l l --save htmlparser@1.7.7

М одуль re q u e s t реализует упрощ енного клиента H TTP, которы й мож ет исп ользо­
ваться для вы борки данны х RSS. М одуль h tm lp arse r обладает функциональностью ,
которая позволяет преобразовать низкоуровневые данные RSS в структуры данных
JavaS crip t.

Затем создайте в новом каталоге ф айл index.js с кодом из листинга 2.17.

Листинг 2.17. Реализация управления последовательным потоком выполнения


в простом приложении
const fs = r e q u i r e ( 'f s ') ;
const request = re q u ire ('re q u e s t'); Задача 1: Убедиться в том, что
const htmlparser = re q u ire ('h tm lp a rse r'); файл со списком URL-адресов
const configFilename = './r s s _ f e e d s .t x t'; канала RSS существует.
function checkForRSSFile() { <-
70 Глава 2. Основы 2рограммирования Node

fs.exists(configF ilenam e, (e x ists) => {


i f (!e x ists)
return next(new Error('M issing RSS f i l e : ${configFilenam e}')); ■<
n ex t(n u ll, configFilename); При возникновении ошибки
}); вернуть управление.
}
function readRSSFile(configFilename) { •<- Задача 2: Прочитать и разобрать
fs.readFile(configFilenam e, ( e rr, feedL ist) => { файл с URL-адресами канала.
i f (e rr) return n e x t(e rr);
feedL ist = feedL ist •<- Преобразует список URL-адресов поставки
.to S trin g () канала в строку, а затем в массив.
.re p la c e (/A\s+ |\s+ $ /g , ' ' )
. s p l i t ( '\ n ') ;
const random = Math.floor(Math.random() * feed L ist.len g th );
n ex t(n u ll, feedList[random]); Выбирает случайный
}); URL-адрес из массива.
}
function downloadRSSFeed(feedUrl) { Задача 3: Выполнить запрос HTTP
request({ u ri: feedUrl }, (e rr, re s, body) => { и получить данные выбранного канала.
i f (e rr) return n e x t(e rr);
i f (res.statusC ode !== 200)
return next(new Error('Abnormal response sta tu s co d e'));
n ex t(n u ll, body);
}); Задача 4: Разобрать данные
} RSS в массив.
function parseRSSFeed(rss) {
const handler = new htmlparser.RssHandler();
const parser = new htm lparser.P arser(handler);
parser.parseC om plete(rss);
i f (!handler.dom .item s.length)
return next(new Error('No RSS items fo u n d')); Выводит заголовок и URL-адрес
const item = handler.d o m .item s.sh ift(); первого элемента, если он
c o n s o le .lo g (ite m .title ); ■< существует.
co n so le.lo g (item .lin k );
}
const task s = [
checkForRSSFile, ■<------ Добавляет каждую выполняемуюзадачу в массив в порядке выполнения.
readRSSFile,
downloadRSSFeed,
parseRSSFeed
];
function n e x t(e rr, re s u lt) { Функция с именем next выполняет каждуюзадачу.
i f (e rr) throw e rr; <.— — Выдает исключение, если в ходе выполнения задачи происходит ошибка.
const currentTask = t a s k s .s h if t( ) ; < -------- Следующая задача берется из массива задач.
i f (currentTask) {
cu rren tT ask (resu lt); <.-------- Выполняет текущуюзадачу.
}
}
next(); Запускает последовательное выполнение задач.

Прежде чем тестировать прилож ение, создайте ф айл rss_feeds.txt в одном каталоге со
сценарием прилож ения. Если у вас нет под рукой ни одного канала RSS, опробуйте
кан ал блога N ode по адресу http://blog.nodejs.org/feed/. П ом ести те U R L -адреса
2.13. Реализация параллельного потока выполнения 71

канала RSS в текстовы й ф айл, по одному в каж дой строке ф айла. П осле того как
ф айл будет создан, откройте окно командной строки и введите следующие команды,
чтобы перейти в каталог при лож ения и вы полнить сценарий:

cd listing_217
node index.js

П оследовательны й поток вы полнения, как показы вает реализаци я примера, позво­


ляет вводить обратны е вы зовы в действие тогда, когда они необходимы — вместо
простого влож ения.

Разобравш ись с реализаци ей последовательного потока вы полнения, м ож но пере­


ходить к параллельном у вы полнению асинхронны х задач.

2.13. Реализация параллельного


потока выполнения
Чтобы несколько асинхронны х задач вы полнялись параллельно, задачи также необ­
ходимо поместить в массив, но на этот раз порядок их следования неважен. Каж дая
задача долж на вы зы вать ф ункцию -обработчик, которая увеличивает количество
заверш ен ны х задач. П осле того как все задачи будут заверш ены , ф у н к ц и я -о б р а­
ботчик долж на вы полнить некоторую заверш аю щ ую логику.

В к ач естве п р и м ер а п а р а л л е л ь н о го п о то к а в ы п о л н е н и я мы созд ад и м п ростое


при лож ение, которое ч итает содерж им ое текстовы х ф ай л ов и вы водит частоты
исп ользован ия слов в ф айлах.

Чтение содержимого текстовых файлов будет производиться при помощ и асинхрон­


ной ф ун кц и и re a d F ile , чтобы операции ч тен и я из ф айлов м огли осущ ествляться
параллельно. Н а рис. 2.12 показано, как работает это прилож ение.

Результат вы глядит прим ерно так (хотя, скорее всего, он будет намного длиннее):

would: 2
wrench: 3
w riteable: 1
you: 24

О ткройте окно ком андной строки и введите следую щ ие команды, чтобы создать
два каталога: один для примера, а другой, внутри него, — д ля текстовы х ф айлов,
которы е нуж но проанализировать:

mkdir listing_218
cd listing_218
mkdir te x t
72 Глава 2. Основы программирования Node

З атем создайте в каталоге listing_218 ф айл word_count.js, содерж ащ ий код из л и с­


тинга 2.18.

Листинг 2.18. Реализация управления параллельным потоком выполнения


в простом приложении
const f s = r e q u i r e ( 'f s ') ;
const task s = [];
const wordCounts = {};
const file s D ir = '. / t e x t ';
l e t completedTasks = 0;

f unct i on checkIfComp let e () { Когда все задачи будут завершены, вывести список
comp let edTa sks++; всех слов, использованных в файлах, и количество
i f (completedTasks === ta sk s.le n g th ) { вхождений каждого слова.
for ( le t index in wordCounts) { < -----
console.log('${index}: ${wordCounts[index]}');
}
}
}
function addWordCount(word) {
wordCounts[word] = (wordCounts[word]) ? wordCounts[word] + 1 : 1;
}
function countWordslnText(text) {
const words = te x t
.to S trin g ()
.toLowerCase()
.sp lit(/\W + /)
.s o r t( ) ;
words
.filte r(w o rd => word)
.forEach(word => addWordCount(word)); < -------- Подсчитывает вхождения слов втексте.
}
fs .r e a d d ir ( f ile s D ir, ( e rr, f il e s ) => { Получает список файлов в каталоге.
i f (e rr) throw e rr;
f ile s .fo rE a c h (file => {
const task = ( f il e => { -<
Определяет задачудля обработки каждого
return () => { файла. Каждая задача включает вызов функции,
fs .r e a d F ile ( f ile , (e rr, te x t) => { которая асинхронно читает содержимое файла,
i f (e rr) throw e rr; после чего подсчитывает в нем вхождения слов.
countWordslnText(text);
checkIfComplete();
});
}; Добавляет каждуюзадачу в массив функций,
} )('$ { file s D ir} /$ { file } ');
task s.p u sh (task ); вызываемыхдля параллельного выполнения.
}
tasks.forE ach(task => ta s k ( ) ) ; Запускает параллельное выполнение задач.
});
2.14. Средства, разработанные в сообществе 73

Каждый файл читается, и подсчет слов


в каждом файле ведется асинхронно.

Рис. 2.12. Управление параллельным потоком выполнения

П реж де чем тестировать прилож ение, создайте несколько текстовы х ф айлов в к а ­


талоге, созданном ранее. О ткройте окно ком андной строки и введите следую щ ие
команды , чтобы перейти в каталог п р и лож ения и вы полнить сценарий:

cd word_count
node word_count.js

Теперь, когда вы знаете, как работает последовательное и парал л ельн ое в ы п о л ­


нение задач, рассм отрим инструм ентарий, разработанны й в сообщ естве. Э тот и н ­
струм ентарий позволяет легко управлять потоком вы полнения, и вам не придется
реализовы вать его сам остоятельно.

2.14. Средства, разработанные в сообществе


М ногие дополнения, разработанны е в сообществе, предоставляю т удобные средства
уп равлени я потоком вы полнения. Н аибольш ей популярностью пользую тся такие
74 Глава 2. Основы программирования Node

дополнения, как Async, Step и Seq. И хотя каж дое из них заслуж ивает вним ания,
мы снова используем A sync в другом примере.

ДОПОЛНЕНИЯ ДЛЯ УПРАВЛЕНИЯ ПОТОКОМ ВЫПОЛНЕНИЯ


За дополнительной информацией о дополнениях для управления потоком выполне­
ния, разработанных в сообществе, обращайтесь к статье «Virtual Panel: How to Survive
Asynchronous Programming in JavaScript» Вернера Шустера (Werner Schuster) и Дио
Синодиноса (Dio Synodinos) на InfoQ: http://mng.bz/wKnV

В л и сти н ге 2.19 п ри веден п ри м ер и сп о л ьзо в ан и я A sync д л я у п о р я д о ч ен и я в ы ­


полнен ия задач в сценарии, использую щ ем параллельны й поток вы полнен ия для
одноврем енной загрузки двух ф айлов с их последую щ ей архивацией.

СЛЕДУЮЩИЙ ПРИМЕР НЕ БУДЕТ РАБОТАТЬ В MICROSOFT WINDOWS


Так как команды tar и curl не входят в комплект поставки операционной системы
Windows, следующий пример не будет работать в этой операционной системе.

Листинг 2.19. Использование средств управления потоком, разработанных


в сообществе, в простом приложении
Загружает исходный код Node
const async = re q u ire ('a s y n c '); для заДанной версии
const exec = re q u ire ('c h ild _ p ro c e ss').e x e c ;
function downloadNodeVersion(version, d e stin a tio n , callback) { ■<-----
const u rl = ' h ttp ://n o d e ]s.o rg /d ist/v $ { v e rsio n } /n o d e -v $ { v ersio n } .ta r.g z';
const file p a th = '$ { d estin atio n } /$ { v ersio n } .tg z';
e x e c ('c u rl ${url} > $ { file p a th } ', callback);
}
asy n c .se rie s([ -<-------- Последовательно выполняет серию задач.
callback => {
a sy n c .p a ra lle l([ < -------- Выполняет загрузки параллельно.
callback => {
console.log('Downloading Node v 4 . 4 . 7 ...') ;
downloadNodeVersion('4.4.7', '/tm p ', callback);
},
callback => {
console.log('Downloading Node v 6 . 3 . 0 ...') ;
downloadNodeVersion('6.3.0', '/tm p ', callback);
}
], callback);
},
callback => {
co nsole.log('C reating archive of downloaded f i l e s . . . ' ) ;
exec( < -------- Создает файл архива.
't a r cvf n o d e_ d istro s.tar /tm p /4 .4 .7 .tg z /tm p /6 .3 .0 .tg z ',
e rr => {
i f (e rr) throw e rr;
2.15. Заключение 75

co n so le.lo g ('A ll done!');


callback();
}
);
}
]);

В этом примере последовательны й поток вы полнения используется для того, чтобы


загрузка гарантированно бы ла заверш ена до начала архивации.

С ценарий определяет вспомогательную функцию , которая загруж ает заданную по­


ставляемую версию исходного кода Node. Затем последовательно вы полняю тся две
задачи: п араллельная загрузка двух версий N ode и упаковка загруж енны х версий
в новы й архивны й файл.

2.15. Заключение
К од N ode м ож ет о ф о р м л я т ьс я в м одули, п р ед н азн ач ен н ы е д л я повторного и с ­
пользования.

О Ф у н к ц и я r e q u ir e используется для загрузки модулей.

О О бъекты m odule.exports и ex p o rts предназначены для предоставления внешнего


доступа к ф ун кц и ям и перем енны м из модуля.

О Ф ай л package.json определяет зависи м ости и ф айл, экспортируем ы й как основ­


ной.

О Д л я у п равлени я асинхронной логикой м ож но использовать влож енны е обрат­


ны е вызовы, генераторы собы тий и средства уп равлени я потоком вы полнения.
Что представляет собой
веб-приложение Node?

Э та глава полностью посвящ ена веб-прилож ениям Node. П рочитав ее, вы поймете,
как вы глядят веб-п рилож ени я N ode и как приступить к их построению . Вы будете
делать все, что делает соврем енны й веб-разработчик при создании прилож ений.

М ы создадим веб-прилож ение по образцу таких поп улярн ы х сайтов отлож енного
чтения, как In stap ap er (www.instapaper.com) и P o ck et (getpocket.com). В процессе
работы нам пр ед сто и т со зд ать н о вы й п роект N ode, у п р а в л я ть зави си м о стям и ,
создать R E S T -совм естимы й A PI, сохранить инф орм ацию в базе данных, а такж е
создать ин терф ейс на основе ш аблонов. Н а первы й взгляд довольно много, но все
концепции этой главы будут более подробно исследованы в последую щ их главах.

Н а рис. 3.1 показано, как вы глядит результат.

Рис. 3.1. Веб-приложение для отложенного чтения


3.1. Структура веб-приложения Node 77

Н а странице при лож ения слева с целевого сайта удалены все элем енты навигации,
оставлен только основной контент и заголовок. Ч то еще важнее, статья сохран я­
ется в базе данных; это позволит вам прочитать ее позднее, когда оригинал может
оказаться недоступным.

П реж де чем строить веб-приложение, следует сначала создать новый проект. В сле­
дую щ ем проекте показано, как создать проект N ode с нуля.

3.1. Структура веб-приложения Node


Типичное веб-прилож ение N ode состоит из следую щ их компонентов:

О package.json — ф айл со списком зависим остей и ком андой запуска прилож ения;

О public/ — папка статических активов (C SS, кли ен тский код Jav aS crip t);

О node_modules/ — место д ля устан овки зависим остей проекта;

О один или несколько ф айлов Ja v a S c rip t с кодом прилож ения.

Код п ри лож ен и я часто подразделяется следую щ им образом:

О app.js и ли index.js — код подготовки прилож ения;

О models/ — м одели баз данных;

О views/ — ш аблоны, используем ы е для генерировани я страниц прилож ения;

О controllers/ и ли routes/ — обработчики запросов H T T P ;

О middleware/ — пром еж уточны е компоненты .

Н е существует правил, диктую щ их структуру прилож ения; обычно веб-фреймворки


обладаю т достаточной гибкостью и требую т настройки. Тем не менее описанная
схема встречается во м ногих проектах.
Всему этом у гораздо прощ е научиться на практике. Д авайте создадим заготовку
веб-п рилож ени я так, как это делаю т опы тны е программисты Node.

3.1.1. Создание нового веб-приложения


Ч тобы создать новое веб-п рилож ени е, необходим о создать новы й проект Node.
Е сли вам потребуется освеж ить пам ять — обращ айтесь к главе 2; а мы в двух сл о­
вах напомним, что д ля этого нуж но создать каталог и вы полнить ком анду npm i n i t
с настройкам и по умолчанию :

mkdir la te r
cd la te r
npm i n i t -fy
78 Глава 3. Что представляет собой веб-приложение Node?

Н о вы й проект создан; что д альш е? Б о л ьш и н ство лю дей д обавят в npm модуль,


упрощ аю щ ий веб-разработку. В Node имеется встроенный модуль h ttp с сервером, но
прощ е воспользоваться чем-то таким, что сокращ ает служ ебны й код, необходимый
д ля типичны х задач веб-разработки. А теперь посмотрим, как установить Express.

Добавление зависимости
Д л я д о б ав л ен и я за в и си м о сти в п роект и сп о л ьзу й те npm . С л едую щ ая ком анда
устанавливает Express:

npm in s ta l l --save express

Е сли теперь просм отреть ф айл package.json, вы увидите, что в него был добавлен
модуль Express. С оответствую щ ий ф рагм ент вы глядит так:

"dependencies": {
"express": "Л4.14.0"
}

М одуль Express находится в папке node_modules/ проекта. Если вы захотите удалить


Express из проекта, вы полните ком анду npm rm e x p re ss - -save. М одуль удаляется
из node_modules/ и обновляет ф айл package.json.

Простой сервер
E xpress ориен ти руется на построение м одели при лож ен и я в контексте запросов
и ответов H T T P и строится на базе встроенного м одуля N ode h ttp . Ч тобы создать
простейшее приложение, следует создать экземпляр прилож ения вызовом e x p re s s (),
добавить обработчик марш рута, а затем связать прилож ение с портом TCP. П олны й
код примера:

const express = re q u ire ('e x p re s s ');


const app = express();

const port = process.env.PORT | | 3000;

a p p .g e t ( '/ ', (req, res) => {


res.sen d ('H ello W orld');
});

a p p .lis te n (p o rt, () =>


console.log('E xpress web app available at localhost: ${p o rt} ');
};

Все не так сложно, как каж ется! С охраните код в ф айле с именем index.js и зап у ­
стите его ком андой node in d e x .js . Затем откройте стран ицу http://localhost:3000
для просмотра результатов. Ч тобы не приходилось запоминать, как именно должно
запускаться каж дое прилож ение, м ногие разработчики использую т сценарии npm
д ля упрощ ения процесса.
3.1. Структура веб-приложения Node 79

Сценарии npm
Ч тобы сохранить ком анду запуска сервера (node in d e x .j s ) как сценарий npm, от­
кройте ф айл package.json и добавьте в раздел s c r i p t s новое свойство с именем s t a r t :

"sc rip ts " : {


" s ta r t" : "node in d ex .js",
" te s t" : "echo \"E rro r: no t e s t sp e c ifie d \" && e x it 1"
},

Теперь прилож ение можно запустить командой npm s t a r t . Если вы получите ошибку
из-за того, что порт 3000 уж е используется на ваш ей маш ине, используйте другой
порт командой PORT=3001 npm s t a r t . Сценарии npm использую тся для самых разных
целей: построения пакетов на стороне клиента, зап уска тестов и генерировани я
докум ентации. В них м ож но разм естить все, что угодно; по сути это м еханизм з а ­
пуска м ини-сценариев.

3.1.2. Сравнение с другими платформами


Д л я сравнения приведем эквивалентное прилож ение P H P H ello W orld:

<?php echo '<p>Hello World</p>'; ?>

Код пом ещ ается в одной строке, он интуитивно понятен — каким и ж е преи м ущ е­


ствам и обладает более слож ны й прим ер N ode? Р азл и ч и я проявляю тся в парадигме
программирования: с P H P ваш е прилож ение я вл яется страницей, а с N ode — серве­
ром. П ример для Node полностью управляет запросом и ответом, поэтому вы можете
делать все, что угодно, без настройки сервера. Е сли вы хотите использовать сжатие
H T T P или перенаправление U R L, эти возм ож ности м ож но реализовать как часть
логики прилож ения. Вам не нуж но отделять логику H T T P от логики прилож ения;
и то и другое я в л яется частью прилож ения.

Вместо того чтобы создавать отдельную кон ф и гурац ию сервера H TTP, вы можете
хранить ее в том ж е месте (то есть в том же каталоге). Это обстоятельство упрощ ает
разверты ван ие при лож ений N ode и управление ими.

Д ругая особенность, упрощ аю щ ая разверты ван ие при лож ений Node, — npm. П о ­
скольку зависимости устанавливаю тся на уровне проекта, у вас не будет конфликтов
меж ду проектам и в одной системе.

3.1.3. Что дальше?


И так, вы получили представление о создании проектов ком андой npm i n i t и уста­
новке зависи м остей ком андой npm i n s t a l l - -sa v e, вы смож ете быстро создавать
новы е проекты . И это очень хорош о, потом у что вы см ож ете опробовать новые
идеи без кон ф ли ктов с другим и проектами. Если п ояви тся новы й зам ечательны й
80 Глава 3. Что представляет собой веб-приложение Node?

веб-ф рейм ворк, которы й вам захочется опробовать, создайте новы й каталог, вы ­
полните ком анду npm i n i t и установите м одуль из npm.

К огда все будет готово, м ож н о пер ех о д и ть к н ап и сан и ю кода. Н а этой стадии


в проект м ож но добавить ф айлы J a v a S c rip t и загрузить м одули, устан овлен ны е
ком андой npm - -sav e, вы зовом re q u ire . С осредоточим ся на том, что больш инство
веб-разработчиков делает после этого: на добавлении R E S T -совм естим ы х м арш ­
рутов. Это пом ож ет вам определить A P I п р и лож ения и понять, какие м одели базы
данны х нуж ны в ваш ей ситуации.

3.2. Построение REST-совместимой веб-службы


Н аш е прилож ение будет представлять собой R E S T -совместимую веб-службу, кото­
рая позволяет создавать и сохранять статьи по аналогии с Instapaper или Pocket. Она
будет использовать модуль, которы й берет за основу исходную служ бу R eadability
(www.readability.com) для преобразования неряш ли вы х веб-страниц в элегантны е
статьи, которы е вы смож ете прочитать позднее.

П ри проектировании R E S T -совм естим ой служ бы необходимо подумать над тем,


какие операции вам потребую тся, и связать их с м арш рутам и в Express. В данном
случае необходимо иметь возм ож ность сохранять статьи, организовы вать их вы ­
борку д ля чтения, получать список всех статей и удалять те статьи, которы е вам
уж е не нужны. Э ти операции соответствую т следую щ им марш рутам:

О POST / a r t i c l e s — создание новой статьи;

О GET / a r t i c l e s / : i d — получение одной статьи;

О GET / a r t i c l e s — получение всех статей;

О DELETE / a r t i c l e s / : i d — удаление статьи.

П реж де чем переходить к таким вопросам , как базы дан ны х и веб-интерф ейсы ,
остановим ся на создании R E S T -совм естимы х ресурсов в Express. Вы м ож ете вос­
пользоваться cU R L д ля вы дачи запросов к прилож ению -примеру, чтобы получить
некоторое представление о происходящ ем , а потом перейти к более слож ны м опе­
рац и ям (таким , как сохранение данны х), чтобы прилож ение было больш е похож е
на полноценное веб-прилож ение.

В следую щ ем листинге приведено простое прилож ение Express, которое реализует


эти м арш руты с использованием м ассива Ja v a S c rip t для хранени я статей.

Листинг 3.1. Пример REST-совместимых маршрутов


const express = re q u ire ('e x p re s s ');
const app = express();
const a r tic le s = [{ t i t l e : 'Example' }];
3.2. Построение REST-совместимой веб-службы 81

a p p .s e t( 'p o r t', process.env.PORT | | 3000);

a p p .g e t ( '/ a r t ic l e s ', (req, re s, next) => { -<■ (1) Получает все статьи.
re s .s e n d (a rtic le s );
});
a p p .p o s t ( '/ a r t ic l e s ', (req, re s, next) => { <- (2) Создает статью.
res.send('O K ');
});
a p p .g e t ( '/ a r t i c l e s / : i d ', (req, re s, next) => { <- (3) Получает одну статью.
const id = req.param s.id;
c o n so le .lo g ('F e tc h in g :', id );
re s .s e n d (a rtic le s [id ]);
});
a p p .d e l e te ( '/ a r t ic l e s /: i d ', (req, re s, next) => { (4) Удаляет статью.

a p p .lis te n ( a p p .g e t( 'p o r t') , () => {


console.log('App sta rte d on p o r t', a p p .g e t( 'p o rt'));
});
module.exports = app;

С охраните этот код в ф айле index.js; если все сделано правильно, он долж ен зап у ­
скаться ком андой node in d e x .js .

Ч тобы использовать этот пример, вы полните следую щ ие действия:

mkdir listing3_1
cd listing3_1
npm i n i t -fy
run npm in s t a l l --save express@4.12.4

С оздание новы х проектов N ode более подробно рассм атривается в главе 2.

ЗАПУСК ПРИМЕРОВ И ВНЕСЕНИЕ ИЗМЕНЕНИЙ

Чтобы запустить эти примеры, не забудьте перезапускать сервер после каждой


м оди ф и кац и и кода. Д л я этого остановите процесс N ode клавиш ам и C trl+ C
и запустите его снова ком андой node index.js.
П римеры приводятся в виде фрагментов, поэтому вы сможете последователь­
но объединять их д ля получения работоспособного прилож ения. Е сли они
почем у-либо не будут работать, попробуйте загрузить исходны й код книги
по адресу h ttp s://g ith u b .c o m /a le x y o u n g /n o d e jsin a c tio n .
82 Глава 3. Что представляет собой веб-приложение Node?

В листинге 3.1 присутствует встроенны й массив данных, который используется при


вы даче списка всех статей в ф орм ате J S O N (1 ) методом Express re s .s e n d . Express
автоматически преобразует массив в действительны й ответ JS O N , что очень удобно
при создании просты х R E S T A PI.

Э тот прим ер такж е м ож ет вы дать ответ с одной статьей по тому ж е принципу (3).
Вы даж е можете удалить статью (4 ), использовав клю чевое слово Jav a S c rip t d e le te
с числовы м идентиф икатором, заданны м в URL. Ч тобы получить значения из URL,
вклю чите их в строку м арш рута ( / a r t i c l e s / : i d ) и прочитайте нуж ное значение
re q .p a ra m s .id .

Л и сти н г 3.1 не позволяет создавать статьи (2 ), потому что д ля этого нуж ен парсер
тела запроса; эта тема рассм атривается в следую щ ем разделе. А пока посмотрим,
как использовать этот прим ер с cU R L (http://curl.haxx.se).

П осле зап у ск а пр и м ер а ком ан дой node i n d e x . j s вы м ож ете об ращ аться к нем у


с запросам и из браузера и ли cU R L. Ч тобы получить одну статью, вы полните сл е­
дующ ую команду:

curl h ttp ://lo c a lh o s t:3 0 0 0 /a rtic le s /0

Ч тобы получить все статьи, обратитесь с запросом к /articles:

curl h ttp ://lo c a lh o s t:3 0 0 0 /a rtic le s

С татьи даж е м ож но удалять:

curl -X DELETE h ttp ://lo c a lh o s t:3 0 0 0 /a rtic le s /0

Н о почему мы сказали, что вам не удастся создать статью ? Главная причина закл ю ­
чается в том, что реал и зац и я запроса P O S T требует разбора тела запроса. Ранее
в поставку Express входил встроенны й парсер тела запроса, но возмож ны х способов
реализации было столько, что разработчики реш или ввести отдельную зависимость.

П арсер тела запросов знает, как приним ать тела запросов P O S T в кодировке MIME
(M u ltip u rp o se In te rn e t M ail E xtensions) и преобразовывать их в данные, которые вы
можете использовать в своем коде. О бы чно вы получаете данные JS O N , с которыми
удобно работать. К аж ды й раз, когда вы отправляете данны е ф орм ы на сайте, где-то
в программном обеспечении на стороне сервера задействуется парсер тела запросов.

Ч тобы добавить оф ици альн о поддерж иваем ы й парсер тела запросов, вы полните
следую щ ую ком анду npm:

npm in s ta l l --save body-parser

Теперь загрузи те парсер тела запросов в своем п ри лож ен и и (п о б л и ж е к началу


ф ай л а), как показано в листинге 3.2. Е сли вы повторяете при водим ы е примеры,
сохраните его в одной папке с листингом 3.1 (listing3_1), но мы такж е сохранили его
в отдельной папке в исходном коде кн иги (ch03-what-is-a-node-web-app/listing3_2).
3.3. Добавление базы данных 83

Листинг 3.2. Добавление парсера тела запросов


const express = re q u ire ('e x p re s s ');
const app = express();
const a r tic le s = [{ t i t l e : 'Example' }];
const bodyParser = re q u ire ('b o d y -p a rse r');

a p p .s e t( 'p o r t', process.env.PORT | | 3000); (1) Поддерживает тела запросов,


закодированные в формате JSON.
app.use(bodyParser.json()); < ----
app.use(bodyP arser. urlencoded({ extended: tru e } )); -<---- 1 (2 ) Поддерживает тела запросов
I в кодировке формы.
a p p .p o s t ( '/ a r t ic l e s ', (req, re s, next) => {
const a r tic le = { t i t l e : re q .b o d y .title };
a rtic le s .p u s h ( a r tic le ) ;
re s .s e n d (a rtic le );
});

В этом листинге добавляю тся два полезны х аспекта: разбор тела запроса в формате
JS O N ( 1 ) и тела запроса в кодировке ф орм ы (2 ). Также добавляется базовая р еа­
л и зац и я создания статей: если вы создадите запрос P O S T с полем t i t l e , в массив
статей будет добавлена новая статья! Соответствующ ая команда cU R L вы глядит так:

curl --data "title=Example 2" h ttp ://lo c a lh o s t:3 0 0 0 /a rtic le s

С ейчас мы подош ли достаточно близко к построению реального веб-прилож ения.


О стается добавить еще две возмож ности: м еханизм постоянного хранени я данны х
в базе данны х и механизм генерирования удобочитаемой версии статей, найденных
в И нтернете.

3.3. Добавление базы данных


Заран ее определенного м еханизм а вклю чения баз данны х в при лож ения N ode не
существует, но процесс обычно состоит из следую щ их шагов.
1. О пределить базу данны х, которую вы хотите использовать.
2. И зучить популярны е м одули npm, реализую щ ие драйвер или об ъектн о-реля­
ционное отображ ение (O R M , O b ject-R elational M apping).
3. Д обавить в проект модуль ком андой npm -sav e.
4. С оздать модули, инкапсулирую щ ие обращ ения к базе данны х в Ja v a S c rip t API.
5. Д обавить м одели к м арш рутам Express.

П реж де чем д о бавл ять поддерж ку базы данны х, п род олж им работать с Express
и разработаем код обработки м арш рутов с ш ага 5. О бработчики марш рутов H T T P
в E x p re ss-части п р и л о ж е н и я вы даю т п р о сты е вы зо вы к м о д ел я м баз данны х.
П ример:
84 Глава 3. Что представляет собой веб-приложение Node?

a p p .g e t( '/ a r t i c l e s ', (req, re s, e rr) => {


A r tic le .a ll( e r r , a r tic le s ) => {
i f (e rr) return n e x t(e rr);
re s .s e n d (a rtic le s );
});
});

В данном случае м арш рут H T T P предназначен д ля получения списка всех статей,


поэтом у метод м одели мож ет иметь вид A r t i c l e . a l l . Его кон кретная ф орм а и зм е­
няется в зависи м ости от A P I баз данных; типичны е прим еры — A r t i c l e .f i n d ( { } ,
c b )1 и A r t i c l e . f e t c h A l l ( ) . t h e n ( c b ) 2. О братите внимание: cb в этих прим ерах — со­
кращ ение от «callback», то есть «обратны й вызов».

С ущ ествует великое м нож ество баз данных; как ж е определить, какая вам нуж на?
Н иж е мы изл о ж и л и причины , по которы м д ля этого прим ера бы ла вы брана база
данны х SQ Lite.

КАКАЯ БАЗА ДАННЫХ?

Д л я наш его проекта будет использоваться база данны х SQ L ite ( w w w.sqlite.


org) с популярны м м одулем sqlite3 ( h ttp ://n p m js.c o m /p a c k a g e /sq lite 3 ). Б аза
данных SQ Lite удобна тем, что она является внутрипроцессной базой данных:
вам не нуж но устанавливать сервер, вы полняем ы й на заднем плане в ваш ей
системе. Л ю бы е добавляем ы е данны е запи сы ваю тся в ф айл, которы й сохра­
н яется при остановке и перезапуске при лож ения, поэтому S Q L ite хорош о
подходит д ля начала работы с базам и данных.

3.3.1. Проектирование собственного API модели


П рилож ен ие долж но поддерж ивать создание, вы борку и удаление статей. С ледо­
вательно, класс модели A r t ic l e долж ен содерж ать следую щ ие методы:

О A r t i c l e . a l l ( c b ) — возвращ ает все статьи;

О A r t i c l e . f i n d ( i d , cb) — находит заданную статью по идентиф икатору;

О A r t i c l e . c r e a t e ( { t i t l e , c o n te n t }, cb) — создает статью с заголовком и к о н ­


тентом;

О A r t i c l e . d e l e t e ( i d , cb) — удаляет статью по идентиф икатору.

Все эти методы м ож но реализовать с помощ ью м одуля sqlite3. Э тот модуль п озво­
ляет вы бирать несколько строк результатов вы зовом d b .a l l и ли одну строку — вы ­
зовом d b .g e t. Н о сначала нуж но создать подклю чение к базе данных.

1 Mongoose: http://m ongoosejs.com .

2 Bookshelf.js http://bookshelfjs.org.
3.3. Добавление базы данных 85

Л и сти н г 3.3 дем онстрирует вы полнение этих операций с SQ L ite в Node. Этот код
следует сохранить в ф айле db.js в одной папке с кодом из листинга 3.1.

Листинг 3.3. Модель Article


const sq lite 3 = r e q u ire ('s q lite 3 ').v e rb o s e ();
const dbName = 'l a t e r . s q l i t e ';
const db = new sqlite3.Database(dbName); < -------- (1) Подключается кфайлу базыданных.

d b .s e ria liz e (() => {


const sql = '
CREATE TABLE IF NOT EXISTS a r tic le s
(id integer primary key, t i t l e , content TEXT)
;
d b .ru n (sq l); < -------- (2) Создает таблицу articles, если она еще не существует.
});
class A rticle {
s ta tic a ll(c b ) {
db.all('SELECT * FROM a r t i c l e s ', cb); <-------- (3) Выбирает все записи.
}
s ta tic fin d (id , cb) {
db.get('SELECT * FROM a r tic le s WHERE id = ? ', id , cb); (4) Выбирает
} конкретную
статью.
s ta tic cre a te (d a ta , cb) {
const sql = 'INSERT INTO a r t i c l e s ( t i t l e , content) VALUES (?, ? ) ';
db .ru n (sq l, d a t a . t i t l e , d ata.co n ten t, cb); < ---- (5) Вопросительные знаки задают параметры.
}
s ta tic d e le te (id , cb) {
i f (!id ) return cb(new E rro r('P lease provide an i d ') ) ;
db.run('DELETE FROM a r tic le s WHERE id = ? ', id , cb);
}
}
module.exports = db;
m odule.exports.A rticle = A rticle;

В этом прим ере создается объект с именем A r tic le , которы й м ож ет создавать, ч и ­


тать и удалять данны е с использованием стандартного синтаксиса SQ L и м одуля
sqlite3. Сначала файл базы данных открывается вызовом sq lite 3 .D a ta b a se (1), после
чего создается таблиц а a r t i c l e s (2 ). С интаксис SQ L IF NOT EXISTS здесь особенно
удобен, потом у что он позволяет снова вы полнить код без случайного удален ия
и повторного создания таблицы a r t i c l e s .

Когда база данны х и таблиц ы будут подготовлены , при лож ение готово к выдаче
запросов. Д л я получения всех статей используется метод sqlite3 a l l (3 ). Д л я п о ­
лучен и я конкретной статьи используется синтаксис запросов с вопросительны м
знаком (4); модуль sqlite3 подставляет в запрос идентификатор. Наконец, вы можете
вставлять и удалять данны е методом run (5 ).
86 Глава 3. Что представляет собой веб-приложение Node?

Ч тобы этот прим ер работал, необходимо установить м одуль sqlite3 командой npm
i n s t a l l - -sav e s q l i te 3 . Н а м ом ент нап исания книги последней бы ла версия 3.1.8.

П осле того как базовая ф ункциональность баз данны х будет подготовлена, ее н е­


обходимо добавить в м арш руты H T T P из листинга 3.2.

С ледую щ ий ли сти н г дем онстрирует добавление всех методов, кром е PO ST. (О н


рассм атри вается отдельно, потом у что д ля него пон адобится м одуль readability,
которы й мы еще не устан овили.)

Листинг 3.4. Добавление модуля Article к маршрутам HTTP


const express = re q u ire ('e x p re s s ');
const bodyParser = re q u ire ('b o d y -p a rse r');
const app = express();
const A rticle = r e q u ir e ( './d b ') .A r tic le ; -<-------- (1) Загружает модуль базыданных.
a p p .s e t( 'p o r t', process.env.PORT | | 3000);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: tru e }));
a p p .g e t( '/ a r t i c l e s ', (req, re s, next) => {
A r tic le .a ll( ( e r r , a r tic le s ) => { -<-------- (2) Получает все статьи.
i f (e rr) return n e x t(e rr);
re s .s e n d (a rtic le s );
});
});

a p p .g e t ( '/ a r t i c l e s / : i d ', (req, re s, next) => {


const id = req.param s.id;
A rtic le .fin d (id , (e rr, a r tic le ) => { < -------- (3) Находит конкретную статью.
i f (e rr) return n e x t(e rr);
re s .s e n d (a rtic le );
});
});

a p p . d e l e t e ( '/ a r t i c l e s / : id ', (req, re s, next) => {


const id = req.param s.id;
A rtic le .d e le te (id , (e rr) => { •<-------- (4) Удаляет статью.
i f (e rr) return n e x t(e rr);
res.send({ message: 'D eleted' });
});
});

a p p .lis te n ( a p p .g e t( 'p o r t') , () => {


console.log('App sta rte d on p o r t', a p p .g e t('p o r t')) ;
});

module.exports = app;

Л и сти н г 3.4 предполагает, что вы сохранили листинг 3.3 в ф айле db.js в том ж е к а ­
талоге. N ode загруж ает этот модуль (1 ) и затем использует его для вы борки каждой
статьи (2 ), поиска конкретной статьи (3 ) и удален ия (4).
3.3. Добавление базы данных 87

П оследн ее, что остается сделать, — до бави ть п од держ ку со зд ан и я статей. Д л я


этого необходим о им еть возм ож н ость загруж ать статьи и преобразовы вать при
пом ощ и «волш ебного» алгоритм а удобочитаем ости. Д л я этого нам пон адобится
м одуль npm.

3.3.2. Преобразование статей в удобочитаемую форму


и их сохранение для чтения в будущем

И так, вы построили R E S T -совм естимы й A PI, а данны е могут сохраняться в базе


данны х. Теперь следует добавить код д ля преобразования веб-страниц в их уп р о ­
щ енные версии «для чтения». К счастью, вам не придется делать это самостоятельно;
вместо этого м ож но воспользоваться модулем из npm.

П оиск в npm по слову «read ab ility » дает достаточно м ного модулей. П опробуем
воспользоваться модулем n o d e-read ab ility (сущ ествует в версии 1.0.1 на момент
н ап и сан и я кн и ги ). У становите м одуль ком андой npm i n s t a l l n o d e - r e a d a b i l i t y
--s a v e . М одуль предоставляет асинхронную ф ункц ию д ля загрузки U R L -адреса
и преобразования разм етки H T M L в упрощ енное представление.

С ледую щ ий ф рагм ент показы вает, как используется модуль node-readability; если
вы захотите опробовать его, добавьте следую щ ий ф рагм ент в ф айл index.js в д о ­
полнение к листингу 3.5:

const read = re q u ire ('n o d e -re a d a b ility ');


const u rl = ' http://www.manning.com/cantelon2/';
re ad (u rl, (e r r , result)=> {
/ / re s u lt содержит . t i t l e и .content
});

М одуль node-read ab ility м ож ет использоваться с классом базы данны х д ля сохра­


н ения статей методом A r t i c l e .c r e a t e :

re ad (u rl, (e r r , re s u lt) => {


A rtic le .c re a te (
{ t i t l e : r e s u l t . t i t l e , content: re su lt.c o n te n t },
(e r r , a r tic le ) => {
/ / Статья сохраняется в базе данных
}
);
});

Чтобы использовать модуль в прилож ении, откройте ф айл index.js и добавьте новый
обработчик м арш рута a p p .p o s t д ля загрузки и сохранения статей. О б ъеди н яя все
это с тем, что вы узнали о H T T P P O S T в Express и парсере тела запроса, мы п о л у ­
чаем прим ер из листинга 3.5.
88 Глава 3. Что представляет собой веб-приложение Node?

Листинг 3.5. Генерирование удобочитаемых статей и их сохранение


const read = re q u ire ('n o d e -re a d a b ility ');

/ / . . . Оставшаяся часть файла in d ex .js из листинга 3.4

a p p .p o s t ( '/a r t i c l e s ', (req, re s, next) => {


const u rl = req.body.url; < -------- (1) Получает URLиз тела POST.

re a d (u rl, (e rr, re s u lt) => { -<-------- (2) Использует режим удобочитаемости от выборки URL.
i f (e rr || !re su lt) re s.sta tu s(5 0 0 ).se n d ('E rro r downloading a r t i c l e ') ;
A rtic le .c re a te (
{ t i t l e : r e s u l t . t i t l e , content: re su lt.c o n te n t },
(e rr, a r tic le ) => {
i f (e rr) return n e x t(e rr);
res.send('O K '); -<-------- (3) После сохранения статьи возвращает код 200.
}
);
});
});

З д есь мы сначала получаем U R L из тела P O S T (1 ), а затем используем модуль


n o d e-read ab ility д ля п олуч ен и я U R L (2 ). С татья сохраняется с исп ользован ием
класса м одели A r tic le . Если произойдет ош ибка, ее обработка передается пром е­
ж уточном у стеку Express (3 ); в противном случае представление статьи в ф орм ате
JS O N отправляется клиенту.

Ч то бы создать зап рос PO ST , р аботаю щ ий с этим прим ером , и сп ользуй те клю ч


--d a ta :

curl --d ata "url= http://m anning.com /cantelon2/" h ttp ://lo c a lh o s t:3 0 0 0 /a rtic le s

В преды дущ ем разделе мы добавили модуль базы данных, создали инкапсулирую ­


щ ий его Ja v a S c rip t A PI и связали с R E S T -совместимы м H T T P A PI. Это значитель­
ны й объем работы, которы й составляет основную часть ваш их усилий в качестве
разработчика на стороне сервера. Б азы данны х будут более подробно рассмотрены
позднее в этой книге, когда мы займ ем ся M ongoD B и Redis.

О т с о х р а н е н и я с т а т е й и их п р о г р а м м н о й в ы б о р к и м ы п ер ей д е м к д о б а в л е ­
нию веб-и нтерф ейса, чтобы п о л ьзо вател и такж е м огли прочитать сохраненны е
статьи.

3.4. Добавление пользовательского


интерфейса
Д обавлени е ин терф ейса в проект E xpress состоит из нескольких шагов. П ервы й
ш аг — использование ядр а шаблонов; вскоре мы покажем, как установить его и вы ­
полнить визуализац ию ш аблонов.
3.4. Добавление пользовательского интерфейса 89

Ваш е пр и ло ж ен и е такж е долж но п редоставлять статически е ф ай л ы (наприм ер,


разм етку C SS). Н о преж де чем вы полнять визуализацию ш аблонов и писать стили
CSS, вы долж ны узнать, как застави ть об работчи ки м арш рутов из преды дущ их
прим еров отвечать данны м и в ф орм ате JS O N и H T M L при необходимости.

3.4.1. Поддержка разных форматов


Д о настоящ его м ом ента мы использовали метод r e s .s e n d ( ) для отправки объектов
Jav aS crip t клиенту. Д л я создания запросов использовался модуль cURL, и в данном
случае ф орм ат JS O N удобен, потому что легко читается с консоли. Н о чтобы п р и ­
лож ение могло реально использоваться, оно долж но такж е поддерж ивать H TM L.
К ак обеспечить поддерж ку обоих ф орм атов?

П рощ е всего воспользоваться методом r e s .f o r m a t, предоставляем ы м Express. Он


п о зво л я ет пр и ло ж ен и ю вы дать ответ в подходящ ем ф орм ате в зав и си м о сти от
вопроса. Д л я этого прилож ению предоставляется список ф орм атов с ф ункциям и,
которы е отвечаю т нуж ны м образом:

res.form at({
html: () => {
r e s .r e n d e r ( 'a r ti c le s .e js ', { a r tic le s : a r tic le s });
},
json: () => {
re s .s e n d (a rtic le s );
}
});

В этом ф рагм енте r e s .r e n d e r вы полняет визуализац ию ш аблона articles.ejs в п ап ­


ке views. Н о чтобы этот способ сработал, необходимо установить ядро ш аблонов
и создать несколько ш аблонов.

3.4.2. Визуализация шаблонов


Сущ ествует м ного ядер ш аблонов; мы вы берем простое ядро, которое достаточно
легко и зучается, — EJS (E m b e d d e d J a v a S c rip t). У становите м одул ь EJS из npm
(E JS сущ ествует в версии 2.3.1 на м ом ент написания книги):

npm in s t a l l e js --save

Теперь r e s .r e n d e r м ож ет генерировать ф айлы H T M L , отф орм атированны е с и с­


пользованием EJS. Если заменить r e s .s e n d ( a r t i c l e s ) в обработчике м арш рута app.
g e t ( ' / a r t i c l e s ' ) из листинга 3.4, посещ ение адреса http://localhost:3000/articles
в браузере приведет к попы тке визу ал и зац и и articles.ejs.

Д алее необходимо создать ш аблон articles.ejs в папке views. В листинге 3.6 приведен
полны й ш аблон, которы й вы м ож ете использовать.
90 Глава 3. Что представляет собой веб-приложение Node?

Листинг 3.6. Шаблон для списка статей


<% include head %> < -------- (1) Включает другой шаблон.
<ul>
<% a rtic le s .fo rE a c h ((a rtic le ) => { %> -<----- (2) Перебирает все статьи и проводит их визуализацию.
<li>
<a href="/articles/<% = a r t i c l e .id %>">
<%= a r t i c l e . t i t l e %> -<-------- (3) Включает заголовок статьи в качестве текста ссылки.
</a>
</li>
<% }) %>
</ul>
<% include foot %>

Шаблон для списка статей использует шаблоны для заголовка (1 ) и завершителя,


которые включаются в следующие примеры кода. Это делается для того, чтобы
избежать дублирования заголовка и завершителя в каждом шаблоне. Для пере­
бора списка статей (2 ) применяется стандартный цикл JavaScript forEach, после
чего идентификаторы статей и заголовки внедряются в шаблон синтаксической
конструкцией EJS <%= value %> (3).
Пример шаблона заголовка из файла views/head.ejs:
<html>
<head>
< title > L a te r< /title >
</head>
<body>
<div class="container">

А вот соответствующий завершитель (сохраняется в файле views/foot.ejs):


</div>
</body>
</html>

Метод res.form at также может использоваться для вывода конкретных статей.


Здесь ситуация становится более интересной, потому что для того, чтобы при­
ложение приносило реальную пользу, статьи должны быть аккуратно оформлены
и легко читаться.

3.4.3. Использование npm для зависимостей


на стороне клиента
Когда шаблоны будут на своих местах, пора сделать следующий шаг — добавить
стилевое оформление. Вместо того чтобы создавать таблицу стилей, проще по­
вторно использовать существующие стили, причем это можно сделать даже с npm!
Популярный клиентский фреймворк Bootstrap (http://getbootstrap.com/) доступен
в npm ( www.npmjs.com/package/bootstrap); добавьте его в проект:
3.4. Добавление пользовательского интерфейса 91

npm in s t a l l bootstrap --save

Загл я н у в в каталог node_modules/bootstrap/, вы найдете в нем исходны й код п р о ­


екта B o o tstrap . В папке dist/css х р ан я тся ф айлы CSS, п оставляем ы е с B ootstrap.
Ч тобы использовать их в проекте, при лож ение долж но быть способно поставлять
статические файлы .

Статические файлы
Д л я о тп р ав к и б р ау зер у кл и ен тск о го кода Ja v a S c rip t, гр а ф и к и и C SS в E xpress
им еется в с т р о ен н а я п р о с л о й к а e x p r e s s . s t a t i c . Ч тоб ы и с п о л ьзо в ат ь ее, п е р е ­
д ай те катал о г со стати ч ески м и ф ай л ам и , и эти ф ай л ы стан ут д оступ н ы м и для
браузера.

В начале ф ай л а п р и лож ения Express (index.js) им ею тся строки, загруж аю щ ие п ро­


грам м ны е прослойки, необходимы е д ля проекта:

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: tru e }));

Ч тобы загрузить CSS ф рейм ворка B ootstrap, используйте e x p r e s s . s t a t i c д ля р е ­


гистрации ф ай л а с нуж ны м U R L -адресом:

app.use(
'/c s s /b o o ts tra p .c s s ',
e x p re ss.sta tic ('n o d e _ m o d u le s/b o o tstra p /d ist/c ss/b o o tstra p .css')
);

П осле этого м ож но добавить ф айл /css/bootstrap.css в ш аблоны, чтобы использовать


впечатляю щ ие стили B ootstrap. Ф айл views/head.ejs должен выглядеть примерно так:

<html>
<head>
< title > la te r ;< /title >
<link rel="stylesheet" href=”/css/bootstrap.css” >
</head>
<body>
<div class="container">

Это только разм етка CSS; в поставку B o o tstrap такж е входят другие ф айлы , вкл ю ­
ч ая значки, ш риф ты и плагины jQ uery. Вы мож ете добавить в свой проект другие
ф айлы B o o tstrap или ж е упаковать их в один ф айл д ля удобства загрузки.

Npm и разработка на стороне клиента: новые возможности


В преды дущ ем при м ере прод ем онстрировано простое исп ользован ие б и бл и оте­
ки, предназначенной д л я браузеров, с помощ ью npm . В еб-разработчики обычно
загруж аю т ф айлы B o o tstrap и добавляю т их в проект вручную (к а к правило, веб­
разработчики, заним аю щ иеся просты м и статическим и сайтам и).
92 Глава 3. Что представляет собой веб-приложение Node?

О днако соврем енны е разработчики клиентской (и н терф ей сн ой ) части и сп ользу­


ют npm как д ля получения библиотек, так и д ля загрузки их в кли ен тском коде
Jav a S c rip t. Такие инструм енты , как B row serify (http://browserify.org/) и w ebpack
(http://webpack.github.io/), п р едоставляю т в ваш е р асп оряж ен и е всю мощ ь npm
и r e q u ir e д ля загрузки зависим остей. П редставьте, что вы мож ете ввести команду
c o n s t React = r e q u i r e ( 'r e a c t ') не только в коде Node, но и в коде разработчика
кли ен тской части! Э та тема вы ходит за рам ки настоящ ей главы, и все ж е она дает
некоторое представление о том, чего можно добиться при объединении механизмов
програм м ирования N ode с прием ам и програм м ирования кли ен тской части.

3.5. Заключение
О В еб-прилож ение N ode м ож но бы стро построить с нуля при помощ и команды
npm i n i t и Express.

О Д л я установки зависим остей используется ком анда npm i n s t a l l .

О E x p re ss п о з в о л я е т с о з д а в а т ь в е б -п р и л о ж е н и я с и с п о л ь з о в а н и е м R E S T -
совм естимы х API.

О Выбор подходящ ей базы данны х и м одуля базы данны х требует предваритель­


ного анализа и зависи т от конкретны х требований.

О S Q L ite хорош о подходит д ля м алы х проектов.

О EJS предоставляет просты е средства для визуал и зац и и ш аблонов в Express.

О Express поддерж ивает разны е яд р а ш аблонов, вклю чая P ug и M ustache.


II Веб-разработка
с использованием
Node

Итак, вы готовы к более глубокому изучению разработки на стороне


сервера. Технология Node нашла важную нишу за пределами серверного
кода: системы построения фронтэнда. В этой части вы узнаете, как за­
пускать проекты с использованием webpack и Gulp. Также мы представим
самые популярные веб-фреймворки и сравним их с точки зрения разных
разработчиков, чтобы помочь вам определиться с выбором идеального
фреймворка для ваших проектов.

Если вы захотите больше узнать о Connect и Express, глава 6 полностью


посвящена построению веб-приложений с этими модулями. Также имеется
глава, посвященная шаблонам и использованию баз данных с Node.

Обзор полностековой веб-разработки на базе Node завершается главами,


посвященными тестированию и развертыванию. Они помогут вам подго­
товить свое первое приложение Node к работе.
4 Системы построения
фронтэнда

В соврем енной веб-разработке N ode все чащ е исп ользуется д ля запуска и н стр у ­
м ентов и сервисов, от которы х зав и ся т ф рон тэн д -разработчи ки . В озмож но, вам
как N o d e -програм м исту при дется отвечать за н астройку и сопровож дени е этих
инструм ентов. А как полностековом у р азработчику вам стоит исп ользовать эти
ин струм енты д ля создан ия более бы стры х и надеж ны х веб-п рилож ени й. В этой
главе вы научитесь использовать сценарии npm, G ulp и w ebpack д ля построения
проектов, удобны х в сопровож дении.

П реим ущ ества от использования систем построения ф ронтэнда могут быть огром­


ными. Такие системы помогаю т писать более пон ятны й и устойчивы й к будущ им
изм енениям код. Н ет необходимости беспокоиться о поддерж ке браузером ES2015,
если при лож ение обрабаты вается тр анспилятором Babel. К роме того, благодаря
возм ож н ости генерировани я карт исходного кода, сохраняется возм ож ность о т ­
ладки на базе браузера.

В сл еду ю щ ем р а зд ел е п р и в о д и тс я к р а т к о е в вед ен и е во ф р о н т эн д -р а зр а б о т к у
с и сп о льзо ван и ем N ode. П осле этого будут рассм отрен ы при м еры соврем енны х
тех н о л о ги й — нап рим ер, R eact, которы е вы см ож ете и сп о л ьзо в ать в своих п р о ­
ектах.

4.1. Фронтэнд-разработка
с использованием Node
В последнее врем я как разработчики фронтэнда, так и разработчики кода на сто­
роне сервера стали прим енять npm для передачи Jav aS crip t. Это означает, что npm
используется как для м одулей ф ронтэнда (таких, как R eact), так и для серверного
кода (наприм ер, Express). О днако некоторы е м одули трудно четко отнести к той
или иной стороне: lodash — прим ер библиотеки общего назначения, которая может
использоваться N ode и браузерами. П ри тщ ательной упаковке lodash один модуль
4.2. Использование npm для запуска сценариев 95

может использоваться как Node, так и браузерами, а зависимостями в проекте можно


будет управлять при помощ и npm.

В озм ож но, вам уж е встр еч ал и сь други е м о ду л ьн ы е систем ы , п ред назначенны е


д ля разработки на стороне клиента, — такие, как Bower (http://bower.io/). Н икто
не запрещ ает вам использовать их, но как разработчику N ode вам стоит подумать
об исп ользован ии npm.

Впрочем, распространение пакетов — не единственная область прим енения Node.


Ф рон тэн д-разработчики все чащ е полагаю тся на средства создания портируемого
кода J a v a S c rip t с обратной совм естимостью . Т ранспи ляторы — такие, как Babel
(https://babeljs.io/), — использую тся для преобразования современного кода ES2015
в более ш ироко поддерж иваем ы й код ES5. Среди других средств можно упом януть
м и н и ф и каторы (наприм ер, U glifyJS; https://github.com/mishoo/UglifyJS) и и н стру­
менты статического анализа (наприм ер, ESLint; i) д ля проверки правильности кода
перед его распространением .

N ode такж е часто управляет систем ам и запуска тестов. Вы мож ете запускать тесты
д ля U I -кода в процессе N ode или ж е использовать сценарий N ode д ля управления
тестами, вы полняем ы м в браузере.

Т акж е эти ср едства д о во льн о ч асто и сп о л ь зу ю тся вм есте. К огда вы начинаете


ж онглировать траспилятором , м иниф икатором , програм мой статического анализа
и систем ой запуска тестов, вам нуж но будет каким -то образом заф икси ровать, как
работает процесс построения. В одних проектах использую тся сценарии npm; в дру­
гих — использую т G ulp или w ebpack. В этой главе мы рассм отрим все эти подходы,
а такж е некоторы е практические приемы, связанны е с ними.

4.2. Использование npm для запуска сценариев


N ode п оставляется с npm , а в npm сущ ествую т встроенны е средства д ля запуска
сценариев. С ледовательно, вы мож ете рассчиты вать на то, что коллеги или п ользо­
ватели смогут вы полнять такие команды, как npm s t a r t и npm t e s t . Ч тобы добавить
собственную ком анду для npm s t a r t , вклю чите ее в свойство s c r i p t s ф айла package.
json своего проекта:

{
"s c rip ts " : {
" s ta r t" : "node se rv e r.js"
},

Даже если не определять s t a r t , значение node s e r v e r .js используется по умолчанию;


строго говоря, вы мож ете оставить его пусты м — только не забудьте создать ф айл
96 Глава 4. Системы построения фронтэнда

с им енем server.js. Такж е полезно определить свойство t e s t , потому что вы можете


вклю чить свой тестовы й ф рейм ворк как зависимость и запустить его командой npm
t e s t . П редполож им , вы исп ользуете д ля тести рован и я ф рей м ворк M ocha (www.
npmjs.com/package/mocha) и у стан о в и л и его ком ан дой npm i n s t a l l - -s a v e - d e v .
Ч тобы вам не приходилось устанавливать M ocha глобально, вы мож ете добавить
следую щ ую ком анду в ф айл package.json:

{
"s c rip ts " : {
" te s t" : "./node_modules/.bin/mocha t e s t / * .js "
},

О братите внимание: в преды дущ ем прим ере M ocha передаю тся аргументы. Также
при запуске сценариев npm м ож но передать аргум енты через два дефиса:
npm t e s t -- t e s t / * . j s
В табл. 4.1 приведена сводка некоторы х ком анд npm.

Таблица 4.1. Команды npm

Команда Свойство Примеры использования


package.json

start scripts.start Запуск сервера веб-приложения или приложе­


ния Electron

stop scripts.stop Остановка веб-сервера

restart Последовательное выполнение команд stop


и start

install, scripts.install, Выполнение собственных команд построения


postinstall scripts.postinstall после установки пакета. Обратите внимание:
postinstall может выполняться только в команде
npm run postinstall

П оддерживаю тся многие команды, вклю чая команды очистки пакетов перед публи­
кацией и ком анды м играции м еж ду версиям и пакетов. Впрочем, д ля больш инства
задач веб-разработки вам хватит ком анд s t a r t и t e s t .

М ногие задачи, которые вы будете определять, не уклады ваю тся в поддерживаемые


им ена команд. Н апример, предполож им , что вы работаете над просты м проектом,
написанны м на ES2015, и хотите транспили ровать его на ES5. Это м ож но сделать
командой npm run. В следующем разделе рассматривается учебный пример, в котором
будет создан новы й проект д ля построения ф айлов ES2015.
4.2. Использование npm для запуска сценариев 97

4.2.1. Создание специализированных сценариев npm


К ом анда npm run (си н о н и м д ля npm r u n - s c r i p t ) и сп ользуется д л я определен ия
п рои звольны х сценариев, которы е запускаю тся ком андой npm run имя-сценария.
П осмотрим, как создать такой сценарий д ля построения сценария на стороне к л и ­
ента с использованием Babel.

С оздайте новы й проект и установите необходимые зависимости:

mkdir es2015-example
cd es2015-example
npm i n i t -y
npm in s t a l l --save-dev b a b e l-c li babel-preset-es2015
echo '{ "p resets": ["es2015"] }' > .babelrc

В результате вы п о л н ен и я этих ком анд долж ен бы ть создан новы й проект N ode


с базовы м и инструм ентам и и плагинам и Babel д ля ES2015. Затем откройте ф айл
package.json и добавьте в раздел s c r i p t s свойство babel.

Оно долж но вы полнять сценарий, установленны й в папке node_modules/.bin проекта:

"babel": "./node_m odules/.bin/babel brow ser.js -d build/"

Н иж е приведен прим ер ф айла в синтаксисе ES2015, которы й вы мож ете исп оль­
зовать; сохраните его в ф айле browser.js:

class Example {
render() {
return '<h1>Example</h1>';
}
}
const example = new Example();
console.log(exam ple.render());

Ч тобы протестировать этот пример, введите команду npm run babel. Если все было
настроено прави льн о, д о лж н а п о я в и ть ся п ап ка п остроен и я с ф айлом browser.js.
О ткройте browser.js и убедитесь в том, что это действительно ф айл ES5 (поищ ите
конструкцию вида v a r _ c r e a te C la s s в начале ф айла.

Если ваш проект при построении ничего более не делает, ему можно присвоить имя
b u ild вместо b ab el в ф айле package.json. Н о м ож но пойти еще дальш е и добавить
UglifyJS:

npm i --save-dev u glify -es

U glifyJS вы зы вается ком андой n o d e _ m o d u le s /.b in /u g lify js ; добавьте эту команду


в package.json из раздела s c r i p t s с именем u g lify :

./node_m odules/.bin/uglifyjs build/brow ser.js -o build/brow ser.m in.js


98 Глава 4. Системы построения фронтэнда

Теперь вы см ож ете вы полни ть ком анду npm run u g lif y . Всю ф ун кц и он альн ость
можно связать воедино, объединив оба сценария. Добавьте еще одно свойство s c r ip t
с именем b u ild д ля вы зова обеих задач:

"build": "npm run babel && npm run uglify"

О ба сц ен ар и я зап ускаю тся ком ан дой npm run b u ild . У частн ики ваш ей ком анды
м огут объединить несколько средств у п ак овки кл и ен тской части вы зовом этой
простой команды. Такое реш ение работает, потому что Babel и UglifyJS могут вы ­
по л н яться как сценарии ком андной строки, они получаю т аргументы командной
строки и легко добавляю тся как однострочны е команды в ф айл package.json. Также
Babel позволяет определить сложное поведение в ф айле .b a b e lrc , что было сделано
ранее в этой главе.

4.2.2. Настройка средств построения фронтэнда


В общем случае возм ож ны три варианта настройки средств построения ф ронтэнда
при исп ользован ии со сценариям и npm:

О передача аргументов командной строки (наприм ер, . /node_m odules/. b in /u g lif y


--so u rce-m ap );

О создание конф игурационного ф ай л а для конкретного проекта. Ч асто п р и м ен я­


ется д ля Babel и E SL int;

О вклю чение парам етров кон ф и гурац ии в ф айл package.json. Babel такж е поддер­
ж ивает эту возможность.

Что, если ваш и требования к построению содержат дополнительны е этапы с такими


операциям и, как копирование, кон катенаци я или перем ещ ение ф ай л ов? М ож но
создать сценарий ком андного интерпретатора и запустить его из сценария npm, но
ваш им коллегам, сведущ им в JavaS cript, будет удобнее, если вы будете использовать
Jav aS crip t. М ногие системы построения предоставляю т Ja v a S crip t A PI для автома­
ти зац и и построения. В следую щ ем разделе описано одно из таких реш ений: Gulp.

4.3. Автоматизация с использованием Gulp


G ulp (http://gulpjs.com/) — систем а построения, работаю щ ая на основе потоков
(stream s). П ользователь м ож ет сводить потоки воедино д ля создан ия процессов
построения, не ограничиваю щ и хся простой тр ан сп и л яц и ей и ли м и н и ф и кац и ей
кода. Представьте, что у вас им еется проект с адм инистративной областью, постро­
енной на базе Angular, и с общ едоступной областью, построенной на базе React; оба
подпроекта имею т некоторы е общ ие требования к построению . С G ulp вы сможете
повторно использовать части процесса построения д ля каж дой стадии. Н а рис. 4.1
представлены прим еры двух процессов построения с общ ей ф ункциональностью .
4.3. Автоматизация с использованием Gulp 99

Административная область Общедоступная область


на базе Angular на базе React
"\
• admin/index.js * public/index.js
Browserify => build/admin.js React => build/public.js

• build/admin.js • build/public.js
Конка­ • lib/shared.js Конка­ • lib/shared.js
тенация => build/admin.js тенация => build/public.js

• build/admin.js • build/admin.js
Minify => assets/admin.min.js Minify => assets/public.min.js

• Retina • Retina
• CSS sprite sheets • CSS sprite sheets
Обработка Обработка
• Branding shared between • Branding shared between
графики графики
admin and public admin and public

Рис. 4.1. Два процесса построения с общей функциональностью

G ulp позволяет обеспечить вы сокую степень повторного исп ользован ия кода с по­
мощ ью двух приемов: пр и м ен ен и я плагинов и определен ия ваш их собственны х
задач построения. Как видно из иллю страции, процесс построения представляет
собой поток, поэтом у задачи и плагины м ож но объединять в цепочку. Н апример,
R eact-часть приведенного прим ера можно обработать с использованием G ulp Babel
(www.npmjs.com/package/gulp-babel/) и встроенны х средств gulp.src:
g u lp .s rc ('p u b lic /in d e x .js x ')
.pipe(babel({
p resets: ['e s2 0 1 5 ', 'r e a c t ']
}))
.pipe(m inify())
.p ip e (g u lp .d e s t( 'b u ild /p u b lic .js ') );

В эту цепочку м ож но даж е достаточно легко добавить стадию конкатенации. Но,


преж де чем описы вать синтаксис более подробно, посмотрим, как создать неболь­
ш ой проект Gulp.

4.3.1. Добавление Gulp в проект


Ч тобы добавить G ulp в проект, необходим о установить пакеты gulp-cli и gulp из
npm. М ногие п ользователи устан авли ваю т gulp-cli глобально, поэтом у прим еры
100 Глава 4. Системы построения фронтэнда

G ulp м ож но запустить просты м вводом ком анды gulp. Учтите, что, если пакет gulp
был ранее установлен глобально, следует вы полнить команду npm rm - -g lo b a l gulp.
В ы полните следую щ ий фрагмент, чтобы установить gulp-cli глобально и создать
новы й проект N ode с зависим остью от Gulp:

npm i --g lo b al g u lp -c li
mkdir gulp-example
cd gulp-example
npm i n i t -y
npm i -save-dev gulp

Затем создайте ф айл gulpfile.js:

touch g u lp f ile .js

О ткройте ф айл. Д алее мы воспользуем ся G ulp д ля построения небольш ого п ро­


екта R eact, в котором исп ользую тся пакеты gulp-babel (www.npmjs.com/package/
gulp-babel), gulp-sourcem aps и gulp-concat:
npm i --save-dev gulp-sourcemaps gulp-babel babel-preset-es2015
npm i --save-dev gulp-concat react react-dom babel-p reset-react

Н е забудьте и сп о льзо вать npm с клю чом - -sa v e -d e v , когда вы хотите добавить
плагины G ulp в проект. Если вы эксперим ентируете с новы ми плагинам и и позднее
реш ите удалить их, используйте ком анду npm u n i n s t a l l - -sav e-d ev , чтобы удалить
их из ./node_modules и обновить ф айл package.json проекта.

4.3.2. Создание и выполнение задач Gulp


С оздание задач Gulp требует написания кода Node с Gulp A PI в файле с именем gulp­
file.js. G ulp A P I содерж ит методы для таких операций, как поиск ф айлов и передача
их через плагины, которы е каким -то образом их преобразовываю т.

П опробуйте сами: откройте ф айл gulpfile.js и создайте задачу построения, которая


использует gulp.src д ля поиска ф айлов JS X , Babel д ля обработки ES2015 и React,
а затем вы полняет конкатенацию д ля сл ияния ф айлов, как показано в листинге 4.1.

Листинг 4.1. Gulp-файл для ES2015 и React с использованием Babel


Плагины Gulp загружаются также,
const gulp = re q u ire ('g u lp '); как и стандартные модули Node.
const sourcemaps = require('gulp-sourcem aps'); .<■
const babel = re q u ire ('g u lp -b a b e l');
const concat = re q u ire ('g u lp -c o n c a t'); Встроенні средства gulp.src используются для
Встроенные
поиска всех файлов React с расширением JSX.
g u lp .ta s k ( 'd e f a u lt', () => {
return g u lp .s rc ('a p p /* .js x ') Запускает анализ файлов для построения
.pipe(sourcem aps.init()) < отладочных карт исходного кода.
.pipe(babel({
4.3. Автоматизация с использованием Gulp 101

p resets: ['e s2 0 1 5 ', 'r e a c t'] < ---- Настраивает gulp-babel для использования ES2015 и React (jsx).
}))
.p ip e ( c o n c a t( 'a ll.J s ') ) < -------- Объединяет все файлы с исходным кодом вall.js.
.p ip e (so u rc e m a p s.w rite('.')) < -------- Записывает файлы с картами отдельно.
.p ip e ( g u lp .d e s t( 'd is t') ) ; < -------- Перенаправляет все файлы в dist/folder.
});

В листинге 4.1 исп ользую тся плагины G ulp д ля получения, обработки и записи
ф айлов. С н ач ал а все входны е ф ай л ы нах о д ятся по ш аблону, после чего плагин
gulp-sourcem aps используется д ля сбора м етрик карты исходного кода для отладки
на стороне клиента. О братите вним ание на то, что д ля работы с картам и исходно­
го кода нуж ны две фазы: в одной вы указы ваете, что хотите использовать карты,
а в другой записы ваете ф айлы карт. Также gulp-babel настраивается д ля обработки
ф айлов с использованием ES2015 и React.

Э та задача G ulp м ож ет быть вы полнена ком андой gulp в терминале.

В этом прим ере все ф айлы преобразую тся с использованием одного плагина. Так
уж выш ло, что B abel и транспили рует код R eact JS X , и преобразует ES2015 и ES5.
Когда это будет сделано, вы полняется конкатенация ф айлов с использованием пла­
гина gulp-concat. П осле заверш ен ия тран сп и л яц и и происходит безопасная запись
карт исходного кода, и итоговая сборка пом ещ ается в папку dist.

Ч тобы опробовать этот g u lp -ф айл, создайте ф айл JS X с именем app/index.jsx. П р о ­


стой ф ай л JS X , кото р ы й м ож ет и сп о л ьзо в аться д л я т е сти р о ван и я G ulp, мож ет
вы глядеть так:

import React from 'r e a c t ';


import ReactDOM from 'react-dom ';

ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementByld('example')
);

Gulp позволяет легко описать стадии построения на JavaScript, а при помощ и метода
g u lp .ta s k ( ) вы смож ете добавить в этот ф айл собственны е задачи. Зад ачи обычно
строятся по одной схеме:

О ввод — получение исходны х ф айлов;

О тр асп и л яц и я — прохож дение через плагин, которы й преобразует их;

О кон катенаци я — объединение ф айлов д ля создания м онолитной сборки;

О вы вод — назначение м естонахож дения или перем ещ ение вы ходны х ф айлов.

В преды дущ ем прим ере sourcem aps я в л яется особым случаем, потому что требует
двух конвейеров: д ля ко н ф и гурац ии и д ля вы вода ф айлов. Это логично, потому
102 Глава 4. Системы построения фронтэнда

что карты исходного кода за в и с я т от со о тветстви я и сход ной н ум ерац и и строк


и нум ерации строк в транспили рован ной сборке.

4.3.3. Отслеживание изменений


П оследнее, что потребуется ф ронтэнд-разработчику, — цикл « п о строен и е/об н ов­
ление». Д л я упрощ ения процесса легче всего воспользоваться плагином G ulp для
отслеж ивания изм енений в ф айловой системе. Впрочем, существуют и альтернатив­
ные реш ения. Н екоторы е библиотеки хорош о работаю т с «горячей» перезагрузкой,
более общие проекты на базе D O M и CSS хорош о работаю т с проектом LiveReloard
(http://livereload.com/).
Н априм ер, к проекту из листинга 4.1 м ож но добавить gulp-w atch (www.npmjs.com/
package/gulp-watch). Д обавьте пакет в проект:
npm i --save-dev gulp-watch

Н е забудьте загрузить пакет в gulpfile.js:

const watch = req u ire('g u lp -w atch ');

А теперь добавьте задачу watch, которая вы зы вает задачу по ум олчанию из преды ­


дущ его примера:

g u lp .ta sk ('w a tc h ', () => {


w a tc h ('a p p /* * .jsx ', () => g u lp .s ta r t( 'd e f a u lt') ) ;
});

Э тот ф рагмент определяет задачу с именем watch, а затем использует вызов w atch()
д ля отслеж ивани я изм енений в ф айлах R eact JSX . П ри каж дом изм енении ф айла
вы полняется задача построения по умолчанию . С небольш им и изм ен ен иям и этот
рецепт может использоваться для построения ф айлов SASS (S yntactically Awesome
Style Sheets), оптим изации граф ики и вообще практически всего, что только может
понадобиться в проектах кли ен тской части.

4.3.4. Использование отдельных файлов


в больших проектах
П о мере роста проекта обычно приходится добавлять новые задачи G ulp. Рано или
поздно образуется больш ой файл, в котором трудно разобраться. Впрочем, проблема
реш ается: разбейте свой код на модули.

К ак вы уже видели, G ulp использует систему модулей N ode д ля загрузки плагинов.


С пециальной системы загрузки плагинов не существует; использую тся стандартные
модули. Также система модулей Node может использоваться для разбивки длинны х
g u lp -ф айлов с целью упрощ ения сопровож дения. Ч тобы использовать отдельны е
ф айлы , вы полните следую щ ие действия.
4.3. Автоматизация с использованием Gulp 103

1. С оздайте папку с именем gulp, а в ней — влож енную папку tasks.


2. О пределите свои задачи с использованием обычного синтаксиса g u lp .ta s k ( )
в отдельны х файлах. Х орош ее практическое правило — создать отдельны й ф айл
д ля каж дой задачи.
3. С оздайте ф айл с именем gulp/index.js для вклю чения всех ф айлов задач Gulp.
4. В клю чите ф айл gulp/index.js в gulpfile.js.

И ерар х и я ф айлов долж на вы глядеть так:

g u lp f ile .js
gulp/
gulp/in d ex .js
gulp/tasks/developm ent-build.js
g u lp /task s/p ro d u ctio n -b u ild .js

О п и сан н ы й прием пом огает у поряд очи ть структуру проектов со слож ны м и з а ­


дачам и построения, но он такж е м ож ет работать в сочетани и с м одулем gulp-help
(www.npmjs.com/package/gulp-help). Э тот м одуль позвол яет д окум ентировать з а ­
дачи G ulp; ком ан да g u lp h e lp вы водит и н ф орм ац и ю о каж дой задаче. О н удобен
при работе в группе и ли если вам п р и х о ди тся работать со м нож еством проектов,
исп ользую щ их G ulp. Н а рис. 4.2 показано, как вы гл яд и т результат вы п ол н ен и я
команды .

-/Projects/world-domination: gulp help


[10:33:383 Using gulpfile -/Projects/world-domination/gulpfile.js
[10:33:38] Starting 'help'...

Usage
gulp [TASK] [OPTIONS...]

Available tasks
help Display this help text,
version prints the version. Aliases: v, V

[10:33:38] Finished 'help' after 1.2 ms

Рис. 4.2. Пример вывода gulp-help

G ulp — средство автом атизации проектов общего назначения. G ulp хорош о р аб о­


тает при добавлении в проекты кроссплатф орм енны х сценариев д ля вы полнения
служебны х операций — например, проведения сложных тестов на стороне клиентов
или создания тестов над общим набором объектов для баз данных. Х отя Gulp может
использоваться д ля построения активов на стороне клиента, д ля этой цели такж е
сущ ествую т специальны е инструм енты — как правило, они требую т меньш его объ­
ема кода и настройки, чем Gulp. О дним из таких инструментов явл яется w ebpack —
пакет, п р ед назначенны й д л я у п ак о вки м одулей J a v a S c rip t и CSS. В следую щ ем
разделе продем онстрировано использование w ebpack в проекте React.
104 Глава 4. Системы построения фронтэнда

4.4. Построение веб-приложений


с использованием webpack
П акет w ebpack специально предназначен д ля построения веб-прилож ений. П ред­
ставьте, что вы работаете с дизайнером , которы й уж е создал статический сайт для
одностраничного веб-приложения, и теперь хотите адаптировать его для построения
более эф ф ективного кода ES2015 Jav aS crip t и CSS. С Gulp вы пишете код JavaS cript
д ля у п равлени я систем ой построения, поэтом у вам предстоит написать g u lp -ф айл
и несколько других задач. С w ebpack вы пиш ете кон ф и гурац и он н ы й ф айл, а з а ­
тем подклю чаете новую ф ункциональность при помощ и плагинов и загрузчиков.
В некоторы х сл у чаях н и к ак ая д о п о л н и тел ьн ая настрой ка вообщ е не требуется;
достаточно ввести команду webpack в ком андной строке, передать в аргументе путь
к исходном у коду — и она построит проект. Если вас интересует, как это выглядит,
обратитесь к разделу 4.4.4.

О дно из преимущ еств w ebpack заклю чается в том, что эта система позволяет быстро
н астр о и ть си стем у п остроен и я, поддерж иваю щ ую и н кр ем ен тн ы й реж им . Если
настроить ее для автоматического построения при изм енении ф айлов, ей не об яза­
тельно будет перестраивать весь проект при изм енении одного файла. В результате
процесс построения упрощ ается и становится более понятны м.

В этом разделе показано, как использовать w ebpack для небольш их проектов React.
Н о сначала определим ся с терм инологией, п ри нятой в webpack.

4.4.1. Пакеты и плагины


П реж де чем создавать проект w ebpack, необходимо разобраться с терминологией.
П лагины w ebpack изм еняю т поведение процесса построения. Н апример, они м о ­
гут вклю чать такие операции, как автом атическая отправка активов в Am azon S3
(https://github.com/MikaAK/s3-plugin-webpack) или удаление дубликатов ф айлов
из вывода.

В отличие от плагинов, загрузчики (lo ad ers) представляю т преобразования, п р и ­


м еняем ы е к ф айлам ресурсов. Е сли вам потребуется преобразовать SASS в CSS
(и л и ES2015 в ES5), значит, вам нуж ен загрузчик. Загрузч и ки представляю т собой
функции, преобразующие входной текст в выходной; они могут работать асинхронно
или синхронно. П лагины представляю т собой экзем пляры классов, которые могут
подклю чаться к w ebpack A P I более низкого уровня.

Е сли вам нуж но преобразовать код R eact, C offeeScript, SASS и ли любого другого
тран сп и л и р у ем о го язы ка, вам нуж ен загрузчик. Е сли ж е вам нуж но обработать
Jav aS crip t или вы полнить какие-то операции с группами файлов, вам нуж ен плагин.

В следую щ ем разделе вы увидите, как использовать загрузчи к Babel д ля п реоб ­


разован и я проекта R eact ES2015 в пакет, адаптированны й д ля браузера.
4.4. Построение веб-приложений с использованием webpack 105

4.4.2. Настройка и запуск webpack


В этом разделе мы воссоздадим прим ер R eact из листинга 4.1 с использованием
w ebpack. Д л я начала установите R eact в новом проекте:

mkdir webpack-example
npm i n i t -y
npm in s t a l l --save react react-dom
npm in s t a l l --save-dev webpack babel-loader babel-core
npm in s t a l l --save-dev babel-preset-es2015 b ab el-p reset-react

П о сл е д н яя стр о ка у стан а в л и в а е т п л аги н B abel д л я E S2015 и п р ео б р азо вател ь


R eact д ля Babel. Д алее необходимо создать ф айл с именем webpack.config.js, кото­
ры й сообщ ает webpack, где искать входной ф айл, куда записать результат и какие
загру зчи ки следует использовать. М ы воспользуем ся загрузчи ком b a b e l- lo a d e r
с дополнительны м и настройкам и д ля React, как показано в следую щ ем листинге.

Листинг 4.2. Файл webpack.config.js


const path = r e q u ire ('p a th ');
const webpack = require('w ebpack');
module.exports = {
entry: './a p p /in d e x .js x ', -<-------- Входной файл
output: { p a t h : _dirname, filename: 'd is t/b u n d le .js ' }, <.-------- Выходной файл.
module: {
loaders: [
{
t e s t : /.js x ? $ /, < -------- Находит все файлы JSX.
loader: 'b a b e l-lo a d e r',
exclude: /node_modules/,
query: {
p resets: ['e s2 0 1 5 ', 'r e a c t '] < -------- Использует плагины React и Babel ES2015.
}
}
]
},
};

К онф игураци онны й ф айл содерж ит все необходимое для успеш ного построения
при лож ений R eact с ES2015. П роцесс настройки достаточно прост: определите зн а­
чение e n try с главны м файлом, загруж аю щ им прилож ение. Затем укаж ите каталог,
в которы й долж ен запи сы ваться вывод; если каталог еще не существует, он будет
создан. Затем определите загрузчи к и свяж ите его с поиском ф айлов по ш аблону
при помощ и свойства t e s t . Н аконец, задайте все необходимые парам етры загр у з­
чика. В данном прим ере эти парам етры загруж аю т B abel-плагины ES2015 и React.

Вклю чите код R eact JS X в ф айл app/index.jsx; используйте ф рагмент из раздела 4.3.2.
Теперь ком анда ./n o d e _ m o d u le s/.b in /w e b p a c k отком пилирует ES5-версию ф айла
с зависи м остям и React.
106 Глава 4. Системы построения фронтэнда

4.4.3. Использование сервера для разработки webpack


Если вы хотите избежать необходимости перестраивать проект при каж дом изм ене­
нии ф айла React, используйте сервер для разработки w ebpack (http://webpack.github.
io/docs/webpack-dev-server.html). В исходном коде книги он размещ ается в примере
w ebpack-hotload-exam ple (ch04-front-end/webpack-hotload-example). Этот компактный
сервер Express запускает w ebpack с кон ф и гурац ионн ы м ф айлом w ebpack при и з ­
м енении ф айлов, а затем предоставляет изм енивш иеся активы браузеру. Запустите
его с указанием порта, отличного от порта основного веб-сервера; это означает, что
теги s c r i p t долж ны вклю чать другие U R L -адреса на стадии разработки. Сервер
строит активы и сохраняет их в пам яти (вм есто вы ходной папки w ebpack). Сервер
д л я разр або тки такж е м ож ет и сп о льзо ваться д ля горячей загрузки м одулей (по
аналогии с серверам и LiveReload).

Ч тобы добавить сервер д ля разработки w ebpack, вы полните следую щ ие действия.


1. У станови те w e b p a c k - d e v - s e r v e r к о м ан д ой npm i - - s a v e - d e v w eb p ac k -d ev -
server@ 1.14.1.
2. Д обавьте парам етр p u b lic P a th в раздел o u tp u t в ф айле webpack.config.js.
3. Добавьте в каталог построения ф айл index.html, которы й будет управлять загруз­
кой пакетов Ja v a S c rip t и CSS. П роследите за тем, чтобы порт совпадал с портом,
заданны м на следую щ ем шаге.
4. Зап у сти те сервер с парам етрам и, наприм ер w eb p ack -d ev -serv er -- h o t - - i n l i n e
- - c o n te n t- b a s e d i s t / - - p o r t 3001.
5. П осетите http://localhost:3001/ и загрузите прилож ение.

О ткройте ф айл webpack.config.js из листинга 4.2, изм ените свойство o u tp u t и д о ­


бавьте в него p u b lic P a th :

output: {
path: p ath .reso lv e(_dirname, 'd i s t ') ,
filename: 'b u n d le .js ',
publicPath: '/ a s s e t s / '
},

С оздайте новы й ф айл dist/index.html, как показано в листинге 4.3.

Листинг 4.3. Пример шаблона HTML для веб-приложения React


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Warning: Dev server o n ly < /title>
</head>
<body>
<div id="example"></div>
4.4. Построение веб-приложений с использованием webpack 107

<s c r ipt s rc ="/a sse ts/b u n d le . j s "></ s c r i pt > < ---- 1 общедоступный путь пакета,
</body> построенного с webpack.
</html>

О тк р о й те package.json и добавьте ко м ан ду зап у ск а сервера w ebpack в свойство


s c r ip t s :

"sc rip ts " : {


"server:dev": "webpack-dev-server --hot -in lin e
--content-base d is t/ --p o rt 3001"
},

П арам етр - -h o t заставл яет сервер д ля разр аботки исп ользовать реж им горячей
перезагрузки модулей. Е сли отредактировать R ea ct-ф ай л прим ера в app/index.jsx,
браузер долж ен обновиться. М еханизм обновления задается параметром - - in lin e .
В реж име i n l i n e сервер для разработки внедряет код для управления обновлением
пакета. Также сущ ествует версия ifram e, которая заклю чает всю страницу в ifram e.

Теперь запустите сервер д ля разработки:

npm run server:dev

Зап уск сервера для разработки w ebpack инициирует построение и запускает сервер
с про сл у ш и ван и ем порта 3001. Ч тобы про тести ровать результат, введи те адрес
http://localhost:3001 в браузере.

ГОРЯЧАЯ ПЕРЕЗАГРУЗКА

И з-за R eact и других ф рейм ворков, вклю чая AngularJS, существуют проекты
горячей п ерезагрузки м одулей, предназначенны е д л я кон кретны х ф р ей м ­
ворков. Н екоторы е из них задействую т ф рейм ворки потоков данны х (такие,
как R edux и R elay); это означает, что код м ож ет обновляться с сохранением
текущ его состояния. Это идеальны й способ вы полнен ия перезагрузки кода,
потом у что вам не придется заново проделы вать все необходимое для вос­
создания состоян ия пользовательского интерф ейса, над которы м вы рабо­
таете. О днако п ри веден н ы й прим ер в м еньш ей степени п ри вязан к R eact
и хорош о подходит д ля начала работы с серверам и д ля разработки webpack.
О бязательно поэксперим ентируйте и найдите тот вариант, которы й лучш е
подходит д ля ваш его проекта.

4.4.4. Загрузка модулей и активов CommonJS


В этой главе мы и сп о л ьзо в ал и R eact и Babel, но если вы и сп о льзу ете w ebpack
с более традиц ионн ы м и проектам и C om m onJS, w ebpack мож ет предоставить всю
необходимую ф ункц иональн ость без браузерной оболочки совм естимости (shim )
Com m onJS. О н даж е м ож ет загруж ать ф айлы CSS.
108 Глава 4. Системы построения фронтэнда

WEBPACK и COMMONJS
Ч тобы исп ользовать син такси с м одулей C om m onJS с w ebpack, вам не придется
ничего настраивать. Д опустим, им еется ф айл, в котором используется re q u ire :

const hello = r e q u i r e ( './h e l l o ') ;


h e llo ();

И другой ф айл, определяю щ ий ф ункцию h e llo :

module.exports = function() {
return 'h e llo ';
};

В этом случае будет достаточно небольш ого конф игурационного ф ай л а w ebpack


д ля определения точки входа (п ер вы й ф рагм ент) и вы ходного пути построения:

const path = r e q u ire ('p a th ');


const webpack = require('w ebpack');

module.exports = {
entry: './a p p /in d e x .js ',
output: { path: _dirname, filename: 'd is t/b u n d le .js ' },
};

Э тот прим ер показы вает, насколько сильно различаю тся G ulp и webpack. w ebpack
полностью сп ец и али зи р у ется на построении пакетов и в кон тексте этой задачи
может генерировать пакеты с оболочками совместимости Com m onJS. О ткры в файл
dist/bundle.js, вы увидите в начале ф айла оболочку совместимости webpackBootstrap,
а затем все ф айлы из исходного дерева кода, вклю ченны е в зам ы кан ия для м одели­
ровани я системы модулей. С ледую щ ий ф рагм ент я в л я ет ся частью пакета:

function(module, exports, _webpack_require__) {

const hello = _webpack_require__(1);

h e llo ();

/***/ },
/* 1 */
/***/ function(module, exports) {

module.exports = function() {
return 'h e llo ';
};

К ом м ентарии в коде показы ваю т, где о пред еляю тся м одули, а ф айлы получаю т
доступ к объектам module и e x p o rts как аргументам своих зам ы кан ий д ля м одел и ­
ровани я A P I м одуля Com m onJS.
4.5. Заключение 109

Использование пакетов NPM с webpack


Вы мож ете сделать следую щ ий ш аг и вклю чить модули, загруж енны е из npm. Д о ­
пустим, вы хотите использовать jQ uery. Вместо того чтобы вклю чать соответствую ­
щ ий тег s c r i p t в страницу, вы можете установить jQ u e ry командой npm i --sav e -d ev
jq u e ry , а потом загрузить как обы чны й модуль Node:
const Jquery = re q u ire ('jq u e ry ');

Это означает, что w ebpack предоставляет в ваш е распоряж ение м одули C om m onJS
и доступ к м одулям из npm без какой-либо д ополнительной конфигурации!

ПОИСК ЗАГРУЗЧИКОВ И ПЛАГИНОВ

Н а сайте w ebpack доступны списки загрузчиков ( h ttp s://w e b p a c k .g ith u b .io /


d o c s/list-o f-lo a d e rs.h tm l) и плагин ов ( h ttp s ://w e b p a c k .g ith u b .io /d o c s /lis t-
of-plugins.htm l). И нструм енты w ebpack такж е м ож но найти в npm; хорош ей
отправной точкой станет поиск по клю чевом у слову w ebpack ( www.npm js.
co m /b ro w se /k e y w o rd /w e b p ac k ).

4.5. Заключение
О Если вам понадобится автоматизировать простые задачи или запускать сценарии,
сценарии npm идеально подходят д ля этой цели.

О G ulp м ож ет использоваться д ля нап исания более слож ны х задач на Jav a S crip t


и я в л яе т с я кросс-платф орм енной технологией.

О Е сли g u lp -ф ай л ы п олуч аю тся сли ш ком д ли нны м и , код м ож н о р азд ел и ть на


несколько ф айлов.

О w ebpack мож ет использоваться для генерирования пакетов на стороне клиента.

О Е сли вам нуж но построить пакет на стороне клиента, реш ение с w ebpack может
быть м енее трудоем ким , чем настройка соответствую щ его сценари я с Gulp.

О w ebpack поддерж ивает горячую перезагрузку модулей; это означает, что и зм е­


нения в коде будут видны без об новления браузера.
5 Фреймворки на стороне
сервера

Э та глава полностью посвящ ена веб-разработке на стороне сервера. Вы найдете


в ней ответы на разны е вопросы: как вы брать идеальны й ф рейм ворк для заданного
проекта? К аки м и достоин ствам и и недостаткам и обладает каж ды й ф рейм ворк?

Зад ача вы бора правильного ф рейм ворка сложна, потому что их слож но сравнивать
в систем е едины х кри тери ев. У м ногих разработч и ков нет врем ени и зучать все
ф реймворки, поэтому мы склонны приним ать субъективные реш ения относительно
тех ф рейм ворков, с которы м и у нас им еется опыт работы. И ногда разны е ф р ей м ­
ворки использую тся совместно. Н апример, Express мож ет использоваться с более
кр у п н ы м и п р и л о ж е н и я м и , а м и к р о сер ви сы , обесп ечи ваю щ и е раб о ту б ольш их
прилож ений, м огут быть написаны на hapi.

П редставьте, что вы стр о и те си стем у у п р а в л е н и я ко н тен то м (C M S ). С и стем а


п р ед н азн ач ен а д л я у п р ав л ен и я ю ри ди чески м и д окум ентам и, соби раем ы м и и с ­
следо вательско й ф ирм ой. Т акая систем а м ож ет бы ть построена с прим енением
разны х ф рейм ворков:

О загрузка, отправка и чтение докум ентов — Express;

О м икросервис генератора P D F — hapi;

О ком понент электронной ком м ерции — Sails.js.

И д еальн ы й ф рей м ворк д ля заданного проекта зависи т от потребностей проекта


и группы, работаю щ ей над этим проектом. В этой главе д ля вы бора ф реймворка,
подходящ его для конкретного типа проекта, использую тся персонажи, то есть вы ­
м ы ш ленны е люди. Вы взглянете на Koa, hapi, Sails.js, D erbyJS, F latiro n и LoopBack
глазами этих воображаемых программистов. П ерсонажи определяю тся в следующем
разделе.

5.1. Персонажи
М ы не с о б и р а е м с я р а с х в а л и в а т ь н а все л ад ы од и н ф р е й м в о р к , к о т о р ы й м о ­
ж ет и сп о льзо ваться в каж дом проекте. Гораздо лучш е д ействовать многогранно
5.1. Персонажи 111

и исп ользовать набор инструм ентов, подходящ их д ля каж дой задачи. П рактика
прим енения персонаж ей для проведения анализа в ходе проектирования получила
ш ирокое распространение еще и потому, что она помогает проектировщ икам со ­
переж ивать своим пользователям .

В этой главе персонаж и помогут вам взглянуть на фрейм ворки со стороны и понять,
как разны е классы проектов подходят д ля разны х реш ений. П ерсонаж и опред еля­
ю тся в контексте проф ессиональной ситуации и средств разработки. С корее всего,
вы найдете что-то общее хотя бы с одним из трех персонаж ей, придум анны х нами
д ля этой главы.

5.1.1. Фил: штатный разработчик


Ф и л три года проработал полностековы м веб-разработчиком . У него им еется н е­
которы й опы т работы на Ruby, P y th o n и клиентском JavaS cript:

О Положение — ш татны й работник, полностековы й разработчик.

О Тип работы — работа с кли ен тской частью , разработка на стороне сервера.

О Компьютер — M acB ook Pro.

О Инструменты — Sublim e Text, Dash, xScope, Pixelm ator, Sketch, G itH ub.

О Подготовка — среднее общ ее образование; начинал карьеру как программ ист-


лю битель.

Типичны й рабочий день Ф и л а вклю чает работу с дизайнерам и и U X -экспертам и на


встречах в agile-стиле, где обсуж даю тся или разрабаты ваю тся новые возможности,
а такж е рассм атриваю тся вопросы сопровож дения или и сп равлени я ошибок.

5.1.2. Надин: разработчик открытого кода


Н адин переш ла на контрактную систему после успеш ного начала карьеры на долж ­
ности корпоративного веб-разработчика:

О Положение — внеш татны й работник, специалист по Jav aS crip t.

О Тип работы — програм м ирование на стороне сервера, иногда программирование


на Go и Erlang. Также пиш ет поп улярн ое веб-прилож ение с откры ты м кодом —
базу данны х ф ильмов.

О Компьютер — вы сокоп роизводительн ы й PC , Linux.

О Инструменты — Vim, tm ux, M ercurial, средства командного интерпретатора.

О Подготовка — диплом в области ком пью терны х технологий.

В ходе своего рабочего дня Н адин обычно распределяет время между двумя своими
крупны м и кли ен там и и работой над своим проектом с откры ты м кодом. В своей
112 Глава 5. Фреймворки на стороне сервера

кон трактн ой работе она при м ен яет разработку через тестирование, тогда как ее
проекты с откры ты м кодом ориентированы на разработку ф ункциональности.

5.1.3. Элис: разработчик продукта


Э лис работает над успеш ны м прилож ением д ля iOS, но такж е участвует в р азр а­
ботке веб-A PI своей компании:

О Положение — ш татны й работник, программист.

О Тип работы — разр або тка д ля iO S; такж е отвечает за веб-п ри л ож ен и я и веб ­


службы.

О Компьютер — M acB ook Pro, iPad Pro.

О Инструменты — Xcode, Atom , Babel, Perforce.

О Подготовка — у ч ен ая степень; один из первы х п я ти сотр у д н и ко в стартапа,


в котором она работает.

Э лис вы нуж денно работает с Xcode, O bjective-C и Swift, но втайне предпочитает


Ja v a S c rip t и с больш им интересом относится к ES2015 и Babel. С удовольствием
зани м ается разработкой новы х веб-служ б д ля поддерж ки при лож ений iO S и н а­
стольных прилож ений своей компании и хочет чаще работать над веб-приложениями
на базе React.

О пределивш ись с персонаж ами, перейдем к определению терм ина «ф реймворк».

5.2. Что такое фреймворк?


Н екоторы е ф рейм ворки на стороне сервера, рассм атриваем ы е в этой главе, с тех­
нической точки зр ен и я ф рейм воркам и вообщ е не являю тся. К сожалению , термин
«ф рейм ворк» перегруж ен изобилием см ы слов и несет разны е п он яти я д ля разны х
программистов. В сообществе Node больш инство этих проектов было бы правильнее
назы вать м одулям и, но при прям ом сравнении этого сем ейства библиотек ж ел а­
тельно иметь более точное определение.

В проекте L oopB ack (http://loopback.io/resources/#compare) использую тся следу­


ю щ ие определения:

О API-фреймворк — библи отека д ля построений веб -A PI с поддерж кой ф р ей м ­


ворка, упрощ аю щ его стр у кту р и р о в ан и е п р и ло ж ен и я. С ам проект L oopB ack
определяется как ф рейм ворк этого типа.

О Библиотека HTTP-сервера — к этой категории относятся все разработки на базе


Express, вклю чая Koa и Kraken.js. Э ти библиотеки помогают строить приложения,
основанны е на ком андах и м арш рутах H TTP.
5.3. Koa 113

О Фреймворк HTTP-сервера — ф рейм ворк д ля построения м одульны х серверов,


использую щ их протокол H T T P д ля ком м уникаций. П рим ер ф рейм ворков т а ­
кого рода — hapi.

О Веб-фреймворк MVC — к этой категории относятся ф рейм ворки на базе паттерна


«М одель-П редставление-К онтроллер», вклю чая Sails.js.
О Полностековый фреймворк — эти ф рейм ворки использую т Jav aS crip t на сервере
и в браузере и могут совместно использовать клиентский и серверный код (такой
код назы вается изоморфным). В частности, D erbyJS я в л яе т с я полностековы м
ф рейм ворком MVC.

М ногие разработчики N ode поним аю т под термином «ф рейм ворк» второй пункт:
библиотеку H T T P -сервера. В следую щ ем разделе вы познаком итесь с K oa — б и ­
б лиотекой сервера, которая использует генераторы (н овы й синтаксис ES2015) как
уни кальны й подход к построению пром еж уточного П О (ф у н кц и й промеж уточной
обработки) H TTP.

5.3. Koa
Koa (http://koajs.com/) базируется на Express, но использует синтаксис генераторов
ES2015 д ля определения пром еж уточного П О (ф у н к ц и й пром еж уточной обработ­
ки). Это означает, что промежуточное П О можно писать практически в синхронном
стиле. О тчасти это реш ает проблем у пром еж уточного ПО, сильно зависящ его от
обратны х вызовов. С K oa вы мож ете воспользоваться клю чевы м словом y ie ld для
выхода и последую щ его возврата к пром еж уточном у ПО.

В табл. 5.1 приведена сводка основны х возм ож ностей Koa.

Таблица 5.1. Основные возможности Koa

Тип библиотеки Библиотека HTTP-сервера


Функциональность Промежуточное ПО с использованием генераторов, мо­
дель «запрос/ответ»
Рекомендуемое приме­ Облегченные веб-приложения, нежесткие HTTP API,
нение одностраничные веб-приложения
Архитектура плагинов Промежуточное ПО
Документация http://koajs.com /
Популярность 10 000 звезд на GitHub
Лицензия MIT

Л и сти н г 5.1 показы вает, как использовать K oa д ля хроном етраж а запросов. Д ля


этого управление уступается следую щ ему ком поненту промеж уточного П О , после
заверш ен ия которого управление продолж ается на стороне вызова.
114 Глава 5. Фреймворки на стороне сервера

Листинг 5.1. Хронометраж запросов в Koa


const koa = re q u ire ('k o a ');
const app = koa();

app.use(function*(next) {
const s ta r t = new Date; (2) Уступает управление следующему
yield next; <— компоненту промежуточного ПО.
(1) Использует синтаксис
const ms = new Date - s ta r t; генераторов для функций
console.log('% s %s - %s', this.m ethod, t h i s .u r l , ms); промежуточного ПО.
});

app.use(function*() {
this.body = 'Hello World';
}); (3) Управление передается
в позицию исходного вызова yield.
ap p.listen(3000);

В листинге 5.1 генераторы ( 1 ) использую тся д ля переклю чения контекста между


двум я ком понентам и пром еж уточного ПО. О братите вним ание на использование
клю чевого слова fu n c tio n * — в данном случае стрелочная ф ункц ия использоваться
не может. П ри исп ользован ии клю чевого слова y ie ld (2 ) вы полнение передается
вни з по стеку пром еж уточного П О , а затем возвращ ается обратно при возврате
из следую щ его ком п онента (3 ). Д о п о л н и тел ьн ое преи м ущ ество и сп ользован и я
ф у н кц ии-генератора заклю чается в том, что вы м ож ете просто задать th is .b o d y ,
тогда как Express использует ф ункцию д ля отправки ответов: re s .s e n d ( re s p o n s e ).
В терм инологии K oa t h i s назы вается контекстом. Контекст создается для каждого
запроса и используется и д ля и н капсуляции N o d e-объектов запроса, и д ля ответа
H T T P (https://nodejs.org/api/http.html). Когда вам нужно обратиться к каким -то дан­
ны м из запроса (наприм ер, парам етрам G E T или cookie), вы используете контекст.
Это относится и к ответу: как было показано в листинге 5.1, вы мож ете управлять
тем, какие данны е отправляю тся браузеру, задавая значения в th is .b o d y .

Е сли преж де вы исп о льзо вал и пром еж уточное П О E xpress и син такси с генерато­
ров, изучить K oa будет неслож но. Е сли х отя бы что-то из этого окаж ется д л я вас
новы м , вам будет достаточно трудно пон ять л огику K oa — и л и по край н ей м ере
увидеть, чем хорош этот стиль. Н а рис. 5.1 более подробно показано, как y ie ld
передает вы полнен ие м еж ду ко м п онентам и пром еж уточного ПО.

К аж дая ф аза на рис. 5.1 соответствует ном ерам в листинге 5.1. С начала в первом
ком поненте пром еж уточного П О устан авли вается тайм ер (1 ), а затем управление
уступается второму компоненту промежуточного ПО, которы й строит тело (2 ). П о­
сле того как ответ будет отправлен, управление возвращ ается первому компоненту
промеж уточного П О , где вы чи сляется врем я вы полнен ия (3 ). О но вы водится на
терминал вызовом c o n s o le . log, и обработка запроса на этом заверш ается. О братите
вним ание: стадия (4 ) в листинге 5.1 не видна; она обрабаты вается K oa и H T T P -
сервером Node.
5.3. Koa 115

О В первом компоненте О Управление уступается


промежуточного ПО второму компоненту
запускается таймер промежуточного ПО

О Затраченное время О С ответом управление


выводится на консоль возвращается первому
компоненту

Рис. 5.1. Порядок выполнения компонентов промежуточного ПО в Koa

5.3.1. Настройка
Н астройка проекта с Koa требует установки модуля и определения промежуточного
ПО. Если вам нуж на более ш ирокая ф ункциональность (например, A PI м арш рути­
зации, упрощ аю щ ий определение различны х типов запросов H T T P и реакцию на
них), необходимо установить пром еж уточное П О м арш рутизации. Таким образом,
при типичном рабочем процессе используемое ваш им проектом промежуточное ПО
долж но плани роваться заранее, поэтом у вы долж ны заранее собрать инф орм ацию
о популярны х модулях.

РАЗМЫШЛЕНИЯ ПЕРСОНАЖЕЙ

Элис: «М не как разработчику продукта нравится миним ализм ф ункц иональ­


ности Koa, потому что у нашего проекта существуют уникальны е требования
и мы хотим сформировать весь стек в соответствии с наш ими потребностями».
Ф и л: «М не как ш татном у програм м исту каж ется, что ф аза и зуч ен и я п р о ­
м еж уточного П О создает слиш ком м ного проблем. Я предпочел бы, чтобы
это бы ло сделано за меня, потому что во м ногих м оих проектах действую т
похож ие требовани я и я не хочу м ногократно устан авли вать одни и те же
м одули для вы полнен ия основны х операций».
116 Глава 5. Фреймворки на стороне сервера

В следующем разделе продемонстрирован сторонний модуль, реализую щ ий мощную


библиотеку м арш рутизац ии д ля Koa.

5.3.2. Определение маршрутов


О д ним из п о п у л я р н ы х ком п о н ен то в п р ом еж уточного П О д л я м ар ш р у ти зац и и
я в л яе т с я k o a-ro u ter (https://www.npmjs.com/package/koa-router). Как и Express, он
базируется на ком андах H TTP, но в отличие от Express поддерж ивает A P I с в о з­
можностью сцепления. Следую щ ий фрагмент показывает, как определяю тся группы
марш рутов:

router
.p o s t( '/p a g e s ', function*(next) {
/ / Создание страницы
})
. g e t( '/p a g e s /:id ', function*(next) {
/ / Визуализация страницы
})
.p u t('p a g e s-u p d a te ', '/p a g e s /:id ', function*(next) {
/ / Обновление страницы
});

И м ена марш рутов задаю тся дополнительны м аргументом. Это очень удобно, п о ­
том у что вы мож ете генерировать U R L -адреса, которы е поддерж иваю тся не всеми
веб-ф рейм воркам и Node. Пример:

ro u te r.u rl('p a g e s -u p d a te ', '9 9 ') ;

Э тот м одуль предоставляет уникальное сочетание возм ож ностей Express и других


веб-ф рейм ворков.

РАЗМЫШЛЕНИЯ ПЕРСОНАЖЕЙ

Ф ил: «Эта библиотека марш рутизации напоминает мне многое из того, что мне
понравилось в R uby on Rails. П ожалуй, к Koa все ж е стоит присмотреться!»
Н адин: «Я виж у возм ож ности д ля разб и ен и я м оих сущ ествую щ их проектов
на м одули и последую щ его распространения кода в сообществе».

5.3.3. REST API


Koa не включает в себя инструменты, необходимые для создания R E ST -совместимых
A P I без р еализаци и некоторого пром еж уточного П О обработки марш рутов. П р и ­
веденны й прим ер м ож ет быть расш ирен д ля реализаци и R E S T -совместимого A PI
в Koa.
5.4. Kraken 117

5.3.4. Сильные стороны


Б ы ло бы легко сказать, что сильны е стороны Koa обусловлены ранним п р и н я ти ­
ем син такси са генераторов, но теперь, когда стандарт ES2015 получил ш ирокое
распространение в сообщ естве N ode, эта возм ож ность уж е перестала быть такой
уникальной. В настоящ ее врем я главное преимущ ество K oa — плавность и удобство
работы при наличии превосходных сторонних модулей; дополнительную инф орм а­
цию м ож но найти в вики K oa (https://github.eom/koajs/koa/wiki#middleware). Р а з ­
работчики продуктов лю бят Koa за элегантны й синтаксис и возможность адаптации
к проектам со специф ическим и требованиям и.

5.3.5. Слабые стороны


Ш ирота возможностей настройки Koa отталкивает некоторых разработчиков. Созда­
ние множ ества м елких проектов с Koa может привести к низкой степени повторного
исп ользован ия кода м еж ду проектам и, если только у вас нет готовы х стратегий.

5.4. Kraken
K raken базируется на Express, но добавляет новую ф ункциональность через специ­
альные модули, разработанные PayPal. О дин из особенно полезных модулей — Lusca
(https://github.com/krakenjs/lusca) — предоставляет прослойку безопасности п р и ­
лож ения. И хотя Lusca м ож ет использоваться и без K raken, одним из преимущ еств
K raken я в л яется предопределенная структура прилож ения.

П ри л о ж ен и я Express и K oa не требую т ни какой конкретной структуры проекта,


и если вы пож елаете упростить работу над новы м проектом, K raken помож ет вам
в этом. В табл. 5.2 приведена сводка основной ф ункц иональн ости Kraken.

Таблица 5.2. Основные возможности Kraken

Тип библиотеки Библиотека HTTP-сервера


Функциональность Жесткая структура проекта, модели, шаблоны
(Dust), укрепление безопасности (Lusca), на­
стройка конфигурации, интернационализация
Рекомендуемое применение Корпоративные веб-приложения
Архитектура плагинов Промежуточное ПО Express
Документация https://www.kraken.com/help/api
Популярность 4000 звезд на GitHub
Лицензия Apache 2.0
118 Глава 5. Фреймворки на стороне сервера

5.4.1. Настройка
Е сли у вас уж е им еется проект Express, K raken м ож но д обавить как ком понент
промеж уточного ПО:

const express = re q u ire ('e x p re s s '),


const kraken = re q u ire ('k ra k e n -js ');

const app = express();


app.use(kraken());
app.listen(3000);

Н о если вы хотите создать новы й проект, попробуйте воспользоваться Yeoman —


генератором K raken, которы й упрощ ает генерирование новы х проектов. П ри п о ­
мощ и генераторов Yeoman м ож но создавать подготовленны е проекты д ля разны х
ф рейм ворков. Н иж е приведена последовательность действий д ля создания специ­
ал и зирован ного проекта K raken с исп ользован ием Yeoman, с предпочти тельной
структурой ф айловой системы Kraken:

$ npm in s t a l l -g yo generator-kraken bower g ru n t-c li


$ yo kraken

hh / '_ _ \
l(@)(@)l Release the Kraken!
) __ (
/ , ') ) ( ( '. \
(( (( )) ))
' \ ') ( ' / '

Tell me a b it about your applicatio n :

[?] Name: kraken-test


[?] D escription: A Kraken application
[?] Author: Alex R. Young

Генератор создает новы й каталог, так что вам не придется делать это самостоятельно.
П осле того как генератор заверш ит работу, вы смож ете запустить сервер, откры ть
адрес http://localhost:8000 и убедиться в этом.

5.4.2. Определение маршрутов


В K ra k e n м ар ш р у ты о п р е д е л я ю тс я н а р я д у с контроллером. В м есто того ч т о ­
бы р а зд е л я т ь о п р е д е л ен и я м ар ш р у то в и о б р аб о тч и к и м ар ш рутов, как это д е ­
л ается в E xpress, K rak en исп о льзу ет реш ение, вдохн овл ен н ое паттерн ом MVC;
оно пол у ч ается д остаточно ком п актны м б лагод аря исп ользован и ю стрелочны х
ф у н к ц и й ES6:
5.4. Kraken 119

module.exports = (ro u ter) => {


r o u t e r . g e t ( '/ ', (req, res) => {
re s .re n d e r('in d e x ');
});
};

М арш руты могут вклю чать в себя парам етры в URL:

module.exports = (ro u ter) => {


r o u te r .g e t( '/p e o p le /:id ', (req, res) => {
const people = { alex: { name: 'Alex' } };
re s .re n d e r('p e o p le /e d it', people[req.param .id]);
});
};

A PI м арш рутизации K raken — express-enrouten (https://github.com/krakenjs/express-


enrouten) — частично вы числяет м арш рут на основании каталога, в котором нахо­
дится файл. П редполож им , им еется ф ай л о вая структура следую щ его вида:

co n tro lle rs
|-u ser
|- c r e a te .j s
|- lis t.js

K raken генерирует м арш руты вида / u s e r / c r e a t e и / u s e r / l i s t .

5.4.3. REST API


K raken мож ет использоваться д ля ф орм ирования R E ST A PI, но не предоставляет
конкретной поддерж ки для них. В озможности express-enrouten в сочетании с разбо­
ром JS O N означают, что вы можете использовать K raken для реализации R EST API.

В средствах марш рутизации K raken имеется поддерж ка команд H T T P для D ELETE,


GET, PO ST, P U T и т. д., благодаря чем у р еал и зац и я R E S T им еет м ного общего
с Express.

5.4.4. Сильные стороны


Так как в поставку K raken вклю чается генератор, на высоком уровне проекты Kraken
похож и друг на друга. Е сли проекты Express м огут иметь сильно различаю щ ую ся
структуру, в п р о ек тах K rak en обы чно и сп о л ьзу ется ед и н ая схем а разм ещ ен и я
ф айлов и каталогов.

Так как K raken предоставляет как библиотеку ш аблонизации (D u st), так и средства
ин терн ац ионализац ии (M ak ara), эти две области идеально интегрированы . Ч тобы
написать ш аблоны D u st с поддерж кой интернационализации, необходимо задать
соответствую щ ий ключ:

<h1>{@pre type="content" key="greeting"/}</h1>


120 Глава 5. Фреймворки на стороне сервера

Затем добавьте ф айл . p r o p e r tie s в locales/language-code/view-name.properties. Ф айлы


свойств представляю т собой просты е пары «клю ч-значение»; если преды дущ ий
прим ер находится в ф айле представления с именем public/templates/profile.dust, то
ф айлом .profile будет ф айл locales/US/en/profile.properties.

РАЗМЫШЛЕНИЯ ПЕРСОНАЖЕЙ

Ф и л: « М ен я раду ет то, что K rak en и сп о л ьзу ет оп ред ел ен н ую стр у кту р у


ф ай л о во й систем ы и кон троллеры д л я м арш рутов. Н екоторы е участни ки
м оей группы уж е знаю т D jango и R uby on Rails, поэтому переход д ля них
будет несложным. Похоже, у K raken хорош ая документация; и в блоге много
полезной инф орм ации».
Элис: «М не нрави тся идея повы ш ения безопасности за счет использования
Lusca, но K raken предоставляет м ного ф ункц иональн ости, которая мне на
самом деле не нуж на. П оэтом у пока я попробую просто ограничиться и с ­
пользованием Lusca».

5.4.5. Слабые стороны


K raken слож нее в изучении, чем K oa или Express. Н екоторы е задачи, реализуем ы е
в E xpress на програм м ном уровне, в ы п о л н яю тся из кон ф и гурац и он н ы х ф айлов
JS O N , и иногда бы вает трудно точно разобраться в том, какие свойства JS O N п о ­
надобятся д ля того, чтобы добиться ж елаем ого эф ф екта.

5.5. hapi
hapi (http://hapijs.com/) — серверный ф реймворк для создания A PI веб-приложений.
Он имеет собственны й A PI плагинов и не вклю чает никакой поддерж ки на стороне

Таблица 5.3. Основные возможности hapi

Тип библиотеки Фреймворк HTTP-сервера


Функциональность Абстракция высокоуровневого серверного контейнера,
заголовки безопасности
Рекомендуемое при­ Одностраничные веб-приложения, HTTP API
менение
Архитектура плагинов Плагины hapi
Документация http://hapijs.com/api
Популярность 6000 звезд на GitHub
Лицензия BSD 3 Clause
5.5. hapi 121

клиента или уровня модели базы данных; поддерживает A PI марш рутизации и имеет
собственную обертку д ля сервера H T T P. В hapi вы проекти руете A PI, о р и ен ти ­
руясь на сервер как на основную абстракцию . Б л агод аря встроенной серверной
ф ункц иональн ости подклю чений и ведения ж урн ала hapi хорош о п роявляет себя
в области м асш табирования и у п равлени я с точки зрен и я D evO ps. В табл. 5.3 п р и ­
ведена сводка основной ф ункц иональн ости hapi.

5.5.1. Настройка
С начала создайте новы й проект N ode и установите hapi:

mkdir listing5_2
cd listing5_2
npm i n i t -y
npm in s t a l l --save hapi

Затем создайте новы й ф айл с именем server.js. Д обавьте код из листинга 5.2.

Листинг 5.2. Базовый сервер hapi


const Hapi = r e q u ire ('h a p i');
const server = new H api.Server();
server.connection({
host: 'lo c a lh o s t',
port: 8000
});
s e r v e r .s ta r t( ( e r r ) => {
i f (e rr) {
throw e rr;
}
console.log('S erver running a t : ' , s e rv e r.in fo .u ri);
});

П рим ер м ож но запустить в том виде, в котором он приведен в листинге, но без


м арш рутов он особой пользы не принесет. В следую щ ем разделе вы узнаете, как
в hapi организована обработка марш рутов.

5.5.2. Определение маршрутов


В hapi имеется встроенны й A PI без создания марш рутов. Вы долж ны предоставить
объект, вклю чаю щ и й свой ства д л я м етода запроса, U R L и ф ун кц и ю обратного
вы зова, назы ваем ую обработчиком (h a n d le r). В л и сти н ге 5.3 п ри веден прим ер
определения м арш рута с м етодом -обработчиком .

Листинг 5.3. Сервер Hello World на hapi


const Hapi = r e q u ire ('h a p i');
const server = new H api.Server();
122 Глава 5. Фреймворки на стороне сервера

server.connection({
host: 'lo c a lh o s t',
port: 8000
});

server.route({
method: 'GET',
p a th :'/h e llo ',
handler: (request, reply) => {
return re p ly ('h e llo w orld');
}
});

s e r v e r .s ta r t( ( e r r ) => {
i f (e rr) {
throw e rr;
}
co nsole.log('S erver running a t : ' , s e rv e r.in fo .u ri);
});

Добавьте этот код в предыдущ ий листинг для определения марш рута и обработчика,
которы й отвечает текстом «H ello W orld». П рим ер м ож но запустить командой npm
s t a r t . Введите адрес http://localhost:8000/hello, чтобы увидеть ответ.

hapi не вклю чает предопределенной структуры папок или какой-либо ф ункциональ­


ности MVC; вся структура полностью базируется на серверах. В этом отнош ении
hapi м ож но сравнить с Express. О днако обратите вним ание на клю чевое отличие:
сигнатура обработчика марш рута r e q u e s t, re p ly отличается от сигнатуры Express
re q , re s. О бъекты запроса и ответа hapi такж е отличаю тся от своих эквивалентов
Express: вы долж ны вызвать rep ly вместо того, чтобы оперировать с объектом Express
re s. У Express больш е общего со встроенны м H T T P -сервером Node.

Ч тобы вы йти за пределы этого простого примера и получить доступ к расш иренной
ф ункциональности (наприм ер, предоставлению статических ф айлов), вам понадо­
бятся плагины.

5.5.3. Плагины
hapi использует собственную архитектуру плагинов, и больш инству проектов для
предоставления такой ф ункциональности, как аутен ти ф и каци я и проверка ввода,
требуется несколько плагинов. П ростой плагин, необходим ы й больш инству п ро­
ектов, — in e rt (https://github.com/hapijs/inert) — добавляет поддерж ку статических
ф айлов и обработчики каталогов.

Ч тобы вклю чить in e rt в проект hapi, необходимо сначала зарегистрировать плагин


методом s e r v e r . r e g i s t e r . П ри этом добавляю тся метод r e p l y . f i l e д ля отправки
одиночны х ф айлов и встроенны й обработчик каталогов. Рассм отрим обработчик
каталогов.
5.5. hapi 123

Убедитесь в том, что вы создали проект из листинга 5.2. Затем установите inert:

npm in s t a l l --save in e rt

Теперь плагин м ож но загрузить и зарегистрировать. О ткройте ф айл server.js и д о ­


бавьте следую щ ие строки:

Листинг 5.4. Добавление плагина в hapi


const In e rt = r e q u ir e ( 'in e r t') ;
s e r v e r.re g is te r(In e rt, () => {});

server.route({
method: 'GET',
path: '/{param *}',
handler: {
d irecto ry : {
path: ' . ' ,
redirectToSlash: tru e ,
index: tru e
}
}
});

М арш руты hapi м огут получать не только ф ункции, но и объекты конф игурации
д л я плагинов. В этом ли сти н ге объект d i r e c t o r y вклю чает настрой ки in e rt для
предоставления ф айлов из текущ его пути и вы вода индекса ф айлов в этом ката­
логе. В этом отнош ении hapi отличается от промеж уточного П О Express; пример
показы вает, как плагины могут расш ирять поведение сервера в п ри лож ениях hapi.

5.5.4. REST API


hapi поддерж ивает команды H T T P и параметризацию U RL, что позволяет реализо­
вать R E S T A P I с использованием стандартного A PI марш рутов hapi. В следующем
ф рагм енте представлен м арш рут для обобщ енного м етода удаления:

server.route({
method: 'DELETE',
path: '/ite m s /{ id } ',
handler: (req, reply) => {
/ / Удалить "item" на основании req.param s.id
re p ly (tru e );
}
});

Кроме того, плагины упрощ аю т создание R E S T -совместимы х A PI, например, hapi-


sequelize-crud (https://www.npmjs.com/package/hapi-sequelize-crud) автоматически
генерирует R E S T -совм естимы й A P I на основании м оделей Sequelize (http://docs.
sequelizejs.com/en/latest/).
124 Глава 5. Фреймворки на стороне сервера

РАЗМЫШЛЕНИЯ ПЕРСОНАЖЕЙ

Ф и л: «М не определенно хочется опробовать hapi-sequelize-crud, потому что


у нас уж е есть прилож ения, использую щ ие P o stg reS Q L и M ySQ L, поэтом у
Sequelize м ож ет бы ть подходящ им вариантом . Н о hapi не обладает такой
ф ун кц и о н ал ьн о стью во встроенном виде; кто знает — вдруг плагин п ер е­
станет поддерж иваться? Я не уверен, насколько хорош о hapi подходит для
ш татной работы».
Элис: «К ак разработчик продукта я считаю, что hapi представляет интерес.
К ак и Express, hapi м иним алистичен, однако A P I плагинов более ф орм ален
и вы разителен».
Надин: «Я виж у ряд возмож ностей для создания плагинов с открыты м кодом
для hapi, а сущ ествую щ ие плагины написаны хорош о. Похоже, у hapi техн и ­
чески подкованная аудитория, и мне это нравится».

5.5.5. Сильные стороны


A P I плагинов — одно из самы х больш их преимущ еств исп ользован ия hapi. П л а­
гины могут расш ирять сервер hapi, но при этом они такж е добавляю т другие виды
поведения, от проверки данны х до ш аблонизации. К ром е того, поскольку hapi б а­
зируется на серверах H TTP, эта технология хорош о подходит д ля некоторы х типов
сценариев развертывания. Если вы запускаете несколько серверов, которые должны
быть связаны друг с другом или д ля которы х нуж но организовать распределение
нагрузки, серверны й A P I hapi м ож ет оказаться предпочтительны м по сравнению
с Express или Koa.

5.5.6. Слабые стороны


Н едостатки hapi прим ерно те же, что у Express: м ин им ализм и вы текаю щ ее из него
отсутствие правил структуры проекта. Вы никогда не мож ете быть уверены в том,
когда может прекратиться разработка того или иного плагина, поэтому зависимость
от слиш ком больш ого количества плагинов мож ет создать проблем ы с сопрово­
ж дением в будущем.

5.6. Sails.js
Ф рейм ворки, которы е бы ли рассм отрены до настоящ его момента, представляли
собой м ин им альны е серверны е библиотеки. Sails (http://sailsjs.org/) — ф рейм ворк
M VC (M o d el-V iew -C o n tro ller), при нци пиально отли чаю щ и йся от серверной б и ­
блиотеки. Sails вклю чает в себя библиотеку объектно-реляционного отображ ения
5.6. Sails.js 125

(O R M , O b ject-R elatio n al M apping) для работы с базам и данны х и м ож ет автом а­


ти ч еск и ген ери ровать R E S T A P I. Т акж е под держ и ваю тся м ногие соврем енны е
возм ож ности, вклю чая встроенную поддерж ку W ebSocket. А поклонников React
или A ngular порадует независим ость от клиентской части: Sails не яв л яе тс я полно­
стековы м ф рей м во р ко м и поэтом у м ож ет и сп ользоваться п ракти чески с лю бой
библи отекой и ли ф рейм ворком кл и ен тско й части. В табл. 5.4 приведена сводка
основной ф ункц иональн ости hapi.

Таблица 5.4. Основные возможности Sails

Тип библиотеки Фреймворк MVC

Функциональность Поддержка баз данных с ORM, генерирование REST


API, WebSocket

Рекомендуемое применение Приложения MVC в стиле Rails

Архитектура плагинов Промежуточное ПО Express

Документация http://sailsjs.org/documentation/concepts/

Популярность 6000 звезд на GitHub

Лицензия BSD 3 Clause

РАЗМЫШЛЕНИЯ ПЕРСОНАЖЕЙ

Ф и л: «Д а ведь это им енно то, что мне нуж но, — в чем подвох?!»
Э лис: « С н ач ал а я подум ала, что м не это не подойдет, потом у что мы уже
п о тр ати л и вр ем я р азр аб о тк и на п р и ло ж ен и е R eact. Н о если это реш ение
ориентировано на сервер, возможно, оно сработает д ля наш его продукта».

5.6.1. Настройка
В поставку Sails вклю чен генератор проектов, поэтому лучш е вы полнить глобаль­
ную установку, чтобы упростить создание новы х проектов. Установите Sails из npm
и введите ком анду s a i l s new, чтобы создать проект:

npm i n s ta ll -g s a ils
s a ils new example-project

К ом анда создает новы й каталог с ф айлом package.json для базовы х зависимостей


Sails. В новы й проект вклю чается собственно Sails, EJS и G ru n t. Вы м ож ете з а ­
пустить сервер ком андой npm s t a r t и ли ввести команду s a i l s l i f t . Когда сервер
заработает, по адресу http://localhost:1337 будет доступ н а встроен н ая стран ица
с вводной инф орм ацией.
126 Глава 5. Фреймворки на стороне сервера

5.6.2. Определение маршрутов


Чтобы добавить марш руты (в Sails они назы ваю тся нестандартными маршрутами),
откройте ф айл config/routes.js и добавьте свойство в экспортируем ы е марш руты .
С войство состоит из ком анды H T T P и частичного U R L -адреса. Н апример, н еко­
торы е действительны е м арш руты Sails м огут вы глядеть так:

m odule.exports.routes = {
'g e t /exam ple': { view: 'example' },
'p o st /ite m s ': 'Item C o n tro ller.create
};

П ервы й м арш рут ожидает получить ф айл с именем views/example.ejs. Второй м арш ­
рут ож идает получить ф айл с именем api/controllers/ItemController и методом c re a te .
Ч тобы сгенерировать этот контроллер с методом c re a te , вы полните команду s a i l s
g e n e ra te c o n t r o l l e r item c r e a te . А налогичны е команды могут использоваться для
бы строго создания R E S T -совм естимы х A PI.

5.6.3. REST API

Sails объединяет модели баз данны х и контроллеры в A PI; чтобы быстро создать
заглуш ки д ля R E S T -совм естимы х A PI, введите ком анду s a i l s g e n e ra te a p i имя-
ресурса. Ч тобы использовать базу данных, сначала необходимо установить адаптер
базы данны х. Д л я д о бавл ен и я M y S Q L необходим о н ай ти и м я пакета W aterlin e
M yS Q L (https://github.com/balderdashy/waterline), а затем добавить его в проект:

npm in s ta l l --save w aterline sails-m ysql

Затем откройте ф айл config/connections.js и введите подробную инф орм ацию о под­
клю чении д ля ваш его сервера M ySQ L. Ф ай л ы м оделей Sails позволяю т вам опре­
делить подклю чение к базе данных, так что вы мож ете использовать разны е модели
с разны м и базам и данны х. Н априм ер, это позволяет реализовать такие ситуации,
как хранение базы данны х пользовательских сеансов в R edis с хранением других,
более д олгосрочны х ресурсов в р ел яц и о н н ой базе дан ны х (напри м ер, M ySQ L ).

У W aterline — библиотеки баз данных для Sails — имеется собственный репозиторий


с документацией ( https://github.com/balderdashy/waterline-docs). Помимо поддержки
разны х баз данны х W aterlin e обладает и другим и полезны м и ф ункциям и: вы м о ­
ж ете определять им ена столбцов и таблиц д ля поддерж ки унаследованны х схем,
а A PI запросов поддерж ивает обещания (prom ises), чтобы запросы бы ли в больш ей
степени похож и на соврем енны й код Jav aS cript.
5.7. DerbyJS 127

РАЗМЫШЛЕНИЯ ПЕРСОНАЖЕЙ

Ф и л: «П ростота создания A P I и поддерж ка м оделям и W aterline сущ еству­


ющ их схем баз данны х? Похоже, Sails идеально подходит д ля нас. У нас есть
клиенты , которы е хотят постепенно перейти с M yS Q L на P ostgreS Q L ; в о з­
можно, W aterlin e позволит нам это сделать. Н екоторы е разработчики и про­
ектировщ ики уж е работали с R uby on Rails, и я думаю, что они моментально
освоят соврем енны й синтаксис ES2015 в Node».
Элис: « Ф р ей м во р к предоставляет возм ож ности, которы е в наш ем проекте
не нужны. Я считаю, что K oa или hapi лучш е подойдет нам».

5.6.4. Сильные стороны

Н ал и ч и е встроенны х средств создан ия проектов и генерировани я A P I означает,


что операции настройки проектов и добавления типичных R EST A PI вы полняю тся
быстро. Такж е Sails хорош о подходит для создания новы х проектов и организации
совм естной работы, потому что проекты Sails имею т одинаковую структуру ф а й ­
л овой системы . С оздатели Sails, М айк М акн ил (M ik e M cN eil) и И рл Н атан (Irl
N ath an ), нап исали книгу Sails.js in Action (M an n in g P ublications, 2017), в которой
показано, что Sails помогает и начинаю щ им програм м истам Node.

5.6.5. Слабые стороны

Sails также обладает рядом недостатков, общих для всех ф реймворков MVC на сторо­
не сервера: A P I м арш рутизации означает, что прилож ение долж но проектироваться
с учетом средств м арш рутизации Sails, и вам может быть трудно адаптировать свою
схему д ля того подхода, которы й используется в W aterline.

5.7. DerbyJS
D erb y JS — п о л н о стеко вы й ф рейм ворк, которы й поддерж ивает син хронизац ию
данны х и серверную визуализацию представлений. В этом он зависит от M ongoD B
и Redis. Уровень син хронизац ии данны х предоставляется ShareJS и поддерж ивает
автом атическое разреш ение кон ф ли ктов. В табл. 5.5 приведена сводка основной
ф ункц иональн ости D erbyJS.
128 Глава 5. Фреймворки на стороне сервера

Таблица 5.5. Основные возможности DerbyJS

Тип библиотеки Полностековый фреймворк

Функциональность Поддержка баз данных с ORM (Racer), изоморфизм

Рекомендуемое приме­ Одностраничные веб-приложения с поддержкой на сто­


нение роне сервера

Архитектура плагинов Плагины DerbyJS

Документация http://derbyjs.com/docs/derby-0.6

Популярность 4000 звезд на GitHub

Лицензия MIT

5.7.1. Настройка
Е сли у вас установлен M ongoD B или Redis, вам придется установить оба пакета
д ля запуска прим еров D erbyJS. Д окум ентация D erbyJS объясянет, как это делается
в M ac OS, L inux и W indow s (http://derbyjs.com/started#environment).

Ч тобы бы стро создать новы й проект D erbyJS, устан овите derb y и derb y -starter.
П акет d e rb y -sta rte r используется д ля базовой ин иц и али зац и и при лож ения Derby:

mkdir example-derby-app
cd example-derby-app
npm i n i t -f
npm in s ta l l --save derby d e rb y -sta rte r derby-debug

П р и л о ж ен и я D erb y разб и ваю тся на несколько м алы х при лож ений ; создайте н о ­
вы й каталог пр и ло ж ен и й с тр ем я ф айлам и: index.js, server, js и index.html. В л и ст и н ­
ге 5.5 при ведено простое п р и ло ж ен и е Derby, которое вы п ол н яет ви зуали зац и ю
ш аблона.

Листинг 5.5. Файл Derby app/index.js


const app = module.exports = re q u ire ('d e rb y ')
.c re a te A p p ('h e llo ', _filenam e);
app.loadViews(_dirname);

a p p .g e t ( '/ ', (page, model) => {


const message = m o d el.at('h ello .m essag e');
m essage.subscribe(err => {
i f (e rr) return n e x t(e rr);
m essag e.createN u ll('');
page.render();
});
});
5.7. DerbyJS 129

С ерверны й ф айл долж ен загрузить только модуль derby-starter, как показано в сле­
дую щ ем фрагменте. С охраните его в ф айле app/server.js:

re q u ire ( 'd e r b y - s ta r te r ') .r u n (_dirname, { port: 8005 });

Ф ай л app/index.html строит поле in p u t и сообщ ение, введенное пользователем:

<Body:>
H oller: <input value="{{hello.message}}">
<h2>{{hello.message}}</h2>

П р и л о ж ен и е д о лж н о за п у с к а ть с я из катал о га example-derby-app ком ан дой node


d e r b y / s e r v e r .j s . П осле того как оно заработает, редактирование ф ай л а app/index.
html приведет к запуску прилож ения; редактирование кода и ш аблонов сопрово­
ж дается автом атическим и обновлениям и в реальном времени.

5.7.2. Определение маршрутов


D erbyJS использует d erb y -ro u ter для м арш рутизации. П оскольку в основе DerbyJS
леж ит Express, A P I м арш рутизации для марш рутов на стороне сервера аналогичен,
и в браузере используется тот ж е модуль м арш рутизации. П ри щ елчке на ссы лке
в при лож ении D erbyJS делается попы тка построить ответ в клиенте.

D erbyJS — полностековы й ф рейм ворк, поэтом у процедура д обавления м арш рутов


отличается от других библиотек, рассм отренны х в этой главе. С ам ы й идиом атиче­
ский способ добавления базового м арш рута основан на добавлении представления.
О ткройте ф айл apps/app/index.js и добавьте м арш рут следую щ им вызовом:

a p p .g e t( 'h e llo ', '/ h e l l o ') ;

О ткройте ф айл apps/app/views/hello.pug и добавьте простой ш аблон Pug:

index:
h2 Hello
p Hello world

Теперь откройте ф айл apps/app/views/index.pug и им портируйте шаблон:

im p o rt:(src= "./h ello ")

Если вы вы полнили ком анду npm s t a r t , проект долж ен постоянно обновляться, по­
этом у при откры тии адреса http://localhost:3000/hello теперь долж но отображ аться
новое представление.

С трока in d ex : содерж ит пространство имен д ля представления. В D erbyJS и м е­


на п р е д с та в л е н и й сн аб ж аю тся п р о с т р а н с т в а м и им ен, о тд ел ен н ы х д в о е т о ч и я ­
ми, поэтому вы ф актически создаете им я h e l l o : index. И нкапсуляция представлений
в пространствах имен нуж на для предотвращ ения конф ликтов в больш их проектах.
130 Глава 5. Фреймворки на стороне сервера

5.7.3. REST API


В проектах D erbyJS R E S T -совм естимы е A P I создаю тся добавлением марш рутов
и обработчиков м арш рутов в Express. Ваш проект D erbyJS содерж ит ф айл server,
js, которы й использует Express для создания сервера. О ткры в ф айл server/routes.js,
вы найдете в нем пример марш рута, определенного с использованием стандартного
A P I м арш рутизац ии Express.

В серверном ф айле марш рутов вы мож ете использовать ap p .u se д ля м онтирования


другого при лож ения Express, поэтом у R E S T A P I м ож но см оделировать в виде со­
верш енно отдельного п р и лож ения Express, которое м онтируется основным п р и ­
лож ением D erbyJS.

5.7.4. Сильные стороны

В D erbyJS имеется A PI м одели базы данны х и A PI синхронизации данных. DerbyJS


мож ет использоваться как д ля построения одностраничны х веб-прилож ений, так
и д ля соврем енны х п р и лож ений реального врем ени. Б л агодаря встроенной п о д ­
держ ке W ebSocket и синхронизац ии вам не придется беспокоиться о том, какую
библиотеку W ebSocket стоит использовать и ли как организовать синхронизацию
данны х м еж ду клиентом и сервером.

РАЗМЫШЛЕНИЯ ПЕРСОНАЖЕЙ

Ф ил: «О дин из наш их клиентов интересуется построением проекта ви зуал и ­


заци и данны х на основании инф орм ации, получаем ой в реальном времени,
и я думаю, что D erbyJS хорошо подойдет для этой цели. Но, похоже, освоение
этой технологии потребует серьезных усилий. Не уверен, что смогу уговорить
наш их разработчиков».
Элис: «К ак разработчик продукта я не представляю , как приспособить п о ­
требности наш его продукта к архитектуре D erbyJS, поэтом у вряд ли этот
вариант подойдет для моего проекта».

5.7.5. Слабые стороны


Лю дей, уж е им ею щ их опы т работы с библиотекам и на стороне сервера и ли к л и ­
ента, довольно трудно убедить использовать D erbyJS. Н апример, разработчики на
стороне клиента, лю бящ ие R eact, обы чно не хотят использовать D erbyJS. Р а зр а ­
ботчики на стороне сервера, которы м нрави тся создавать R E ST A PI и ли проекты
M VC и которы е уверенно чувствую т себя с W ebSocket, такж е не понимают, зачем
им изучать D erbyJS.
5.8. Flatiron.js 131

5.8. Flatiron.js
F latiro n — веб-ф рейм ворк, вклю чаю щ ий в себя ф ункциональность м арш рутизации
U R L, у п р ав л ен и я данны м и, пром еж уточного П О , плагинов и веден и я ж урнала.
В отличие от б ольш ин ства веб-ф рейм ворков, м одули F latiro n проекти ровали сь
в расчете на м аксим альную логическую изоляцию , поэтому вам не при дется и с­
пользовать их все. Вы даж е мож ете использовать их в собственны х проектах: если,
скаж ем , вам п о н р ав и тся м о д у л ь в ед ен и я ж у р н ала, вы м ож ете п од кл ю чи ть его
к проекту Express. В отличие от м ногих ф рейм ворков Node, уровни м арш рутизации
U R L и пром еж уточного П О в F latiro n не написаны с использованием Express или
C onnect, хотя пром еж уточное П О обладает обратной совместимостью с Connect.
В табл. 5.6 приведена сводка основной ф ункц иональн ости Flatiron.

Таблица 5.6. Основные возможности Flatiron

Тип библиотеки Модульный фреймворк MVC

Функциональность Уровень управления базой данных (Resourceful); изо­


лированные модули, пригодные для повторного исполь­
зования

Рекомендуемое приме­ Облегченные приложения MVC, использование модулей


нение Flatiron в других фреймворках

Архитектура плагинов API плагинов Broadway

Документация https://github.com/flatiron

Популярность 1500 звезд на GitHub

Лицензия MIT

5.8.1. Настройка
Установка F latiro n требует глобальной установки средств ком андной строки для
создания новы х проектов Flatiron:

npm in s t a l l -g f la tir o n
f la tir o n create exam ple-flatiron-app

П осле вы полнен ия этих ком анд вы найдете новы й каталог с ф айлом package.json
и необходимыми зависимостями. Выполните команду npm i n s t a l l , чтобы установить
зависим ости, а затем запустите прилож ение ком андой npm s t a r t .

О сновной ф айл app.js вы глядит как типичное прилож ение Express:

const f la tir o n = r e q u ir e ( 'f la tir o n ') ;


const path = r e q u ire ('p a th ');
const app = fla tiro n .a p p ;
132 Глава 5. Фреймворки на стороне сервера

ap p .c o n fig .file ({ f i l e : p a th .jo in (_dirname, 'c o n fig ', 'c o n fig .js o n ') });

a p p .u se (fla tiro n .p lu g in s .h ttp );


a p p .r o u te r .g e t( '/', () => {
th is .r e s .js o n ( { 'h e l l o ': 'w orld' })
});
ap p.start(3000);

О братите внимание: систем а м арш рутизац ии отличается как от Express, так и от


Koa. О тветы возвращ аю тся через t h i s . r e s вместо аргумента ф ун кц и и обратного
вы зова. Рассм отрим м арш руты F latiro n более подробно.

5.8.2. Определение маршрутов


Б и б л и о тека м арш р у ти зац и и F la tiro n н азы вается D irector. И хотя она м ож ет и с ­
п о л ь зо в а т ь с я д л я сер в ер н ы х м ар ш р у то в , она т ак ж е п о д д ер ж и в ает м арш руты
в браузерах, поэтом у она мож ет при м ен яться и д ля одностраничны х прилож ений.
D irecto r вы зы вает м арш руты с ком андам и H T T P в стиле Express:

ro u te r.g e t('/e x a m p le ', example);


ro u te r.p o st('/e x a m p le ', examplePost);

М арш руты могут иметь параметры, и параметры могут определяться регулярны ми


вы раж ениям и:

ro u te r.p a ra m ('id ', / ( [ \ \ w \\ - ] + ) / ) ;


r o u te r .o n ( '/p a g e s /:id ', pageld => {});

Ч тобы сгенерировать ответ, используйте re s.w rite H e a d д ля отправки заголовков


и re s .e n d д ля отправки тела ответа:

r o u t e r . g e t ( '/ ', () => {


this.res.w riteH ead(200, { 'c o n te n t-ty p e ': 'te x t/p la in ' });
th is .re s .e n d ('H e llo , W orld');
});

A PI м арш рутизации также может использоваться в виде класса — с объектом табли­


цы м арш рутизации. Ч тобы использовать его, создайте экзем пляр м арш рутизатора
и используйте метод d is p a tc h при поступлении запросов H T T P:

const h ttp = r e q u ir e ( 'h ttp ') ;


const d ire c to r = r e q u ir e ( 'd ir e c to r ') ;
const router = new d irecto r.h ttp .R o u ter({
'/exam ple': {
ge t: () => {
this.res.w riteH ead(200, { 'Content-Type': 'te x t/p la in ' })
th is .r e s .e n d ( 'h e llo w orld');
}
5.8. Flatiron.js 133

}
});
const server = h ttp .c re a te S e rv e r((re q , res) =>
ro u te r.d isp a tc h (req , res);
});

И сп о л ьзо ван и е A P I м ар ш р у ти зац и и в виде класса такж е означает возм ож ность


подклю чения к потоковом у A PI. Это позволяет организовать быструю и легкую
обработку больш их запросов, что хорош о подходит д ля таки х задач, как разбор
отправленны х данны х с ранним выходом:

const d ire c to r = r e q u ir e ( 'd ir e c to r ') ;


const router = new d ire c to r.h ttp .R o u te r();
r o u t e r . g e t ( '/ ', { stream: tru e }, () => {
th is .r e q .o n ( 'd a ta ', (chunk) => {
console.log(chunk);
});
});

A P I м арш рутизац ии D irecto r мож ет быть полезен д ля создания R E ST API.

5.8.3. REST API


R EST A PI могут создаваться стандартными методами в стиле H T T P -команд Express
или же с использованием средств м арш рутизации D irector. Это позволяет группи­
ровать м арш руты на основании ф рагм ентов и параметров URL:

ro u te r.p a th (/\/u s e rs \/(\w + )/, () => {


th is .g e t( ( id ) => {});
th is .d e le te ( ( id ) => {});
th is .p u t( ( id ) => {});
});

F latiro n также предоставляет высокоуровневую R E S T -обертку Resourceful (https://


github.com/flatiron/resourceful), которая поддерж ивает C ouchD B , M ongoD B, Socket.
IO и проверку данных.

5.8.4. Сильные стороны

Ф р ей м во р к ам достаточно трудно завоевать популярность; им енно по этой п р и ­


ч ине логическая и зо л яц и я ком понентов F la tiro n я в л яет с я одной из его сильны х
сторон. Н еко то р ы е из м оду л ей F la tiro n м огут п р и м ен я ться без и сп о л ьзо ван и я
всего ф рейм ворка. Н априм ер, м одуль веден ия ж урн ала W in sto n (https://github.
com/winstonjs/winston) присутствует во м ногих проектах, которы е не использую т
остальны е ком поненты F latiron. Это означает, что некоторы е компоненты F latiron
получают достаточно серьезный творческий вклад от сообщества с открытым кодом.
134 Глава 5. Фреймворки на стороне сервера

A PI U R L -м арш рутизации D irector изоморфен; это означает, что он может использо­


ваться в реш ениях для разработки как на стороне клиента, так и на стороне сервера.
A P I D ire c to r такж е о тли чается от A P I м ар ш рути зац и и в стиле Express: D irecto r
использует упрощ енны й потоковы й A PI, а объект м арш рутизац ии генерирует со­
бы ти я до и после вы полнен ия марш рутов.

В отличие от больш ин ства веб-ф рейм ворков N ode, F latiro n содерж ит менедж ер
плагинов. П лагины , поддерж иваем ы е сообщ еством, упрощ аю т расш и рение п р о ­
ектов Flatiron.

РАЗМЫШЛЕНИЯ ПЕРСОНАЖЕЙ

Н адин: «М не нрави тся м одульн ая структура F latiron, а м енедж ер плагинов


просто зам ечателен. Я уж е представляю себе некоторы е плагины, которы е
я хотела бы создать».
Элис: «Н е могу сказать, что все м одули F latiro n мне нужны, но мне хотелось
бы опробовать F la tiro n с другой систем ой O R M и библ и отекой ш аблони-
зации».

5.8.5. Слабые стороны


Ф р ей м во р к F latiro n не настолько прост в исп ользован ии с больш им и проектам и
в стиле MVC, как другие ф рейм ворки. Н апример, Sails прощ е настраивается. Если
вы создаете несколько традиционны х веб-прилож ений среднего размера, F latiron
мож ет сработать хорошо. В озм ож ность настройки F latiro n м ож ет стать д о п ол н и ­
тельны м преимущ еством , но проследите за тем, чтобы ф рейм ворк сначала прош ел
оценку наряду с другим и вариантами.

К ч ислу сильны х конкурентов при надлеж ит LoopB ack — последний ф рейм ворк,
описанны й в этой главе.

5.9. LoopBack
Ф р ей м во р к LoopB ack был создан S trongL oop — компанией, предоставляю щ ей н е­
сколько ком м ерческих сервисов, поддерж иваю щ их разработку веб-п рилож ени й
Node. Ф о рм ально это ф рейм ворк A PI, но благодаря некоторы м своим возм ож н о­
стям он хорош о подходит д ля баз данны х и при лож ений MVC. О н даж е вклю чает
в себя веб-интерфейс для анализа и управления R E ST API. Если вы ищете решение,
которое помож ет создавать веб-A PI д ля м обильны х и настольны х клиентов, ф у н к ­
циональность LoopBack подойдет идеально. В табл. 5.7 приведена сводка основной
ф ункц иональн ости LoopBack.
5.9. LoopBack 135

Таблица 5.7. Основные возможности LoopBack

Тип библиотеки Фреймворк API


Функциональность ORM, API пользовательского интерфейса, WebSocket,
клиентский SDK (включая iOS)
Рекомендуемое приме­ API для поддержки разных клиентов (мобильные, на­
нение стольные, веб)
Архитектура плагинов Промежуточное ПО Express
Документация http://loopback.io/doc/
Популярность 6500 звезд на GitHub
Лицензия Двойная лицензия: MIT и соглашение о подписке
StrongLoop

L o o p B a c k р а с п р о с т р а н я е т с я с о т к р ы т ы м ко д ом , и с м о м е н т а п р и о б р е т е н и я
StrongLoop компанией IBM фреймворк получил серьезную коммерческую поддерж ­
ку. В результате он стал уникальны м предложением в сообществе Node. В частности,
в него вклю чены генераторы Yeoman для быстрой настройки оснастки прилож ений.
В следую щ ем разделе показано, как создать новое прилож ение LoopBack.

5.9.1. Настройка
Д л я создания нового проекта L oopB ack необходимо воспользоваться програм мой
ком андной строки StrongL oop (www.npmjs.com/package/strongloop). П осле глобаль­
ной установки пакета strongloop ин струм ентарий ком андной строки запускается
ком ан дой s l c . П акет вклю чает в себя средства у п р ав л ен и я процессам и, но нас
интересует преж де всего генератор проектов LoopBack:

npm in s t a l l -g strongloop
slc loopback

П рограм м а ком андной строки S trongL oop пом огает вам пройти все дей стви я по
созданию нового проекта. Введите им я проекта, а затем вы берите заготовку п р и ­
лож ения a p i-s e rv e r. Когда генератор закончит устанавливать зависимости проекта,
он вы ведет полезны е реком ендации по работе с новым проектом (рис. 5.2).

Чтобы запустить проект, введите команду node ., а чтобы создать модель — команду
slc :lo o p b a c k :m o d e l. Вы будете регулярно использовать ком анду s lc при создании
нового проекта LoopBack.

К о гд а п р о е к т за р а б о т а е т , вы см о ж е т е о б р а т и т ь с я к A P I E x p lo re r по ад р есу
http://0.0.0.0:3000/explorer/. Щ елкните на ссы лке User, чтобы раскры ть узел. Вы
увидите больш ой список доступн ы х м етодов A PI, вкл ю чая стан дартны е R E ST -
совм естимы е марш руты — такие, как PUT /U se rs и DELETE /U s e rs /{ id } . A P I Explorer
показан на рис. 5.3.
136 Глава 5. Фреймворки на стороне сервера

• • 3. alex@AI«xs-ѴлсВоок-Рго; -/Docum*nts/Codt (a h )
sic loopback

I I .-----------------------------.
I— <o)— I I Let's create a LoopBack |
-------- ' I application! |
( _U _ )

|e ' Y

? What's the name of your application? loopback-example


? Enter name of the directory to contain the project: loopback-example
create loopback-example/
info change the working directory to loopback-example

7 What kind of application do you have in mind? api-server (A LoopBack API server with local Use
r auth)
Generating .yo-rc.json

I'm all done. Running for you to install the required dependencies. If this fails, t
ry running the command yourself.

create .editorconfig
create .jshintignore
create .jshintrc
create server/boot/root.js
create server/middleware.json
create server/middleware.production.json

Рис. 5.2. Генератор проектов LoopBack

Рис. 5.3. StrongLoop API Explorer с маршрутами User


5.9. LoopBack 137

5.9.2. Определение маршрутов


В LoopB ack м арш руты м ож но добавлять на уровне Express. Д обавьте новы й ф айл
с именем server/boot/routes.js, после чего добавьте марш рут через экзем пляр Loopback
R outer:

module.exports = (app) => {


const router = app.loopback.Router();
r o u te r .g e t( '/h e llo ', (req, res) => {
re s.se n d ('H e llo , w orld');
});
app.use(router);
};

Теперь при посещ ении http://localhost:3000/hello выдается ответ H ello , world. Впро­
чем, добавление м арш рутов таким способом нетипично д л я проектов LoopBack.
В озм ож но, оно необходим о д л я некоторы х необы чны х кон ечн ы х точек A PI, но,
как правило, м арш руты добавляю тся автом атически при генерировании моделей.

5.9.3. REST API


С ам ы й простой способ со зд ан и я R E S T A P I в проекте L oopB ack основан на и с­
п ол ьзо ван и и генератора м оделей, которы й я в л я е т с я частью ф ун кц и он ал ьн ости
команды slc . Н апример, чтобы добавить новую модель с именем product, выполните
ком анду s l c loopback:m odel:

slc loopback:model product

К ом анда s l c вы полняет всю последовательность действий по созданию модели,


п о зв о л я я вам вы брать, я в л я е т с я ли м одель чисто серверной, а такж е задать н е­
которы е свойства и валидаторы . П осле того как вы добавите модель, взгляните на
соответствую щ ий ф айл JS O N — это долж ен быть ф айл common/models/product.json.
Этот ф айл JS O N я вл яется облегченным способом определения поведения моделей,
вклю чаю щ им в себя все свойства, заданны е на преды дущ ем шаге.

Е сли вы хотите добавить новы е свойства, введите команду s l c lo o p b a c k :p ro p e rty .


Н овы е свойства м огут добавляться в м одели в лю бой момент.

РАЗМЫШЛЕНИЯ ПЕРСОНАЖЕЙ

Ф и л: «Н аш им группам нрави тся идея LoopB ack — в основном из-за способ­


ности бы стро добавлять R E S T -совм естим ы е ресурсы и просм атривать их
в A PI Explorer. Н о мне LoopBack нравится тем, что эта технология достаточно
гибка д ля поддерж ки наш их унаследованны х веб-прилож ений MVC. М ожно
подклю читься к старой базе данны х и перевести эти проекты в Node».
138 Глава 5. Фреймворки на стороне сервера

Элис: «Это единственный фреймворк, который в действительности ориентиро­


ван на iO S и Android, а также полнофункциональных веб-клиентов. У LoopBack
имею тся клиентские библиотеки д ля iO S и A ndroid, а это очень важно для
нас — разработчиков продуктов, зависящ их от м обильны х прилож ений».

5.9.4. Сильные стороны


Д аже из этого краткого введения долж но быть очевидно, что одна из сильных сторон
LoopBack — отсутствие необходимости в написании стереотипного кода. Программа
командной строки генерирует практически все необходимое для облегченных R E ST -
совм естимы х веб-A PI, даж е м оделей баз данны х и проверки данных. В то же время
LoopB ack ничего особенно не диктует в отнош ении кода кли ен тской части. К тому
ж е LoopBack позволяет вам думать о том, какие м одели долж ны быть доступны для
браузера, а какие сущ ествую т только на стороне сервера. Н екоторы е ф рейм ворки
действую т неправильно и отправляю т браузеру все, что можно.

Е сли ваш и м оби льны е п р и ло ж ен и я долж ны взаим одействовать с веб -A PI, п р и ­


см отритесь к клиентском у S D K -пакету L oopB ack (http://loopback.io/doc/en/lb2/
Client-SDKs.html). LoopBack поддерж ивает интеграцию A P I и отправку сообщ ений
как д ля iO S, так и д ля A ndroid.

5.9.5. Слабые стороны


A P I м одели L oopB ack на базе JS O N отличается от больш инства A P I баз данны х
на основе Jav aS crip t. Возможно, вы не сразу поймете, как он соответствует схеме
базы данны х текущ его проекта. И поскольку уровень H T T P базируется на Express,
его возм ож ности частично ограничиваю тся тем, что поддерж ивает Express. И хотя
Express — добротная библиотека H T T P -сервера, сущ ествую т более новы е библи о­
теки д ля N ode с более соврем енны м и A P I. У LoopBack не сущ ествует конкретного
A PI плагинов. Вы можете использовать промежуточное П О Express, но этот вариант
не настолько удобен, как A P I плагинов F latiro n и ли hapi.

Н а этом описание ф рейм ворков в этой главе подходит к концу. П реж де чем п ере­
ходить к следую щ ей главе, давайте сравним ф рейм ворки, чтобы помочь в вы боре
наиболее подходящ его варианта д ля вашего следую щ его проекта.

5.10. Сравнение
Если вы следовали за разм ы ш лени ям и персонаж ей в этой главе, возмож но, вы уже
реш или, какой ф рейм ворк вам стоит использовать. А если еще не реш или — в остав­
ш ейся части этой главы сравниваю тся сильны е стороны всех ф рейм ворков. Если
ж е и сравнение не проясни т ситуацию , рис. 5.4 пом ож ет вам вы брать правильны й
ф рейм ворк, ответив на несколько вопросов.
Р ис. 5.4. Выбор фреймворка Node
140 Глава 5. Фреймворки на стороне сервера

Если взглянуть на описания популярны х фреймворков Node на стороне сервера, все


они каж утся одинаковы ми. Все они предоставляю т облегченные H T T P A PI и и с­
пользую т м одель сервера вместо м одели страницы, используем ой в PH P. О днако
различия в их архитектуре имеют значительные последствия для проектов, созданных
на их основе, поэтому сравнение таких ф реймворков мы начнем на уровне HTTP.

5.10.1. Серверы HTTP и маршруты


Б ольш ин ство ф рейм ворков N ode базируется на C onnect или Express. В этой главе
бы ли представлены три примера, которые не базирую тся на Express и представляют
собственны е реш ения д ля H T T P A PI: Koa, hapi и Flatiron.

Ф р ей м в о р к K oa бы л создан тем ж е автором, что и Express, но предлагает более


свеж ий подход за счет исп ользован ия более соврем енны х средств Jav aS crip t. Если
вам нрави тся Express, но вы хотите использовать синтаксис генераторов ES2015,
возмож но, K oa вам подойдет.

A PI сервера hapi и м арш рутизации обладают вы сокой степенью модульности и вос­


приним аю тся не так, как Express. Если синтаксис Express каж ется вам неуклю жим,
опробуйте hapi. hapi упрощ ает работу с серверами H TTP, так что, если вам нуж но
вы полнять такие операции, как подклю чение к серверам и ли кластеризация, в о з­
можно, вы предпочтете hapi потом кам Express.

С истем а м арш рутизац ии F latiro n обладает обратной совместимостью с Express, но


такж е и дополнительной ф ункциональностью . О на генерирует собы тия и исп оль­
зует таблицу м арш рутизации. В этом отнош ении она отличается от стека в стиле
Express и ли ком п онентов M iddlew are. С истем е м арш р у ти зац и и F la tiro n мож но
передать объектны й литерал. М арш рути зация такж е работает в браузерах, и если
у вас есть серверны е разработчики, которы е пы таю тся освоить соврем енную р а з­
работку на стороне клиента, они могут более уверенно чувствовать себя с Flatiron,
чем пускаться во все тяж ки е с R eact R outer.

5.11. Написание модульного кода


Н е все ф рейм ворки, рассм отренны е здесь, напрям ую поддерж иваю т плагины, но
все они в той или иной степени поддерж иваю т расш ирение. Ф рей м ворки на базе
Express могут использовать пром еж уточное П О C onnect, но hapi и F latiro n имеют
собственны е A P I плагинов. Х орош о определенны й A PI плагинов полезен, потому
что он упрощ ает расш ирение ф рейм ворка д ля его новы х пользователей.

Если вы используете больш ой ф рейм ворк MVC (наприм ер, Sails.js или LoopBack),
A P I плагинов упрощ ает создание нового проекта. LoopBack частично обходит н е­
обходимость в A PI плагинов, предоставляя в распоряж ение программиста мощ ные
средства управлени я проектами. О бративш ись к учетной запи си StrongL oop в npm
5.13. Заключение 141

(www.npmjs.com/~strongloop), вы увидите м нож ество L oopB ack-проектов д ля д о ­


бавлен ия поддерж ки Angular, нескольких баз данны х и т. д.

5.12. Выбор персонажей


П ерсонаж и этой главы получили достаточно информ ации, чтобы принять правиль­
ное реш ение для своего следую щ его проекта:
Ф ил: «В итоге я реш ил остановиться на LoopBack. Это был трудны й выбор; и Sails,
и K rak en им ею т п ревосход ную ф у н к ц и о н ал ь н о сть, ко то р ая п о н р ави л ась моей
команде. Н о нам показалось, что L oopB ack обладает более сильной долгосрочной
поддерж кой и избавляет от м ногих у силий в разработке на стороне сервера».
Н адин: « К ак р азработч и к проектов с откры ты м кодом я вы брала F latiro n . Этот
ф рейм ворк хорош о адаптируется к различны м проектам, над которы м и я работаю.
Н апри м ер, одни проекты о гр ан и ч и ваю тся и сп ользован и ем W in sto n и D irector,
а другие использую т весь стек».
Элис: «Я вы брала hapi для своего следующего проекта. Этот ф реймворк минимален,
поэтому я могу приспособить его к уникальным требованиям проекта. Больш ая часть
кода будет относиться к Node, не полагаясь на какой-то конкретны й ф реймворк.
Я чувствую , что это реш ение хорош о работает с hapi».

5.13. Заключение
О K oa — облегченны й м и н и м ал ьн ы й ф рейм ворк, исп ользую щ ий син такси с ге­
нераторов для пром еж уточного П О . О н хорош о подходит д ля хостинга од н о­
страничны х веб-прилож ений, зависящ их от внеш них веб-API.
О hapi ориен ти руется на серверы H T T P и м арш руты . hapi хорош о подходит для
облегченны х внутренних подсистем, состоящ их из м нож ества м елких сервисов.
О F latiro n — набор несвязны х модулей, которы е могут использоваться либо как
веб-ф рейм ворк MVC, либо как более облегченная библиотека Express. F latiron
обладает совм естимостью с пром еж уточны м П О Connect.
О K raken строится на базе Express с добавлением средств безопасности; м ож ет
и сп о льзо ваться д л я р еал и зац и и MVC, содерж ит п оддерж ку O R M и систем у
ш аблонизации.
О Sails.js — ф рейм ворк MVC, построенны й под влияни ем R ails/D jango; содерж ит
O R M и систему ш аблонов.
О D erb y JS — и зо м о р ф н ы й ф р ей м в о р к, хорош о п од х о д ящ и й д л я п р и л о ж ен и й
реального времени.
О LoopBack снимает необходимость в написании стереотипного кода для быстрого
генерирования R E ST A PI с полноценной поддерж кой баз данных и A PI Explorer.
Connect и Express

И з главы 3 вы узнали, как построить простое при лож ение Express. В этой главе
Express и C o n n ect рассм атриваю тся более подробно. Э ти два поп улярн ы х модуля
N ode использую тся м ногим и веб-разработчикам и. Э та глава показывает, как стро­
ить в еб -п р и л о ж ен и я и R E S T A P I с при м ен ением наи более часто п ри м ен яем ы х
паттернов.

CONNECT И EXPRESS

Концепции, обсуждаемые в следующем разделе, непосредственно применимы


к вы сокоуровневом у ф рейм ворку Express, потому что он расш иряет и допол­
няет C o n n ect вы сокоуровневы м и вспом огательны ми средствами. П осле чте­
ни я этого раздела вы будете твердо понимать, как работаю т промеж уточны е
ком поненты C o n n ect и как объединять их для создания прилож ения. Д ругие
веб -ф р ей м в о р к и N ode работаю т ан алогичны м образом, так что и зучени е
C o n n ect даст вам преим ущ ества при изучении новы х ф рейм ворков.

Д ля начала мы покажем, как создать базовое прилож ение Connect. Далее в этой главе
вы увидите, как построить более слож ное прилож ение Express с использованием
поп улярн ы х приемов Express.

6.1. Connect
В этом разделе вы познакомитесь с Connect. Вы увидите, как промежуточные компо­
ненты могут использоваться для построения просты х веб-прилож ений и насколько
важ ную роль играет порядок пром еж уточны х компонентов. П озднее это помож ет
вам при построении при лож ений Express с более вы сокой степенью модульности.
6.1. Connect 143

6.1.1. Настройка приложения Connect


Express строится на базе C onnect, но знаете ли вы, что полностью ф ун кц и он ал ь­
ное веб-прилож ение м ож но построить и просто на базе C onnect? Ч тобы загрузить
и установить C o n n ect из реестра npm, введите следую щ ую команду:

$ npm in s t a ll connect@3.4.0

М иним альное при лож ение C o n n ect вы глядит так:

const app = re q u ire ('c o n n e c t')();


app.use((req, re s, next) => {
res.en d ('H ello , w o rld !');
});
app.listen(3000);

Это простое при лож ение (находящ ееся в папке ch06-connect-and-express/hello-world


в архиве прим еров) отвечает сообщ ением H e llo , w orld!. Ф у н кц и я, передаваем ая
ap p .u se , представляет собой промежуточный компонент (m iddlew are com ponent),
которы й заверш ает запрос отправкой текста H e llo , w orld! как ответа. П ром еж у­
точн ы е ком п оненты образую т основу д л я всех п ри ло ж ен и й C o n n e c t и Express.
Рассм отрим их более подробно.

6.1.2. Как работают промежуточные


компоненты Connect

В C o n n ect промежуточный компонент представляет собой ф ункц ию Jav aS crip t,


к отор ая по соглаш ению получает три аргум ента: объект запроса, объект ответа
и аргумент, котором у обычно при сваивается им я next, — ф ун кц и я обратного вы ­
зова, указы ваю щ ая, что ком п онент завер ш и л свою работу и вы полнен ие м ож ет
быть продолж ено следую щ им пром еж уточны м компонентом.

Д о того как в аш и п р о м еж у то ч н ы е к о м п о н ен ты н ач н ут в ы п о л н я т ь ся , C o n n e c t
исп о льзу ет диспетчера, ко то р ы й получает запросы и передает их первом у п р о ­
м еж у то ч н о м у ком поненту, д о б ав л ен н о м у в п ри л о ж ен и и . Н а рис. 6.1 показан о
ти п и чн о е п р и ло ж ен и е C o n n ect, состоящ ее из д и сп етчера и набора п р о м еж у то ч ­
ны х ком п онентов, вклю чаю щ его в себя ком п онент веден и я ж урн ала, парсер тела
запроса, сервер стати ч ески х ф ай л о в и специ альное пром еж уточное програм м ное
обеспечение.

К ак видите, архитектура A P I пром еж уточного П О означает, что более слож ное по­
ведение мож ет строиться из м еньш их структурны х блоков. В следую щ ем разделе
вы увидите, как это делается посредством объединения компонентов.
144 Глава 6. Connect и Express

GET /img/logo.png POST /user/save


О Диспетчер получает запрос
и передает его первому
а Диспетчер о промежуточному компоненту

nextQ О Запрос регистрируется


в журнале и передается
следующему компоненту
logger вызовом next()

nextQ О Тело запроса разбирается


(если оно существует), после
bodyParser чего передается следующему
О компоненту вызовом next()
nextQ О Если запрос обращен
к статическому файлу, ответ
О res.endQ static отправляется с этим файлом,
и next() не вызывается;
nextQ в противном случае запрос
перемещается к следующему
промежуточному компоненту
customMiddleware ;.end() 0
ѳ Запрос обрабатывается
специальным промежуточным
компонентом, и ответ
завершается

Рис. 6.1. Жизненный цикл двух запросов HTTP в сервере HTTP

6.1.3. Объединение промежуточных компонентов


C o n n e c t п р ед о став л я ет м етод с им енем u se д л я о б ъ ед и н ен и я п ром еж уточ н ы х
ком понентов. О пределим две ф у н кц и и пром еж уточны х ком понентов и добавим
их в прилож ение: первая — уж е у п ом и навш аяся ранее ф ун кц и я «H ello , World»,
а д ругая — ф у н кц и я lo g g er.

Листинг 6.1. Использование нескольких промежуточных компонентов Connect


const connect = re q u ire ('c o n n e c t');
function logger(req, re s, next) { ■<---- Выводит метод HTTP и URLзапроса, после чего вызывает next().
console.log('% s %s', req.method, re q .u rl);
n ext();
}
function h ello (req , res) { < -------- Завершает ответ на запрос HTTPтекстом «hello world».
res.setH eader('C ontent-T ype', 't e x t /p la i n ') ;
re s.e n d ('h e llo w orld');
}
connect()
.use(logger)
.use(hello)
.listen (3 0 0 0 );

П ром еж уточны е ком поненты имею т две сигнатуры: с next и без. Это объясняется
тем, что ком понент заверш ает ответ H T T P и возвращ ать управление диспетчеру
ему не нужно.
6.1. Connect 145

Ф у н к ц и я u s e () возвращ ает экзем пляр при лож ения C on n ect д ля поддерж ки сц е­


п лени я методов, как было показано выше. О братите внимание: сцепление вызовов
.u s e ( ) при этом не требуется:

const app = connect();


app.use(logger);
app.use(hello);
app.listen(3000);

Итак, у вас заработало простое прилож ение «Hello World»; теперь можно посмотреть,
почему важен порядок вы зовов .u s e ( ) пром еж уточны х ком понентов и как страте­
гически использовать этот порядок для внесения изм енений в работу прилож ения.

6.1.4. Упорядочение компонентов


П орядок пром еж уточны х ком понентов в ваш ем при лож ении мож ет кардинально
влиять на его поведение. О пустив n e x t( ) , вы остановите вы полнение прилож ения,
а объединение ком понентов позволяет реализовать такие ф ункц и он ал ьн ы е в о з­
м ож ности, как аутентиф икация.

Ч то произойдет, если пром еж уточны е ком п оненты не вы зовут n e x t? В ернем ся


к предыдущ ему примеру «H ello W orld», в котором первым используется компонент
logger, а за ним следует компонент h e llo . В этом примере C onnect направляет вывод
в s td o u t, а затем реагирует на запрос H TTP. Н о что произойдет, если компоненты
будут следовать в другом порядке?

Листинг 6.2. Ошибка: компонент hello предшествует компоненту logger


Всегда вызывает next(), поэтому
const connect = re q u ire ('c o n n e c t'); следующий компонент будет вызван.
function logger(req, re s, next) {
console.log('% s %s', req.method, re q .u rl);
next(); Не вызывает next(), потому что
} компонент отвечает на запрос.
function h ello (req , res) {
res.setH eader('C ontent-T ype', 'te x t / p l a in ') ;
re s.e n d ('h e llo w orld');
}
const app = connect()
.use(h ello ) <-
Компонент logger никогда не будет
.use(logger)
вызван, потому что hello не вызывает
.listen (3 0 0 0 );
next().

В этом прим ере пром еж уточны й ком понент h e llo вы зы вается первы м и реагирует
на запрос H T T P, как и ож идалось. О днако ком понент lo g g e r вы зван никогда не
будет, потом у что h e llo никогда не вы зы вает n e x t( ), поэтому управлени е никогда
не возвращ ается диспетчеру для вы зова следую щ его промеж уточного компонента.
М ораль: если ком понент не вы зы вает n e x t( ) , то остальны е ком поненты в цепочке
вы зы ваться не будут.
146 Глава 6. Connect и Express

Рис. 6.2. Порядок следования промежуточных компонентов важен

Р и су н о к 6.2 показы вает, как в этом при м ере обходится ком понент lo g g e r и как
исправить эту проблему.

К ак видите, разм ещ ать h e l l o перед lo g g e r достаточно бессмысленно, но при п р а­


вильном исп ользован ии упорядочение м ож ет принести пользу.

6.1.5. Создание настраиваемых промежуточных


компонентов

Вы усвоили некоторы е основы исп ользован ия пром еж уточны х компонентов; пора


рассмотреть тему более подробно, чтобы понять, как создавать более универсальные
и общ ие пром еж уточны е компоненты .

О б ы чн о п ри п о стр о ен и и п р о м еж у то ч н ы х ко м п о н ен то в и с п о л ь зу ет с я простое
соглаш ение, которое о ткры вает перед разработч и ком возм ож н ости д л я к о н ф и ­
гурации: и сп о льзо ван и е ф ункц ии, ко торая возвращ ает другую ф ун кц и ю (за м ы ­
кан и е). Б а зо в а я структура настраиваем ы х пром еж уточны х ком понентов такого
рода вы гл яд и т так:
6.1. Connect 147

function setup(options) {
/ / Логика инициализации

return fu nction(req, re s, next) { Здесь выполняется дополнительная


/ / Логика промежуточного компонента инициализация промежуточного компонента.
<-
Значение options остается доступным, хотя
} внешняя функция вернула управление.
}

П рим ер исп ользован ия таких компонентов:

app.use(setup({ some: 'o p tio n s' }));

О братите внимание: ф у н кц и я se tu p вы зы вается в строке ap p .u se , тогда как в п р е­


ды дущ их прим ерах мы просто передавали ссы лку на ф ункцию .

В этом разделе данны й прием будет применен для построения трех настраиваемых,
пригодны х д ля повторного исп ользован ия пром еж уточны х компонентов:

О ком понента lo g g e r с настраиваем ы м ф орм атом вывода;

О ком понента м арш рутизац ии д ля вы зова ф ун кц и й в зависи м ости от зап р аш и ­


ваемого URL;

О ко м п о н ен та п ер езап и си U R L , п р еоб разую щ его ал ьтер н ати вн ы е ч асти U R L


(slu g s) в идентиф икаторы .

Н ачнем с д оработки ком п онента lo g g e r, чтобы расш и рить возм ож н ости его н а­
стройки . П ро м еж у то чн ы й ком п онент lo g g e r , созд ан н ы й ранее в этой главе, не
поддерж ивал настройку. О н был ж естко запрограм м ирован д ля вы вода значений
req.m ethod и r e q . u r l при вызове. Н о что, если в какой-то м ом ент вы захотите и з ­
м енить состав вы водим ой инф орм ации?

Н а практике использование настраиваемого промеж уточного компонента почти не


отличается от использования любого другого промежуточного компонента, который
создавался до настоящ его момента, если не считать того, что ком поненту можно
передать дополнительны е аргументы для изм енения его поведения. И спользование
н астраиваем ого ком п онента в при ло ж ен и и обы чно вы гл яд и т прим ерно так, как
в следую щ ем прим ере (где lo g g e r получает строку с описанием ф орм ата вывода):

const app = connect()


.use(logger(':m ethod : u r l ') )
.u se (h e llo );

Д л я р еализаци и настраиваем ого ком понента lo g g e r сначала необходимо опреде­


лить ф ункцию se tu p , которая получает один строковы й аргум ент (в этом примере
ему присваивается им я fo rm at). П ри вы зове se tu p возвращ ается ф ункц ия, которая
будет использоваться пром еж уточны м ком понентом C onnect. В озвращ аемы й ком ­
понент сохраняет доступ к перем енной fo rm at даж е после того, как ф ун кц и я setu p
148 Глава 6. Connect и Express

вернет управление, потом у что он о пределяется в том ж е зам ы кан и и Jav aS crip t.
Затем lo g g e r зам еняет лексем ы в ф орм атной строке соответствую щ им и свойства­
ми запроса из объекта req, вы водит инф орм ацию в s td o u t и вы зы вает n e x t() , как
показано в листинге 6.3.

Листинг 6.3. Настраиваемый промежуточный компонент logger для Connect


Функция setup может вызываться
многократно с разными Компонент logger использует
конфигурациями. регулярное выражение для поиска
свойств запроса. Компонент logger,
function setup(form at) { -<—
const regexp = /:(\w + )/g ; который будет
return function createLogger(req, re s, next) { использоваться Connect.
const s tr = form at.replace(regexp, (match, property) => Использует регулярное
return req[property]; выражение для
}); Выводит регистрационную
форматирования выводимой
c o n so le .lo g (str); .<- запись о запросе на консоль.
информации.
next();
Передает управление
} следующему компоненту.
}
module.exports = setup;
Напрямуюэкспортирует функцию
setup компонента logger.

Так как ком понент lo g g e r создавался как настраиваем ы й, вы мож ете м ногократно
вы зы вать .u s e ( ) для lo g g e r в одном при лож ении с разны м и кон ф и гурац иям и или
ж е использовать этот код в других прилож ениях, которы е вы разработаете в бу­
дущем. П ростая кон цепц ия настраиваем ы х пром еж уточны х ком понентов хорош о
известна в сообщ естве C o n n ect и при м ен яется ко всем базовы м промеж уточны м
ком понентам C o n n ect д ля последовательности.

Ч т о б ы и с п о л ь з о в а т ь к о м п о н ен т l o g g e r из л и с т и н г а 6.3, п ер ед ай те ем у с т р о ­
к у с в к л ю ч е н и е м н е к о то р ы х св о й ств о б ъ е к та r e q u e s t . Н а п р и м ер , с в ы зо во м
.u s e ( s e tu p ( ':m e th o d : u r l ') ) д ля каж дого запроса будет вы водиться метод H T T P
(G ET, P O S T и т. д.) и U R L -адрес.

П реж де чем переходить к Express, посмотрим, как в C o n n ect поддерж ивается об­
работка ошибок.

6.1.6. Использование промежуточных компонентов


для обработки ошибок

Во всех при лож ениях возникаю т ош ибки (как на системном уровне, так и на п оль­
зовательском ), и вы сильно упростите себе жизнь, если будете готовы к ош ибочным
ситуациям — даж е к непредвиденны м . C o n n ect реализует разновидность пром е­
ж уточны х ком понентов с обработкой ош ибок, которая следует тем ж е правилам,
что и обычная, но наряду с объектами запроса и ответа получает объект ош ибки.
6.1. Connect 149

О бработка ош ибок в C onnect намеренно сделана минимальной, чтобы разработчик


мог сам задать способ обработки ошибок. Н апример, через промежуточное П О могут
проходить только систем ны е ош ибки и ош ибки п ри лож ения (наприм ер, перем ен­
ная fo o не определена), и ли ош ибки пользователя (пароль недействителен), или
их н екоторая ком бинация. C o n n ect позволяет вы брать, что лучш е подходит для
ваш его прилож ения.

В этом разделе мы используем обе разновидности, и вы узнаете, как работаю т про­


меж уточны е компоненты обработки ошибок. Также будут представлены некоторые
полезны е паттерны , которы е могут при м ен яться в следую щ их областях:

О использование обработчика ош ибок C o n n ect по умолчанию ;

О сам остоятельная обработка ош ибок прилож ения;

О использование нескольких ком понентов обработки ош ибок.

А теперь посмотрим, как C o n n ect обрабаты вает ош ибки без какой-либо настройки.

Обработчик ошибок Connect по умолчанию


Р ассм о тр и м сл еду ю щ и й п р о м еж у то ч н ы й ком п онент, к о то р ы й вы д ает ош ибку
R e fe re n c eE rro r, потому что ф у н кц и я f o o ( ) не определена прилож ением:

const connect = re q u ire ('c o n n e c t')


connect()
.u se((req , res) => {
fo o ();
res.setH eader('C ontent-T ype', 't e x t/ p la i n ') ;
re s .e n d ('h e llo w orld');
})
.listen(3000)

По умолчанию C onnect отвечает кодом статуса 500, телом ответа с текстом «Internal
Server E rro r» и дополнительной и н ф орм аци ей о сам ой ош ибке. Это неплохо, но
в лю бом реальном при лож ении вы, скорее всего, предпочтете реализовать более
специализированную обработку ош ибок — например, отправлять их демону веде­
н и я ж урнала.

Самостоятельная обработка ошибок приложения

C o n n e c t такж е предоставляет средства д л я сам остоятельной обработки ош ибок


с использованием пром еж уточны х компонентов. Н апример, в ходе разработки вы
мож ете отвечать кли ен ту J S O N -представлением ош ибки д ля быстрого и простого
получения инф орм ации, а в итоговой версии при лож ение будет отвечать простым
сообщ ением «Server error», чтобы не раскры вать конф иденциальную внутренню ю
ин ф орм ацию (трасси ровку стека, им ена ф айлов, ном ера строк) потенциальном у
атакующему.
150 Глава 6. Connect и Express

Ф у н к ц и я обработки ош ибок пром еж уточны х компонентов долж на определяться


с ч еты р ьм я аргум ентам и, e r r , req, r e s и n e x t (л и с т и н г 6.4), тогда как обы чны е
п р о м еж уточны е ком п оненты получаю т аргум енты req, r e s и n e x t. С ледую щ ий
листинг демонстрирует прим ер промежуточного компонента с обработкой ошибок.
З а полны м прим ером сервера обращ айтесь к архиву исходного кода в папке ch06-
connect-and-express/listing6_4.

Листинг 6.4. Промежуточный компонент обработки ошибок в Connect


const env = process.env.NODE_ENV | | 'development';
Промежуточный компонент обработки
ошибок использует четыре аргумента.
function errorH andler(err, req, re s, next) {
res.statusC ode = 500;
switch (env) { <­ Поведение промежуточного
case 'development': компонента errorHandler зависит
c o n s o le .e rro r('E rro r:'); от значения NODE ENV.
c o n so le .e rro r(e rr);
res.setH eader('C ontent-T ype', 'a p p lic a tio n /js o n ');
res.en d (JS O N .strin g ify (err));
break;
defau lt:
res.en d ('S erv er e r r o r ') ;
}
}
module.exports = errorHandler;

Использование Node_Env для определения режима работы


О дин из распространенны х приемов C o n n ect — использование перем енной о кр у ­
ж ен и я NODE_ENV (process.env.NODE_ENV) д ля переклю чения поведения меж ду в а­
риан там и условий работы сервера — например, реж имом разработки и реж имом
реальной эксплуатации.

О б н ар у ж и в ош ибку, C o n n e c t п ер ек л ю ч ается на вы зов только пром еж уточ н ы х


компонентов, предназначенны х д ля обработки ош ибок (рис. 6.3).

П редставьте, что ваш е п р и лож ение предоставляет возм ож ность вы п ол н ен и я ау ­


тен ти ф и к ац и и д ля работы в адм и н и стр ати вн ой области блога. Е сли ком понент
м арш рутизац ии д ля пользовательских м арш рутов вы зы вает ошибку, компоненты
b lo g и admin будут пропущ ены , потом у что они не я в л я ю т ся ком п онентам и о б ­
работки ош ибок — они определяю т только три аргумента. Затем C o n n ect видит,
что e rro rH a n d le r получает аргум ент ош ибки, и активи зирует его. П ромеж уточны е
ком поненты вы глядят прим ерно так:

connect()
.u s e (ro u te r ( r e q u ir e ( './r o u te s /u s e r') ) )
.u s e ( r o u te r(re q u ire ('./ro u te s /b lo g '))) / / Пропускается
.u s e (ro u te r(re q u ire ('./ro u te s /a d m in '))) / / Пропускается
.use(errorH andler);
6.2. Express 151

О Запрос HTTP к URL-адресу,


вызывающий ошибку
на сервере

Диспетчер передает
запрос по стеку
промежуточных
компонентов,
как обычно

і ОйІ В компоненте
маршрутизации
произошла ошибка!

О Компонент hello
пропускается,
потому что он не был
определен как
промежуточный
компонент
для обработки ошибок

ѳ Компонент
errorHandler
получает объект
ошибки, созданный
компонентом logger,
и может ответить
на запрос
в контексте ошибки

Рис. 6.3. Жизненный цикл запроса HTTP, порождающего ошибку сервера Connect

Ф ун кци ональность ускоренной обработки в зависи м ости от хода вы полнен ия —


одна из ф ундам ентальны х концепций, прим еняем ы х для организации прилож ений
Express.

Теперь, когда вы и зу ч и л и основы C o n n ect, м ож но п ерейти к более подробном у


изучению Express.

6.2. Express
Express — поп улярн ы й веб-ф рейм ворк, которы й некогда строился на базе C onnect,
но и сейчас остается совм ести м ы м с п ром еж уточ н ы м и ком п он ен там и C onnect.
Х отя Express вклю чает в себя базовую ф ункциональность (такую , как предостав­
ление статических ф айлов, м ар ш р у ти зац и я U R L и кон ф и гурац и я при лож ения),
этот ф рейм ворк все равно остается м иним альны м . О н предоставляет достаточную
структуру д ля создания блоков, подходящ их д ля повторного использования, без
изли ш н их ограничений д ля ваш ей практики разработки.
152 Глава 6. Connect и Express

В н еск о льк и х б ли ж ай ш и х р азд ел ах мы р еал и зуем п ри лож ен и е E xpress при п о ­


мощ и генератора при лож ений Express. П роцесс будет описан более подробно, чем
кратки й обзор в главе 3, поэтом у в конце главы вы будете достаточно хорош о п о ­
нимать Express для самостоятельного построения веб-прилож ений Express и REST-
совм естимы х A PI. По ходу главы к заготовке прилож ения будет добавляться новая
ф ункциональность, а к ее концу будет построено полноценное прилож ение.

6.2.1. Генерирование заготовки приложения


Express не требует от разработчика соблю дения определенной структуры п р и л о ­
ж ения. Вы мож ете разм естить м арш руты в лю бом количестве ф айлов, разм естить
откры ты е активы в лю бом каталоге на свое усм отрение и т. д. М ин им альное п р и ­
лож ение Express получается совсем коротким — как в листинге 6.5, реализую щ ем
полностью работоспособны й сервер HTTP.

Листинг 6.5. Минимальное приложение Express


const express = re q u ire ('e x p re s s ');
const app = express();
a p p .g e t ( '/ ', (req, res) => { -<-------- Отвечает на любой веб-запрос к/.
re s .s e n d ('H e llo '); <.-------- Отправляет строку «Hello» как текст ответа.
});
app.listen (3 0 0 0 ); ■<-------- Прослушивает порт 3000.

П рограм м а ком ан дной строки e x p r e s s ( l ) , вход ящ ая в пакет e x p r e s s - g e n e r a to r


(www.npmjs.com/package/express-generator), создает заготовку при лож ения за вас.
С генерированное при лож ение — хорош ая отправная точка д ля начинаю щ их р а з­
работчиков Express, так как в сгенерированное прилож ение вклю чаю тся шаблоны,
откры ты е активы, ко н ф и гурац ия и многое другое.

С тан д ар тн ая заго то в к а п р и л о ж ен и я , ген ер и р у ем ая e x p r e s s ( l ) , состои т из н е ­


скольких каталогов и ф айлов (рис. 6.4). Этот набор был разработан для того, чтобы
разработчики м огли освоиться с Express за считанны е минуты, но структура п р и ­
л ож ени я полностью определяется вами и ваш ей командой.

В примере этой главы используются ш аблоны Em bedded JavaS cript (E JS), которые по
своей структуре напоминают разметку H TM L. Язы к EJS близок к PHP, JS P (для Java)
и ERB (д ля Ruby): серверны й код Jav aS crip t встраивается в документ H T M L и вы ­
полняется перед отправкой клиенту. EJS более подробно рассматривается в главе 7.

В этом разделе будут рассм отрены следую щ ие операции:

О глобальная установка Express с npm;

О генерирование прилож ения;

О анализ п р и лож ения и установка зависимостей.


6.2. Express 153

ф О alex@locohost: ~/Documents/Code/nodeinaction/ch07-connect-and-express/li.

— app.js
— bin
I--- WWW
— package.json
— public
images
Ё javascripts
stylesheets
1— style, css
— routes
I— index, js
'— users, js
— views
error.jade
Ё index.jade
layout.jade

7 directories, 9 files

Рис. 6.4. Структура стандартной заготовки приложения


с использованием шаблонов EJS

Установка Express
В ы полните глобальную установку e x p r e s s - g e n e ra to r с npm:

$ npm in s t a ll -g express-generator

Д л я просм отра всех доступны х парам етров используется ф лаг - -h e lp (рис. 6.5).

alex@locohost: ~/Documents/Code/nodeinaction/ch07-connect-and-express/li...
> express --help

Usage: express [options] [dir]

Options:

-h, --help output usage information


-V, --version output the version number
-e, --ejs add ejs engine support (defaults to jade)
--hbs add handlebars engine support
-H, --hogan add hogan.js engine support
-c, --css <engine> aao styiesneet <engine> suppi
|compass|sass) (defaults to plain css)
--git add .gitignore
-f, --force force on non-empty directory

Рис. 6.5. Справочная информация Express


154 Глава 6. Connect и Express

Н екоторы е из этих парам етров генерирую т небольш ие части п ри лож ения за вас.
Н априм ер, вы мож ете приказать сгенерировать ф и ктивн ы й ш аблонны й ф айл для
вы бранного я д р а ш аблонизации. А налогичны м образом, если задать п реп роцес­
сор CSS при пом ощ и парам етра - -c s s , д ля него будет сгенерирован ф и кти вн ы й
ш аблонны й файл.

П осле того как исполняем ы е ф айлы будут установлены , посмотрим, как сгенери­
ровать прилож ение, над которы м мы будем работать в этой главе.

Генерирование приложения

В наш ем при лож ении будет использован ф лаг -е (и л и - - e j s ) , вы бираю щ ий ядро


ш аб л о н и зац и и EJS. В ы полни те ком анду e x p r e s s -е shoutbox. Ч тобы в о сп р о и з­
вести прим еры кода из наш его репози тори я G itH u b , введите команду e x p re ss -е
lis tin g 6 _ 6 .

П ри лож ен и е будет создано в каталоге shoutbox. О но содерж ит ф айл package.json


для описания проекта и зависим остей, сам ф айл прилож ения, каталоги с общ едо­
ступны м и ф ай лам и и каталог для обработчиков марш рутов.

Анализ приложения

Д авай те п об ли ж е п р и см о тр и м ся к тому, что ж е бы ло сгенерировано. О ткрой те


ф а й л package.json в р ед ак то р е, ч то б ы п р о см о т р е ть за в и с и м о с т и п р и л о ж е н и я
(рис. 6.6). Express не м ож ет угадать, какая версия зависим остей вам понадобится,
поэтом у реком ендуется указать о сновн ую /дополнительную версию и версию и с ­
п р авл ен и я м одуля, чтобы не создать случайны е ош ибки. Н априм ер, вы раж ение
" e x p r e s s " :" ~ 4 .1 3 .1 " яв н о задает нуж ны е версии и обеспечивает исп ользован ие
идентичного кода во всех вариантах установки.

1 {
2 "name": "listing6_6",
3 "version": "0.0.0",
4 "privet*": true,
5 "scripts": {
6 "start": "node ./bin/www"
>,
8 "dependencies": {
9 "body-parser": "~1.13.2",
10 "cookie-parser": "**1.3.5",
11 "debug": "-2.2.0",
12 "express": "*4.13.1",
13 "jade": "*1.11.0",
14 "morgan": "*1.6.1",
15 "serve-favicon": "*2.3.0"
is >
17 }

Рис. 6.6. Содержимое сгенерированного файла package.json


6.2. Express 155

Рассмотрим файл приложения, сгенерированный e x p re s s (l) (листинг 6.6). Пока этот


ф айл останется в исходном виде. П ром еж уточны е компоненты уж е долж ны быть
знакомы вам по разделам этой главы, посвящ енным Connect, но вам стоит взглянуть
на то, как задается конф игурация промежуточных компонентов по умолчанию.

Листинг 6.6. Сгенерированная заготовка приложения Express


var express = re q u ire ('e x p re s s ');
var path = re q u ir e ( 'p a th ') ; I Предоставляет значок приложения
var favicon = re q u ire ('s e rv e -fa v ic o n '); -<---- 1 по умолчанию.
var logger = require('m organ');
var cookieParser = re q u ire ('c o o k ie -p a rs e r');
var bodyParser = re q u ire ('b o d y -p a rse r');
var routes = re q u ir e ( './r o u te s /in d e x ') ;
var users = r e q u ir e ( './r o u te s /u s e r s ') ;
var app = express();

a p p .s e t('v ie w s ', p a th .jo in (_dirname, 'v ie w s'));


app.set('v iew en g in e', 'e j s ') ;
Выводит журналы в формате,
удобномдля разработки.
ap p .u se (lo g g e r('d e v '));
app.use(bodyParser.json()); < -------- Разбирает тела запросов.
app.use(bodyParser.urlencoded({ extended: fa ls e }));
app.use(cookieParser()); Предоставляет статические
a p p .u se (e x p re ss.sta tic (p a th .jo in (_dirname, 'p u b lic ') ) ) ; файлы из каталога ./public.

a p p .u s e ( '/', ro u tes); -<-------- Залает маршруты приложения.


a p p .u s e ('/u s e rs ', users);

app.use(function(req, re s, next) {
le t e rr = new E rror('N ot Found');
e r r .s ta tu s = 404;
n e x t(e rr);
}); Выводит страницы ошибок
в формате HTMLв режиме
i f (a p p .g e t('e n v ') === 'development') { разработки.
app .u se(fu n ctio n (err, req, re s, next) {
r e s .s ta tu s ( e r r .s ta tu s || 500);
r e s .r e n d e r ( 'e r r o r ', {
message: err.m essage,
e rro r: e rr
});
});
}
ap p .u se(fu n ctio n (err, req, re s, next) {
r e s .s ta tu s ( e r r .s ta tu s || 500);
r e s .r e n d e r ( 'e r r o r ', {
message: err.message,
e rro r: {}
});
});

module.exports = app;
156 Глава 6. Connect и Express

Ф ай л ы package.json и app.js существуют, но прилож ение еще не работает, потому что


зависим ости еще не установлены . К аж ды й раз, когда вы генерируете ф айл package.
json из e x p r e s s ( l) , для него необходимо установить зависимости. Выполните ком ан­
ду npm i n s t a l l , а затем запустите при лож ение ком андой npm s t a r t .

Проверьте работоспособность прилож ения, открыв адрес http://localhost:3000 в бра­


узере. В неш ний вид при лож ения по ум олчанию показан на рис. 6.7.

E x p r e s s - M o z illa F ir e fo x

W Express 1+

< !"» localhost 3000 4 v e |

E xpress
Welcome to Express

Рис. 6.7. Приложение Express по умолчанию

О знаком ивш ись со сгенерированной заготовкой, м ож но переходить к построению


реального прилож ения Express. П рилож ение будет вы полнять ф ункции простейш е­
го чата, в котором пользователи смогут публиковать сообщ ения. П ри построении
при лож ений такого рода опы тны е E xpress-разработчики обычно начинаю т с п л а ­
ни рования A P I и, соответственно, необходим ы х м арш рутов и ресурсов.

Планирование приложения Shoutbox


Требования к прилож ению :
1. Р еги страц и я учетны х записей пользователей, процедуры входа и выхода.
2. В озм ож ность публикац ии сообщ ений (записей).
3. С траничны й просм отр записей.
4. П ростой R E S T A P I с поддерж кой аутентиф икации.

Д л я этого необходим о ор ган и зо вать х р анени е дан ны х и об раб отку д ан н ы х при


аутентиф икации. Также следует предусмотреть проверку данных, введенных поль­
зователем . Н еобходим ы е м арш руты вы глядят так:

О м арш руты API;

О GET / a p i / e n t r i e s : получение списка записей;

О GET / a p i / e n t r i e s / p a g e : получение одной страницы записей;

О POST / a p i / e n t r y : создание новой записи;

О м арш руты пользовательского интерф ейса;


6.2. Express 157

О GET /p o s t: ф орм а для новой записи;

О POST /p o s t: пуб ликац ия новой записи;

О GET / r e g i s t e r : вы вод ф орм ы регистрации;

О POST / r e g i s t e r : создание новой учетной записи;

О GET /lo g in : вы вод ф орм ы входа;

О POST /lo g in : вход в прилож ение;

О GET /lo g o u t: вы ход из прилож ения.

Такая структура типи чн а д ля б ольш ин ства веб-прилож ений. Возмож но, вы см о­


ж ете использовать прим ер этой главы как образец д ля построения ваш их будущ их
прилож ений.

В листинге 6.6 м ож но зам етить несколько вы зовов a p p .s e t:

a p p .s e t('v ie w s ', p a th .jo in (_dirname, 'v ie w s'));


app.set('v iew en g in e', 'e j s ') ;

Так в ы п о л н я ется н астр о й к а ко н ф и гу р ац и и п р и л о ж ен и я Express. В следую щ ем


разделе ко н ф и гурац ия Express рассм атривается более подробно.

6.2.2. Настройка конфигурации Express и приложения


Требования вашего при лож ения зависят от условий, в которы х оно вы полняется.
Н апример, во врем я разработки мож ет вы водиться более подробная ж урнальная
инф орм ация, а в условиях реальной эксплуатации — сокращ енны й вариант ж у р ­
нала. Кроме настройки ф ункциональности д ля конкретного окруж ения вы можете
определить настройки уровня прилож ения, чтобы передать Express информацию об
используемом ядре шаблонов и о том, где хранятся шаблоны. Express также позволяет
определять нестандартные параметры конф игурации в форме пар «ключ-значение».

ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ

Д л я настройки перем енны х окруж ения в системах U N IX используется сл е­


дую щ ая команда:
$ NODE_ENV=production node app
В системе W indow s код вы глядит так:
$ se t NODE_ENV=production
$ node app
Д л я работы с перем енны м и окруж ения в при лож ении используется объект
process.env.
158 Глава 6. Connect и Express

В Express реализована м и н и м ал ьн ая система работы с окруж ением . О на состоит


из нескольких методов, использую щ их значение перем енной окруж ения NODE_ENV:

О a p p .s e t( ) ;

О a p p .g e t( ) ;
О a p p .e n a b le ();

О a p p .d is a b le ( ) ;

О a p p .e n a b le d ();

О a p p .d is a b le d ( ) .

В этом разделе будет показано, как использовать систему кон ф и гурац ии для н а ­
стройки поведения Express и как пользоваться этой системой для ваш их собственных
целей в ходе разработки.

Р азберем ся, что ж е поним ается под конфигурацией на базе окружения. Х отя п ере­
м енная окруж ения NODE_ENV появилась в Express, многие другие ф рейм ворки N ode
п р и н ял и ее как м еханизм оповещ ения при лож ений N ode о том, в каких условиях
они работаю т (по ум олчанию реж им разработки).

М ето д a p p . c o n f i g u r e ( ) п о л у ч а ет н е о б я за т е л ь н ы е стр о к и , п р е д с т а в л я ю щ и е
перем енн ы е о круж ения, и ф ункцию . Е сли п ерем енн ая окруж ен и я соответствует
переданной строке, нем едленно ак ти ви зи р у ется ф у н кц и я обратного вы зова; если
зад ан а то лько ф у н кц и я, она вы зы вается д ля всех перем енны х окруж ения. И м ена
перем енны х окруж ения вы бираю тся соверш енно произвольно. Н априм ер, можно
определить перем енн ы е developm ent, s ta g e , t e s t и p ro d u c tio n (и л и сокращ енно
prod):

i f (a p p .g e t('e n v ') === 'development') {


app.use(express.errorH andler());
}

E xpress исп ользует систем у ко н ф и гу р ац и и в своей внутренн ей реали зац и и , что


позволяет вам настроить поведение Express, но система кон ф и гурац ии такж е д о ­
ступна д ля вашего личного использования.

Express такж е предоставляет логические разновидности a p p .s e t( ) и app. g e t (). Н а ­


пример, вы зов a p p .e n a b le ( s e ttin g ) эквивалентен a p p . s e t ( s e t t i n g , t r u e ) , а app.
e n a b le d ( s e ttin g ) м ож ет исп ользоваться д ля проверки установки ф лага (парны е
методы д ля f a l s e — a p p .d is a b le ( s e t t i n g ) и a p p .d is a b le d ( s e ttin g ) ) .

Еще один полезны й параметр, часто используемы й при разработке A PI с Express, —


js o n s p a c e s. Е сли д обавить его в ф ай л app.js, р азм етк а JS O N будет вы водиться
в более удобочитаем ом формате:

a p p .s e t('js o n sp aces', 2);


6.2. Express 159

Теперь вы знаете, как использовать систем у кон ф и гурац ии д ля ваш их целей, и мы


мож ем перейти к визуализац ии представлений в Express.

6.2.3. Визуализация представлений


В прилож ении этой главы будут использоваться шаблоны EJS, хотя, как упоминалось
ранее, исп ользоваться м ож ет практически лю бое ядро ш аблонов, известное в со­
обществе Node. Если вы не знаком ы с EJS, не беспокойтесь. EJS имеет много общего
с другими язы кам и шаблонов, встречающ имися в других платформах веб-разработки
(PH P, JSP, ERB). О сновы EJS будут представлены в этой главе, но EJS с несколькими
другими ядрам и ш аблонов будет более подробно рассматриваться в главе 7.

В изуализация представлений, будь то визуализац ия целой страницы H TM L, ф раг­


м ента H T M L и ли кан ала RSS, кри ти чн а практи чески д ля каж дого прилож ения.
К онц епци я проста: вы передаете данны е представлению , эти данны е п реобразу­
ю тся — обычно в ф орм ат H T M L для веб-прилож ений. Вероятно, вы уж е знаком ы
с понятием представления (view ), потому что многие ф рейм ворки представляю т
аналогичную ф ункциональность; на рис. 6.8 показано, как представления ф о р м и ­
рую т новое представление данных.

{ name: 'Tobi', species: 'ferret', age: 2 }

<hl>Tobi</hl>
<p>Tobi is a 2 year old ferret.</p>

Рис. 6.8. Шаблон HTML + данные = представление данных в формате HTML

Ш аблон, генерирую щ ий ш аблон на рис. 6.8, вы глядит так:


<h1><%= name %></h1>
<p><%= name %> is a 2 year old <%= species %>.</p>

Express предоставляет два способа визуализац ии представлений: на уровне при ло­


ж ен ия вы зовом a p p .re n d e r() и в ответе вы зовом r e s .r e n d e r ( ) , которы й использует
первы й во внутренней реализации. В этой главе используется только r e s . re n d e r().
Загл ян у в в ф айл ./routes/index.js, вы увидите, что в нем определяется ф ункц ия, в ы ­
зы ваю щ ая r e s . r e n d e r ( 'i n d e x ') д ля ви зу ал и зац и и ш аблона ./views/index.ejs, как
показано в следую щ ем ф рагм енте (и з listing6_8):
r o u t e r . g e t ( '/ ', (req, re s, next) => {
re s .re n d e r('in d e x ', { t i t l e : 'Express' });
});
160 Глава 6. Connect и Express

Прежде чем рассм атривать r e s .r e n d e r ( ) более подробно, посмотрим, как настроить


систем у представлений.

Настройка системы представлений

Н астр о й ка систем ы представлени й Express в ы п ол н яется достаточно просто. Н о


несм отря на то, что e x p r e s s ( l ) генерирует кон ф и гурац ию за вас, будет полезно
знать, что ж е происходит «за кулисам и», — эта ин ф орм аци я пригодится вам для
внесения изм енений. М ы ограничим ся трем я областями:

О настройка поиска представлений;

О настройка яд р а ш аблонов по умолчанию ;

О вклю чен и е к эш и р о в ан и я п р ед ставл ен и й д л я сокращ ен и я ф ай л ового в в о д а /


вывода.

Н ачнем с парам етра view s.

Изменение каталога поиска

В следую щ ем ф рагм енте показан парам етр views, сгенерированны й Express:

a p p .s e t('v ie w s ', _dirname + '/v ie w s ');

О н задает путь, которы й будет использоваться Express при поиске представлений.


М ы реком ендуем вклю чать в путь п е р е м е н н у ю __dirnam e, чтобы при лож ение не
зависело от того, что текущ ий рабочий каталог яв л яе тс я корневы м каталогом п р и ­
лож ения.

__DIRNAME

__d irn a m e (с д в у м я п о д ч е р к и в а н и я м и ) — гл о б ал ьн ая п ер ем ен н ая N ode,


определяю щ ая каталог, в котором находится ф айл, вы полняем ы й в н астоя­
щ ее время. Ч асто в процессе разработки этот каталог совпадает с текущ им
рабочим каталогом, но при реальной эксплуатации прилож ения исполняемый
ф айл м ож ет быть запущ ен из другого каталога. П ер е м е н н ая __ dirnam e обе­
спечивает согласование путей меж ду разны м и рабочим и средами.

С ледую щ ий парам етр кон ф и гурац ии — view engine.

Ядро шаблонов по умолчанию


К огда при лож ение бы ло сгенерировано e x p r e s s ( l) , парам етру view en g in e было
присвоено значение e js , потому что ядро ш аблонов EJS было вы брано параметром
ком андной строки -e. Э та настройка позволяет использовать index вместо in d ex .
6.2. Express 161

e js . В противном случае Express требует указать расш ирение д ля того, чтобы опре­
делить используем ое ядр о ш аблонов.

П очем у Express вообщ е учиты вает расш и рения? О ни позволяю т использовать н е­


сколько ядер ш аблонов в одном при лож ении Express с сохранением ясности A PI
д ля стандартны х сценариев исп ользован ия (впрочем , в больш инстве прилож ений
используется только одно ядро ш аблонов).

П редполож им, вы обнаруж или, что каналы RSS прощ е програм м ирую тся с другим
ядром ш аблонов, или вы переходите с одного яд р а на другое. П о ум олчанию в п р и ­
лож ени и используется Pug, а д ля м арш рута /f e e d — EJS, на что в следую щ ем коде
указы вает расш ирение .e js :

app.set('v iew en g in e', 'p u g ');


a p p .g e t ( '/ ', function(){
re s .re n d e r('in d e x ');
});
a p p .g e t( '/f e e d ', function(){
r e s .r e n d e r ( 'r s s .e js ') ;
});

СИНХРОНИЗАЦИЯ PACKAGE.JSON
Помните, что каждое дополнительное ядро шаблонов, которое вы хотите использовать
в приложении, должно быть добавлено в объект зависимостей файла package.json.
Такие пакеты должны устанавливаться командой npm i n s t a l l --s a v e имя_пакета
и удаляться из node_modules и package.json командой npm u n i n s t a l l - -sav e имя_па-
кета. Такой подход упрощает эксперименты с разными ядрами шаблонов, когда вы
еще только подбираете ядро для своего проекта.

Кэширование представлений
П арам етр view cache вклю чается по ум олчанию в среде эксплуатац ии прилож ения
и предотвращ ает вы полнение дискового вво д а/вы во д а при последую щ их вы зовах
re n d e r ( ) . С одерж им ое ш аблона сохраняется в памяти, что приводит к зн ач и тел ь­
ному повы ш ению бы стродействия. П обочны й эф ф ект заклю чается в том, что вы не
смож ете редактировать ф айлы ш аблонов без перезапуска сервера; именно по этой
причине данная настройка отклю чена в процессе разработки. Вероятно, в условиях
реальной эксплуатац ии ее следует держ ать вклю ченной.

К ак показано на рис. 6.9, при отклю ченном кэш ировании представлений ш аблон
читается с диска при каж дом запросе. И м енно этот реж им позволяет вносить и з ­
м енени я в ш аблон без перезапуска при лож ения. П ри вклю ченном кэш ировании
обращ ение к диску происходит только один раз на шаблон.

И так, мы показали, как механизм кэш ирования представлений способствует повы ­


ш ению бы стродействия прилож ения (кром е среды разработки). Теперь посмотрим,
как Express находит представления д ля визуализации.
162 Глава 6. Connect и Express

Запрос Кэширование отключено

Запрос Кэширование включено

Рис. 6.9. Кэширование представлений

Поиск представлений
П роцесс поиска представлений проходит по тому же принципу, что и д ля ф ункции
N ode r e q u i r e ( ) . П ри вы зове r e s .r e n d e r ( ) или a p p .re n d e r( ) Express сначала п ро­
веряет, сущ ествует ли ф айл по абсолю тном у пути. З атем Express проводит поиск
относительно каталога views. Н аконец, Express проверяет ф айл index. Н а рис. 6.10
этот процесс изображ ен в виде блок-схемы.

П оскольку e j s назначается ядром по ум олчанию , при вы зове re n d e r расш ирение


.ejs будет опущ ено, и ф айл ш аблона будет разреш ен правильно.

П о мере эволю ции при лож ения вам понадобятся новые представления, а иногда
несколько представлений для одного ресурса. И спользование поиска представлений
6.2. Express 163

Рис. 6.10. Процесс поиска представлений Express

упрощ ает организацию: например, вы можете использовать подкаталоги, связанные


с ресурсами, и создавать представления в этих подкаталогах.

Д обавление подкаталогов позволяет устранить избы точны е части имен (например,


edit-entry.ejs и show-entry.ejs). Тогда Express добавляет расш ирение я д р а ш аблонов
и связы вает r e s . r e n d e r ( 'e n t r i e s / e d i t ') с ./views/entries/edit.ejs.

E xpress проверяет, сущ ествует ли ф ай л с им енем index в подкаталогах каталога


п ред ставл ен и й . П р и с в а и в а н и е ф ай л ам им ен ресурсов в м н ож ествен н ом числе
(наприм ер, entries) обы чно подразум евает список ресурсов. Э то означает, что вы
мож ете использовать вы зов r e s . r e n d e r ( 'e n t r i e s ') для вы полнения визуализации
ф айла views/entries/index.ejs.

Методы передачи данных представлениям


Вы уж е знаете, как передавать локальны е переменны е непосредственно вызовам
r e s .r e n d e r ( ) , но д л я этой цели такж е м ож но использовать и другие механизмы .
Н априм ер, a p p .lo c a ls д ля перем енны х уровня п ри лож ения и r e s . l o c a l s для л о ­
кальны х перем енны х уровня запроса, которы е обычно задаю тся промеж уточны м и
164 Глава 6. Connect и Express

ком п о н ен там и перед итоговы м м етодом о бработки м арш рутов, где происходит
ви зу ал и зац и я представлений.

Зн ач ен и я , переданны е непосредственно r e s .r e n d e r ( ) , обладаю т более вы соким


приоритетом , чем значения, заданны е в r e s . l o c a l s и a p p .lo c a ls (рис. 6.11).

Рис. 6.11. Значения, напрямую передаваемые функции render, имеют более высокий
приоритет при визуализации шаблона

П о ум олчанию E xpress откры вает п р ед ставлени ям доступ только к одной п ер е­


м енной у р о в н я п р и л о ж ен и я s e t t i n g s ; она п ред ставл яет собой объект со всеми
значениям и, заданны м и вы зовами a p p .s e t( ) . Например, при использовании вызова
a p p . s e t ( ' t i t l e ' , 'My A p p lic a tio n ') ш аблону предоставляется значение s e t t i n g s .
t i t l e , как показано в следую щ ем ф рагм енте EJS:

<html>
<head>
<title><%= s e t t i n g s .t i t l e %></title>
</head>
<body>
6.2. Express 165

<h1><%= s e t t i n g s .t i t l e %></h1>
<p>Welcome to <%= s e t t i n g s .t i t l e %>.</p>
</body>

Во внутренней р еализаци и Express доступ к объекту предоставляется следую щ им


кодом JavaS crip t:

a p p .lo c a ls.se ttin g s = a p p .settin g s;

Вот и все! После знакомства с визуализацией представлений и передачей им данных


мы переходим к определению м арш рутов и написанию обработчиков марш рутов,
которы е вы полняю т визуализац ию представлений для прилож ения. Такж е в этом
разделе будут созданы м одели базы данны х д ля долгосрочного хранени я и н ф о р ­
мации.

6.2.4. Знакомство с маршрутизацией в Express


Главная задача марш рутов Express — связать схемы U R L c логикой ответа. О днако
марш руты такж е могут связать схему U R L с пром еж уточны м и компонентами. Это
позволяет вам использовать пром еж уточны е ком поненты д ля назн ачени я м ного­
кратно используем ой ф ункц иональн ости некоторы м марш рутам.

В этом разделе рассм атриваю тся следую щ ие темы:

О проверка введенны х данны х с использованием пром еж уточны х компонентов,


специ ф и чески х д ля марш рута;

О реал и зац и я проверки данны х, специф ической для марш рута;

О реал и зац и я страничной работы с данными.

Рассм отрим некоторы е способы использования специализированны х промеж уточ­


ны х компонентов, связанны х с конкретны м марш рутом.

Проверка данных, введенных пользователем


Ч тобы у вас бы ли дан ны е д л я проверки, мы н аконец-то реал и зуем возм ож ность
п у б л и к ац и и зап и сей в наш ем п р и ло ж ен и и . Д л я этого необходи м о р еш и ть ряд
задач:

О создать м одель данных;

О добавить м арш руты , связанны е с записью;

О создать ф орм ы д ля ввода;

О добави ть л о ги к у со зд ан и я зап и сей с и сп о льзован и ем данны х, введен ны х на


форме.

Н ачнем с создания м одели записи.


166 Глава 6. Connect и Express

Создание модели записи


П реж де чем д в и гаться дальш е, необходим о у стан ови ть в проекте м одул ь N ode
redis. М одуль устан авли вается ком андой npm i n s t a l l - -sav e re d is . Е сли у вас еще
не установлен м одуль Redis, посетите сайт http://redis.io/ и просм отрите и н струк­
ции по его установке; если вы работаете в m acO S, модуль легко устанавливается
из H om ebrew (http://brew.sh/), а в W indow s используется пакет Redis C hocolatey
( https://chocolatey.org/ ).

М ы в о сп о л ьзу ем ся Redis, чтобы нем ного упрости ть задачу: ф у н кц и он ал ьн ость


R edis и ES6 упрощ ает создан ие облегченны х м оделей без слож н ой библи отеки
баз дан ны х. Е сл и вы не ищ ете л егк и х путей, и с п о л ьзу й те д ругую б и б л и о тек у
баз дан ны х (за и н ф о р м ац и ей об и сп о льзо ван и и баз дан ны х в N ode обращ айтесь
к главе 8).

П осмотрим, как создать облегченную м одель д ля хранени я записей чата. Создайте


ф айл models/entry.js для определения модели записи. Добавьте в ф айл код из листин­
га 6.7. М одель запи си будет представлять собой простой класс ES6 для сохранения
данны х в списке Redis.

Листинг 6.7. Модель для записей


const red is = r e q u ir e ( 'r e d is ') ;
const db = re d is .c re a te C lie n t(); Создает экземпляр клиента Redis.
class Entry {
constructor(obj) {
for ( le t key in obj) { •<-------- Перебирает ключи в переданном объекте.
th is[k ey ] = obj[key]; -<------- Объединяет значения.
}
}
save(cb) {
const entryJSON = JS O N .stringify(this); -<— Преобразует сохраненные данные
db.lpush( -<-------- Сохраняет строку JSON в списке Redis. записей в строку JSON.
'e n t r i e s ',
entryJSON,
(e rr) => {
i f (e rr) return c b (err);
cb();
}
);
}
}
module.exports = Entry;

П осле создания базовой модели следует добавить ф ункцию getRange (л и сти н г 6.8).
Э та ф у н кц и я предназначена д ля вы борки записей.
6.2. Express 167

Листинг 6.8. Логика выборки диапазона записей


class Entry { Функция Redis Irange используется
s ta tic getRange(from, to , cb) { для выборки записей.
d b .lr a n g e ( 'e n tr ie s ', from, to , (e rr, items) => {
i f (e rr) return cb (err);
l e t e n trie s = [];
item s.forEach((item ) => {
entries.push(JSO N .parse(item )); ■< Декодирует записи, ранее
}); сохраненные в формате JSON.
cb(null, e n trie s );
});
}

П осле со зд ан и я м одели м ож н о добави ть м арш руты д л я со зд ан и я и п ол уч ен и я


списка записей.

Создание формы для ввода записей


П рилож ение вы водит список записей, но пока не умеет добавлять их. Э та возм ож ­
ность будет добавлен а сейчас, н ач и н ая с вкл ю чен и я следую щ их строк в раздел
r o u tin g ф ай л а app.js:

a p p .g e t( '/p o s t', en tries.fo rm );


a p p .p o s t( '/p o s t', en tries.su b m it);

Затем добавьте следую щ ий м арш рут в ф айл routes/entries.js. Л оги ка м арш рута з а ­
полняет ш аблон, содерж ащ ий форму:

exports.form = (req, res) => {


r e s .r e n d e r ('p o s t', { t i t l e : 'P o st' });
};

Затем ш аблон EJS в листинге 6.9 создает ш аблон для формы и сохраняет его в views/
post.ejs.

Листинг 6.9. Форма для ввода сообщения


<!DOCTYPE html>
<html>
<head>
<title><%= t i t l e %></title>
<link re l= 's ty le s h e e t' h re f = '/s ty le s h e e ts /s ty le .c s s ' />
</head>
<body>
<% include menu %>
<h1><%= t i t l e %></h1>
<p>Fill in the form below to add a new post.</p>
fo rm a c tio n = '/p o s t' method='post'> Текст
<p> заголовка
<input ty p e = 'te x t' n a m e = 'e n try [title]' p laceh o ld er= 'T itle' /> записи.
168 Глава 6. Connect и Express

</p>
<p>
<textarea name='entry[body]' placeholder='Body'></textarea>
</p> Текст тела
записи.
<p>
<input type='subm it' value='P ost' />
</p>
</form>
</body>
</html>

В ф орм е использую тся такие имена, как e n t r y [ t i t l e ] , поэтому потребуется рас­


ш иренны й разбор тела сообщ ения. Ч тобы сменить парсер тела сообщ ения, откройте
ф айл app.js и перейдите к строке

app.use(bodyParser.urlencoded({ extended: fa ls e }));

П риведите ее к следую щ ему виду, чтобы использовать расш иренны й разбор тела
сообщ ения:

app.use(bodyParser.urlencoded({ extended: tru e }));

Р азобравш и сь с отображ ением ф орм ы , перейдем к созданию записей по отп рав­


ленны м данны м формы.

Реализация создания записи


Ч тобы добавить возм ож ность создания записей по отправленны м данны м формы,
добавьте логику из листинга 6.10 в ф айл routes/entries.js.

Листинг 6.10. Добавление записи по отправленным данным формы


Берется из
exports.subm it = (req, re s, next) => { name=”entry[...]” в форме.
const data = req.body.entry; ■<—
const user = re s .lo c a ls .u s e r;
Промежуточный
const username = user ? user.name : null;
компонент для загрузки
const entry = new Entry({
пользователей будет
username: username,
добавлен в листинге 6.28.
t i t l e : d a t a .t i t l e ,
body: data.body
});
e n try .sa v e ((e rr) => {
i f (e rr) return n e x t(e rr);
r e s . r e d i r e c t ( '/ ') ;
});
};

Теперь при обращ ени и к м арш руту / p o s t в п р и лож ен и и вы см ож ете добавлять


записи. П роблем а обязательного входа в прилож ение перед созданием сообщ ений
будет реш ена в листинге 6.21.
6.2. Express 169

Разобравш ись с созданием контента, перейдем к следую щ ей задаче — построению


списка записей.

Добавление списка записей

Создайте ф айл routes/entries.js. Добавьте код из листинга 6.11 для вклю чения модели
запи си и экспортирования ф ун кц и и д ля вы вода списка записей.

Листинг 6.11. Вывод списка записей


const Entry = re q u ire ('../m o d e ls /e n try ');
e x p o r ts .lis t = (req, re s, next) => {
Entry.getRange(0, -1, ( e rr, e n trie s) => { < -------- Получает записи.
i f (e rr) return n e x t(e rr);
r e s .r e n d e r ( 'e n tr ie s ', { -<-------- Строит ответ HTTP.
t i t l e : 'E n tr ie s ',
e n trie s : e n trie s ,
});
});
};

П осле определения л огики м арш рутов необходимо добавить ш аблон EJS д ля их


отображ ения. С оздайте в каталоге views ф айл с именем entries.ejs и поместите в него
код EJS, представленны й в листинге 6.12.

Листинг 6.12. Представление entries.ejs


<!DOCTYPE html>
<html>
<head>
<title><%= t i t l e %></title>
<link re l= 's ty le s h e e t' h re f = '/s ty le s h e e ts /s ty le .c s s ' />
</head>
<body>
<% include menu %>
<% en tries.fo rE ach ((en try ) => { %>
<div class= 'en try '>
<h3><%= e n t r y .ti t l e %></h3>
<p><%= entry.body %></p>
<p>Posted by <%= entry.username %></p>
</div>
<% }) %>
</body>
</html>

Перед запуском прилож ения вы полните команду touch views/menu. e js для создания
временного ф айла, которы й будет содерж ать меню на более поздней стадии. Когда
п редставления и марш руты будут готовы, необходимо сообщ ить прилож ению , где
искать марш руты.
170 Глава 6. Connect и Express

Добавление маршрутов для работы с записями


П реж де чем добавлять в прилож ение м арш руты, относящ иеся к работе с за п и ся ­
ми, необходимо внести изм ен ен ия в app.js. С начала добавьте следую щ ую команду
re q u ir e в начало ф ай л а app.js:

const e n trie s = r e q u ir e ( './r o u te s /e n tr ie s ') ;

Затем такж е в ф айле app.js приведите строку с текстом a p p . g e t ( '/ ' к следую щ ему
виду, чтобы для лю бы х запросов к пути / возвращ ался список записей:

a p p .g e t ( '/ ', e n t r i e s .l i s t ) ;

Теперь при запуске п р и лож ения на главной странице вы водится список записей.
Разобравш ись с созданием и вы водом списка записей, перейдем к использованию
пром еж уточны х ком понентов, п р и вязан н ы х к конкретны м м арш рутам , д ля п ро­
верки данны х форм.

Использование специализированных промежуточных компонентов

П редполож им , вы хотите, чтобы текстовое поле на ф орм е ввода было об язател ь­


ны м для заполнения. П ервое, что приходит в голову, — добавить проверку прям о
в обратны й вы зов м арш рута, как в следую щ ем ф рагменте. О днако такое реш ение
не идеально, потому что логика проверки тесно связы вается с конкретной формой.
Во м ногих случаях ло ги ка проверки м ож ет быть абстрагирована в компонентах,
пригодны х д ля повторного использования; такое реш ение упростит и ускорит р аз­
работку, а код станет более декларативны м :

exports.subm it = (req, re s, next) => {


le t data = req.body.entry;
i f ( ! d a t a .t i tl e ) {
r e s .e r r o r ( 'T itle is r e q u ire d .');
r e s .r e d ir e c t( 'b a c k ') ;
return;
}
i f ( d a ta .title .le n g th < 4) {
r e s .e r r o r ( 'T itle must be longer than 4 c h a ra c te rs .');
r e s .r e d ir e c t( 'b a c k ') ;
return;
}

М арш руты E xpress м огут получать пром еж уточны е ком поненты , при м ен яем ы е
только при сопоставлении этого марш рута, перед заверш аю щ им обратным вызовом
маршрута. С ами обратные вы зовы марш рутов во всех промежуточных компонентах
работаю т одинаково — даж е в тех, которы е мы собираемся создать для проверки
данных!
6.2. Express 171

Н ачнем с простого, но недостаточно гибкого способа реализаци и проверки данны х


в ф орм е специ али зированн ы х пром еж уточны х компонентов.

Проверка данных формы с использованием специализированных


промежуточных компонентов

П ервы й вариант — написание нескольких простых, но специализированны х пром е­


ж уточны х ком понентов д ля вы полнен ия проверки данных. Р асш ирение марш рута
POST / p o s t с таким и ком понентам и м ож ет вы глядеть так:

a p p .p o s t( '/p o s t',
requireE ntryT itle,
requireEntryTitleLengthAbove(4),
entries.subm it
);

О братите внимание: это определение м арш рута, которое обычно получает в аргу­
ментах только путь и логику м арш рутизации, имеет два дополнительны х аргумента
с пром еж уточны м и ком понентам и проверки данных.

Д ва примера компонентов в листинге 6.13 показывают, как абстрагируется исходная


логика проверки данных. Тем не менее такие компоненты все равно нельзя назвать
м одульны м и; они работаю т только д ля одного поля e n t r y [ t i t l e ] .

Листинг 6.13. Две несовершенные попытки создания промежуточных компонентов


проверки данных
function requireE n try T itle(req , re s, next) {
const t i t l e = re q .b o d y .e n try .title ;
if (title ) {
next();
} else {
r e s .e r r o r ( 'T itle is r e q u ire d .');
r e s .r e d ir e c t( 'b a c k ') ;
}
}
function requireEntryTitleLengthAbove(len) {
return (req, re s, next) => {
const t i t l e = re q .b o d y .e n try .title ;
i f ( title .le n g th > len) {
n ext();
} else {
r e s .e r r o r ( 'T itle must be longer than $ { le n } .');
r e s .r e d ir e c t( 'b a c k ') ;
}
};
}

Б олее уни версальн ое реш ение — абстрагировани е логи ки проверки с передачей


им ени целевого поля. П осмотрим, как это делается.
172 Глава 6. Connect и Express

Построение гибких промежуточных компонентов проверки данных


Р еш ение с передачей им ени поля продем онстрировано в следую щ ем фрагменте.
О но позволяет повторно использовать логику проверки данных и уменьш ить объем
кода, которы й вам потребуется написать:

a p p .p o s t( '/p o s t',
v a lid a te .r e q u ir e d ( 'e n tr y [ title ] ') ,
v a lid a te .le n g th A b o v e ('e n try [title ]', 4),
en tries.su b m it);

Зам ени те строку a p p . p o s t ( '/ p o s t ', e n t r i e s .s u b m it) ; в разделе ro u tin g ф айла app.
jsэтим ф рагм ентом . С тоит зам етить, что сообщ ество Express разработало много
аналогичны х библиотек для всеобщ его использования; тем не менее очень п ол ез­
но поним ать, как работаю т пром еж уточны е ком поненты проверки данны х и как
реализовать их самостоятельно.

С оздайте ф айл с именем ./middleware/validate.js и вклю чите в него код из л и ст и н ­


га 6.14. В ф айл validate.js мы экспортируем несколько пром еж уточны х ком п онен­
тов — в данном случае v a lid a te .r e q u ir e d ( ) и v a lid a te . lengthA bove(). П одробности
р еал и зац и и не важ ны ; суть данного при м ера закл ю чается в том, что небольш ие
дополнительны е усилия значительно расш иряю т возможность использования кода
в разны х частях прилож ения.

Листинг 6.14. Реализация промежуточных компонентов проверки данных


function p a rse F ie ld (fie ld ) { < -------- Разбирает синтаксис entry[name].
return fie ld
.s p l i t ( / \ [ |\ ] / )
. f i l t e r ( ( s ) => s);
}
function getF ield (req , fie ld ) { < -------- Ищет свойство на основании результатов parseField().
le t val = req.body;
field.forE ach((prop) => {
val = val[prop];
});
return val;
}
exports.required = (fie ld ) => {
fie ld = p a rse F ie ld (fie ld ); < -------- Разбирает поле.
return (req, re s, next) => {
i f (g etF ield(req , f ie ld ) ) { <-------- При каждом запросе проверяет, содержит ли поле значение.
nex t(); < -------- Если содержит, происходит переход кследующему промежуточному компоненту.
} else {
r e s .e r r o r ( '$ { f ie ld .jo in ( ' ')} is re q u ire d '); <---- Если не содержит, выдается ошибка.
r e s .r e d ir e c t( 'b a c k ') ;
}
};
};
exports.lengthAbove = ( f ie ld , len) => {
fie ld = p a rse F ie ld (fie ld );
6.2. Express 173

return (req, re s, next) => {


i f (g etF ield (req , fie ld ).le n g th > len) {
n ext();
} else {
const fie ld s = f i e l d .j o i n ( ' ') ;
re s .e rro r('$ { fie ld s } must have more than ${len} c h a ra c te rs');
r e s .r e d ir e c t( 'b a c k ') ;
}
};
};

Ч тоб ы п р о м еж у точ н ы й ко м п о н ен т стал до ступ н ы м д л я п р и л о ж ен и я, добавьте


следую щ ую строку в начало ф айла app.js:

const v alid ate = re q u ire ('./m id d le w a re /v a lid a te ');

Е сли вы теперь опробуете прилож ение, то увидите, что проверка данны х успеш но
действует. A P I п роверки дан ны х м ож но сделать ещ е более д инам ичны м , но мы
оставим вам эту задачу д ля сам остоятельной работы.

6.2.5. Аутентификация пользователей


В этом разд ел е мы создадим систем у а у тен ти ф и кац и и д ля п р и л о ж ен и я с нуля.
П роцесс будет состоять из следую щ их шагов:

О реал и зац и я логики д ля хранени я данны х и аутен ти ф и каци и зарегистрирован­


ны х пользователей;

О добавление ф ункц иональн ости регистрации учетны х записей;

О реал и зац и я входа в прилож ение;

О создание и исп ользован ие пром еж уточного ком понента д ля загрузки п ол ьзо­


вателей.

Д л я реализации учетны х записей пользователей такж е будет использоваться Redis.


Теперь посмотрим, как создать модель пользователя для упрощ ения работы с Redis
в коде Node.

Сохранение и загрузка записей пользователей


В этом разделе мы реализуем загрузку, сохранение и аутентиф икацию пользовате­
лей. Здесь будут реш ены следую щ ие задачи:

О определение зависим остей при лож ения с использованием ф айла package.json;

О создание м одели пользователя;

О добавление л огики загрузки и сохранения данны х пользователей с и сп ользо­


ванием Redis;
174 Глава 6. Connect и Express

О защ ита паролей с использованием bcrypt;

О добавление логики аутен ти ф и каци и д ля попы ток входа.

b c ry p t — ф у н кц и я х еш и рования с затравкой (salt), доступная в виде стороннего


модуля, спроектированного специально для хеш ирования паролей. bcrypt особенно
хорош о подходит д ля паролей из-за аргум ента со счетчиком итераций, зам едл яю ­
щего попы тки подбора.

П реж де чем следовать дальш е, добавьте b c ry p t в проект shoutbox:

npm in s ta l l --save redis bcrypt

Создание модели пользователя


Теперь нуж но создать м одель пользователя. С оздайте ф айл с именем user.js в к а ­
талоге models/.

В листинге 6.15 приведена м одель пользователя. В этом коде вклю чаю тся зав и си ­
м ости redis и b crypt, после чего вы зов r e d i s . c r e a t e C l i e n t ( ) откры вает п одклю ­
чение Redis. Ф у н к ц и я User получает объект и объединяет свойства этого объекта
со своими. Н апример, new U ser({ name: 't o b i ' }) создает объект и задает свойству
name этого объекта значение Tobi.

Листинг 6.15. Начало работы над созданием модели пользователя


const red is = r e q u ir e ( 'r e d is ') ;
const bcrypt = re q u ire ('b c ry p t');
const db = re d is .c re a te C lie n t(); < -------- Создает долгосрочное подключение Redis.
class User {
constructor(obj) {
for ( le t key in obj) { < -------- Перебирает свойства переданного объекта.
th is[k ey ] = obj[key]; •<-------- Задает каждое свойство в текущем классе.
}
}
}
module.exports = User; < -------- Экспортирует класс User.

Н а д ан ны й м ом ент м одель п о л ьзо вател я — не более чем заглуш ка. Н еобходим о


д обавить м етоды д л я со зд ан и я и о б н о вл ен и я зап и сей с п о л ьзовател ьски м и д ан ­
ными.

Сохранение данных пользователя в Redis


С ледую щ и й блок ф у н кц и о н ал ьн о сти , которы й нам понадобится, — сохранение
данны х пользователя в Redis. М етод save из листинга 6.16 проверяет, сущ ествует
ли идентиф икатор пользователя, и если да — вы зы вает метод update, индексируя
идентиф икатор пользователя по им ени и зап ол н яя хеш Redis свойствам и объекта.
В противном случае пользователь, не им ею щ ий идентиф икатора, считается новым
6.2. Express 175

пользователем ; зн ач ен и е u s e r : i d s у велич ивается, чтобы п ользователь получил


уни кальны й идентиф икатор, а пароль хеш ируется перед сохранением в R edis тем
ж е методом update.

Д обавьте код из листинга 6.16 в ф айл models/user.js.

Листинг 6.16. Обновление записей пользователей


class User {
/ / ...
save(cb) {
i f ( th is .id ) { •<-------- Если идентификатор определен, то пользователь уже существует.
th is.u p d ate(cb );
} else {
d b .in c r ( 'u s e r :id s ', (e rr, id) => { < -------- Создает уникальный идентификатор.
i f (e rr) return cb (err);
t h i s .id = id; •<-------- Задает идентификатор для сохранения.
this.hashPassw ord((err) => { ■<-------- Хеширует пароль.
i f (e rr) return c b (err);
th is.u p d a te (c b ); < ------ Сохраняет свойства пользователей.
});
});
}
}
update(cb) {
const id = t h i s .id ;
d b .se t('u se r:id :$ { th is.n a m e } ', id , (e rr) => { — Индексирует пользователей по имени.
i f (e rr) return cb (err);
d b .h m set('u ser:$ { id }', th is , (e rr) => { -<- Использует Redis для хранения свойств
c b (err); текущего класса.
});
});
}

Защита паролей

П ри создании пользователя свойству .p a s s при сваи вается пароль пользователя.


З атем л огика сохранения пользователей зам еняет значение свойства . pass хешем,
сгенерированны м с использованием пароля.

К хеш у при м ен яется затравка (salt). П рим енение затравки на уровне п ользовате­
лей помогает защ ититься от атак на базе радуж ны х таблиц: затравка играет роль
закры того клю ча для м еханизм а хеш ирования. Вы мож ете воспользоваться bcrypt
д ля генерирования 12-сим вольной затравки д ля хеш а методом g e n S a lt() .

АТАКИ НА БАЗЕ РАДУЖНЫХ ТАБЛИЦ


Атаки на базе радужных таблиц пытаются взломать хешированные пароли по зара­
нее вычисленным таблицам. С этой темой можно ознакомиться в Википедии: https://
ru.wikipedia.org/wiki/Радужная_таблица.
176 Глава 6. Connect и Express

П осле того как затравка будет сгенерирована, вы зы вается ф ун кц и я b c r y p t.h a s h (),


которая хеш ирует свойство .p a s s и затравку. П олученное значение hash заменяет
свойство .p a s s , после чего вы зов .u p d a te ( ) сохраняет его в Redis; это делается
д ля того, чтобы в базе данны х пароли не сохранялись в текстовом виде — только
в виде хеша.

Л истин г 6.17, которы й следует добавить в models/user.js, определяет функцию , кото­


рая создает хеш с затравкой и сохраняет его в свойстве .p a ss объекта пользователя.

Листинг 6.17. Добавление шифрования bcrypt в модель пользователя


class User {
// ...
hashPassword(cb) {
bcrypt.genSalt(12, (e rr, s a lt) => { -<-------- Генерирует 12-символьнуюзатравку.
i f (e rr) return cb (err);
t h i s . s a l t = s a lt; < -------- Задает затравкудля сохранения.
b cry p t.h a sh (th is.p a ss, s a lt, (e rr, hash) => { < -------- Генерирует хеш.
i f (e rr) return c b (err);
th is .p a s s = hash; < -------- Присваивает хешдля сохранения update().
cb();
});
});
}
}

Вот и всё!

Тестирование логики сохранения пользователя

Ч то б ы п р о тести р о в ать м ех ан и зм с о х р ан ен и я п о л ьзо вател ей , зап у сти те сервер


Redis ком андой r e d i s - s e r v e r в ком андной строке. Д обавьте код из листинга 6.18
в конец ф айла models/user.js. П осле этого вы полните ком анду node m o d e ls /u s e r .js
в ком андной строке, чтобы создать тестового пользователя.

Листинг 6.18. Тестирование модели пользователя


const User = re q u ire ('./m o d e ls /u s e r');
const user = new User({ name: 'Example', pass: 't e s t ' }); ■<---- Создает нового пользователя.
u se r.sa v e ((err) => { < -------- Сохраняет пользователя.
i f (e rr) c o n so le .e rro r(e rr);
co n so le.lo g ('u ser id %d', u se r.id );
});

В ывод долж ен сообщ ать о том, что пользователь был успеш но создан: например,
u s e r id 1. П осле тестирован ия м одели пользователя удалите код листинга 6.18 из
ф ай л а models/user.js.

В программе redis-cli, входящ ей в поставку Redis, мож но ввести команду HGETALL


д ля вы борки всех клю чей и значений хеш а (л и сти н г 6.19).
6.2. Express 177

Листинг 6.19. Вывод информации в интерфейсе командной строки Redis


$ r e d is - c li < -------- Запускает интерфейс командной строки Redis.
redis> get u ser:id s -<-------- Находит идентификатор последнего созданного пользователя.
"1"
redis> h g e ta ll user:1 -<-------- Читает данные элемента.
1) "name" -<-------- Свойства элемента.
2) "tobi"
3) "pass"
4 ) "$2a$12$BAOWThTAkN]Y7Uht0UdBku4
5) "age "
6) "2"
7) "id"
g) "4 "
9 ) "s a lt"
1 0 ) "$2a$12$BAOWThTAkNjY7Uht0UdBku"
redis> q u it < -------- Завершает интерфейс командной строки Redis.

П осле определения логики сохранения пользователя необходимо добавить логику


загрузки инф орм ации.

ДРУГИЕ КОМАНДЫ ПРОГРАММЫ REDIS-CLI


За дополнительной информацией о командах Redis обращайтесь к справочнику команд
Redis по адресу http://redis.io/com m ands.

Чтение данных пользователя


Когда пользователь пы тается вы полнить вход в веб-прилож ение, он обычно вводит
им я и пароль в форму; введенны е данны е отправляю тся прилож ению д ля аутен­
тиф икац ии. П осле того как ф орм а входа будет отправлена, необходимо каким -то
образом произвести вы борку данны х пользователя по имени.

Э та ло ги ка о п р ед ел яется в л и сти н ге 6.20 в м етоде U ser.getB yN am e(). Ф у н к ц и я


сначала определяет идентиф икатор методом U s e r .g e tI d ( ) , а затем передает н ай ­
денны й идентиф икатор методу U ser. g e t ( ) . Этот метод получает данны е хеш а этого
пользователя от Redis. Д обавьте следую щ ие методы в ф айл models/user.js.

Листинг 6.20. Получение данных пользователя от Redis


class User {
/ / ...
s ta tic getByName(name, cb) {
User.getId(name, ( e rr, id) => { < -------- Определяет идентификатор пользователя по имени.
i f (e rr) return cb (err);
U ser.g et(id , cb); < -------- Получает данные пользователя по идентификатору.
});
}
s ta tic getId(name, cb) {
d b .g et('u ser:id :$ {n am e}', cb); < -------- Получает идентификатор индексированием по имени.
178 Глава 6. Connect и Express

}
s ta tic g e t(id , cb) {
d b .h g e ta ll('u s e r:$ { id } ', (e rr, user) => { < ----- Получает данные в виде простого объекта.
i f (e rr) return cb (err);
c b (n u ll, new U ser(user)); < -------- Преобразует простой объект в новый объект User.
});
}
}

Если вам потребуется получить данные пользователя, используйте код следующего


вида:

User.getByName('tobi', (e rr, user) => {


console.log(user);
});

П осле п о л у ч ен и я хеш ированного п ар о л я м ож но переходить к аутен ти ф и кац и и


пользователя.

Аутентификация входа пользователя


П оследний компонент, необходим ы й для аутен ти ф и каци и пользователя, — метод,
определенны й в листинге 6.21. О н использует определенны е ранее ф ун кц и и для
получения данны х пользователя. Добавьте логику из листинга в ф айл models/user.js.

Листинг 6.21. Аутентификация пользователя


s ta tic authenticate(nam e, pass, cb) {
User.getByName(name, (e rr, user) => { -<-------- Проводит поиск пользователя по имени.
i f (e rr) return cb (err);
i f (!u s e r.id ) return cb(); -<-------- Пользователь не существует.
bcrypt.hash(pass, u s e r .s a lt, ( e rr, hash) => { < -------- Хеширует введенный пароль.
i f (e rr) return cb (err);
i f (hash == user.pass) return cb (n u ll, user); < -------- Обнаружено совпадение.
cb(); < -------- Неверный пароль.
});
});
}

Л огика аутентификации начинается с вы борки данных пользователя по имени. Если


пользователь не найден, нем едленно активи зируется ф ун кц и я обратного вызова.
В противном случае сохраненная затравка и введенны й пароль хеш ирую тся для
получения результата, которы й долж ен совпадать с храним ы м хеш ем u s e r .p a s s .
Е сли вы чи слен ны й хеш не совпадает с хранимы м, значит, пользователь ввел н е­
верны е данные. П ри поиске по несущ ествую щ ем у клю чу Redis возвращ ает пустой
хеш; вот почему вместо !u s e r используется ! u s e r .id .

И так, аутен ти ф и каци я заработала; теперь необходимо позаботиться о том, чтобы


пользователи м огли регистрироваться в прилож ении.
6.2. Express 179

6.2.6. Регистрация новых пользователей


Ч тобы пользователь мог создать учетную запись, а затем вы полнить вход, п р и л о ­
ж ение долж но реализовать ф ункциональность регистрации и входа.

Д л я р еализаци и регистрации необходимо реш ить следую щ ие задачи:

О связать м арш руты регистрации и входа с путям и URL;

О добавить логику д ля отображ ения ф орм ы регистрации;

О добавить логику д ля хранения пользовательских данных, отправленны х с ф о р ­


мой.

Ф орм а изображ ена на рис. 6.12.

Э та ф орм а отображ ается при посещ ении м арш рута / r e g i s t e r в браузере. П озднее
мы создадим аналогичную ф орм у для вы полнен ия входа.

Register
Fill in the form below to sign up!

[ Usemam*

I Password

( SignUp I

Рис. 6.12. Форма регистрации пользователя

Добавление маршрутов регистрации


Ч тобы ф орм а регистрации появилась на экране, необходимо создать марш рут для
визуал и зац и и ф орм ы и вернуть ее браузеру для вывода.

В листинге 6.22 показано, как следует изм енить ф айл app.js. С истем а м одулей Node
используется для им п ортирования модуля, определяю щ его поведение м арш рута
из каталога марш рутов, а методы H T T P и пути U R L связы ваю тся с ф ун кц и ям и
м арш рутизац ии. Так ф ор м и р у ется некое подобие контроллера. К ак вы увидите,
м арш руты регистрации сущ ествую т как д ля м етода GET, так и для PO ST.

Листинг 6.22. Добавление маршрутов регистрации

const re g is te r = r e q u ir e ( './r o u te s /r e g is te r ') ; < -------- Включает логику маршрутизации.

a p p .g e t( '/r e g is te r ', re g iste r.fo rm ); ■<-------- Добавляет маршруты.


a p p .p o s t( '/r e g is te r ', re g iste r.su b m it);
180 Глава 6. Connect и Express

Ч тобы определить логику марш рута, создайте в каталоге routes пустой ф айл с им е­
нем register.js. Н ачн ите определение поведения м арш рута регистрации с эк спорти­
ровани я следую щ ей ф ун кц и и из routes/register.js — м арш рута, которы й вы полняет
визуализац ию ш аблона регистрации:

exports.form = (req, res) => {


r e s .r e n d e r ( 'r e g is te r ', { t i t l e : 'R e g iste r' });
};

Д л я определения H T M L -разметки формы регистрации марш рут использует шаблон


EJS, которы й будет создан на следую щ ем шаге.

Создание формы регистрации

Ч тобы определить H T M L -разм етку ф орм ы регистрации, создайте в каталоге views


ф айл с именем register.ejs. Разм етка H T M L /E JS для определения формы приведена
в листинге 6.23.

Листинг 6.23. Шаблон представления с формой регистрации


<!DOCTYPE html>
<html>
<head>
<title><%= t i t l e %></title>
<link r e l= 's ty le s h e e t' h r e f = '/s ty le s h e e ts /s ty le .c s s ' />
</head>
<body>
<% include menu %> < -------- Навигационные ссылки будут добавлены позднее.
<h1><%= t i t l e %></h1>
<p>Fill in the form below to sign up!</p>
<% include messages %> ■<-------- Вывод сообщений будет добавлен позднее.
<form a c tio n = '/re g is te r' method='post'>
<p>
<input ty p e = 'te x t' name='user[name]' placeholder='Username' /> -<------
</p > Пользователь должен ввести
<p> имя пользователя.
<input type='password' name='user[pass]'
placeholder='Password' /> -<-------- Пользователь должен ввести пароль.
</p>
<p>
<input type='subm it' value='Sign Up' />
</p>
</form>
</body>
</html>

О братите вним ание на директиву in c lu d e messages, которая вклю чает в себя другой
ш аблон: m essages.ejs. Этот ш аблон, которы й будет определен на следую щ ем шаге,
используется д ля взаим одействия с пользователем.
6.2. Express 181

Организация обратной связи с пользователем


Во врем я регистрац ии пользователей, как и во м ногих других ч астях типичного
прилож ения, возникает необходимость в передаче обратной связи пользователю .
Н апример, пользователь м ож ет попы таться зарегистрироваться с именем, которое
уж е занято кем -то другим. В этом случае необходимо предлож ить пользователю
вы брать другое имя.

В п р и л о ж ен и и д л я вы во д а ош ибок будет и сп о л ьзо ваться ш аблон m essages.ejs.


М ногие ш аблоны наш его при лож ения будут вклю чать messages.ejs.

Чтобы создать шаблон сообщений, создайте в каталоге views файл с именем messages.
ejs и пом естите в него код из приведенного ниж е фрагмента. Л оги ка ш аблона п ро­
веряет, задано ли значение переменной l o c a l s . messages. Если оно задано, то шаблон
перебирает ее содерж им ое и вы водит объекты сообщ ений. У каж дого объекта со­
общ ения им еется свойство ty p e (позволяю щ ее при необходимости использовать
сообщ ения для оповещ ений, не явл яю щ и х ся ош ибкам и) и свойство s t r i n g (текст
сообщ ения). Л о ги ка при лож ения ставит ош ибку в очередь д ля вывода, добавляя
ее в массив r e s .lo c a ls .m e s s a g e s . П осле того как сообщ ения будут вы ведены, в ы ­
зы вается метод removeMessages д ля очистки очереди сообщений:

<% i f (locals.m essages) { %>


<% messages.forEach((message) => { %>
<p class='<%= message.type %>'><%= m essage.string %></p>
<% }) %>
<% removeMessages() %>
<% } %>

Н а рис. 6.13 изображ ена ф орм а регистрации при вы воде сообщ ения об ошибке.

Register
Fill In the form below to sign up!

Username already taken!

I Password I

I a»nup I

Рис. 6.13. Вывод сообщений об ошибках на форме

Д обавление сообщ ений в r e s .lo c a ls .m e s s a g e s создает простой м еханизм взаи м о­


действия с пользователем. О днако r e s . lo c a ls не сохраняется меж ду перенаправле­
ниям и, поэтом у необходимо продлить срок их сущ ествования, использовав сеансы
д ля их сохранения м еж ду запросами.
182 Глава 6. Connect и Express

Сохранение временных сообщений в сеансах


В веб-прилож ениях прим еняется стандартны й паттерн проектирования P R G ( P o s t/
R e d ire c t/G e t). В этом паттерне пользователь запраш ивает форму, данны е формы
отп р авл яю тся в виде запроса H T T P PO ST, а пользователь перен ап равляется на
другую веб-страницу. Куда им енно перенап равляется пользователь — зависи т от
результата проверки данны х прилож ением . Если данны е ф орм ы признаю тся д ей ­
ствительны м и, то пользователь перенаправляется на новую веб-страницу. Паттерн
P R G преж де всего используется д ля предотвращ ения повторной отправки форм.

В Express при перенаправлении пользователя содержимое r e s .lo c a ls сбрасывается.


Е сли вы сохраняете сообщ ения д ля пользователя в r e s .l o c a l s , сообщ ения будут
потеряны до того, как они появятся на экране. Проблему можно обойти сохранением
сообщ ений в сеансовой переменной. П осле этого сообщ ения могут быть выведены
на итоговой странице перенаправления.

Ч тобы адаптировать возм ож ность постановки сообщ ений в очередь к сеансовой


переменной, необходимо добавить в при лож ение модуль. С оздайте ф айл с именем
./middleware/messages.js и добавьте в него следую щ ий код:

const express = re q u ire ('e x p re s s ');


function message(req) {
return (msg, type) => {
type = type || 'in f o ';
le t sess = req.session;
sess.messages = sess.messages || [];
sess.messages.push({ type: type, strin g : msg });
};
};

Ф ун кц и я r e s . message предоставляет возможность добавления в сеансовую перемен­


ную сообщ ений от лю бы х запросов Express. О бъект ex p ress .re sp o n se представляет
собой прототип, которы й используется Express д ля объектов ответов. Д обавление
свойств в объект означает, что они станут доступны м и д ля всех пром еж уточны х
ком понентов и м арш рутов. В предш ествую щ ем ф рагм енте e x p r e s s .re s p o n s e п р и ­
сваивается переменной с именем res, чтобы упростить добавление свойств к объекту
и сделать код более понятны м.

Д л я р еализаци и этой возм ож ности потребуется поддерж ка сеансов. Д л я этого мы


воспользуем ся E xpress-совм естимы м модулем: оф ици альн о поддерж иваем ы м п а ­
кетом express-session. Установите его командой npm i n s t a l l - -save e x p re s s -s e s s io n ,
а затем добавьте в app.js:
const session = re q u ire ('e x p re ss-se ssio n ');

app.use(session({
se c re t: 's e c r e t',
resave: fa ls e , saveU ninitialized: true
}));
6.2. Express 183

П ром еж у то чн ы й ко м п о н ен т лучш е р азм ести ть после в ставк и п ром еж уточного


ком понента д ля работы с cookie (при близи тельн о около строки 26).

Ч тобы еще больш е упростить добавление сообщ ений, используйте код из следую ­
щего ф рагмента. Ф у н к ц и я r e s . e r r o r позволяет легко добавить в очередь сообщ е­
ний сообщ ение типа e r r o r . И спользуйте ф ункцию res.m essage, которая ранее была
определена в модуле:

re s .e rro r = msg => this.message(msg, 'e r r o r ') ;

О стается сделать последний шаг: предоставить ш аблонам доступ к этим сообщ е­


ниям д ля вы вода на экран. Если этого не сделать, придется передавать массив re q .
s e s s io n .m e s s a g e s каж дом у вы зову r e s . r e n d e r ( ) в п ри лож ении ; конечно, такое
реш ение вряд ли м ож но назвать идеальным.

Д л я р еш ен и я проблем ы мы создадим пром еж уточны й ком понент, которы й для


каж дого запроса будет заполнять массив re s .lo c a ls .m e s s a g e s данны м и из массива
r e s .s e s s io n .m e s s a g e s , ф актически откр ы вая доступ к сообщ ениям д ля каж дого
визуализируем ого ш аблона. П ока что ф айл ./lib/m essages.js расш и ряет прототип
ответа, но ничего не экспортирует. Д обавление в ф айл следую щ его ф рагм ента эк с­
портирует необходим ы й вам пром еж уточны й компонент:

module.exports = (req, re s, next) => {


res.message = message(req);
re s .e rro r = (msg) => {
return res.message(msg, 'e r r o r ') ;
};
res.locals.m essages = req.session.m essages | | [];
res.locals.removeM essages = () => {
req.session.m essages = [];
};
next();
};

С н ач ал а о п р ед ел яется пер ем ен н ая ш аблона m essages д ля хран ен и я сообщ ений


сеанса; это массив, которы й м ож ет сущ ествовать, а мож ет и не сущ ествовать после
вы полнен ия предыдущ его запроса (н е забы вайте, что эти сообщ ения сохраняю тся
меж ду сеансами). Затем нуж но придумать, как удалять сообщ ения из сеанса; в про­
тивном случае они будут неограниченно накапливаться.

Остается лиш ь интегрировать этот новы й механизм ф ункцией re q u ire () в ф айл app.
js. Этот компонент долж ен м онтироваться после ком понента сеанса, поскольку для
него долж но быть определено свойство re q .s e s s io n . О братите внимание: поскольку
этот пром еж уточны й ком понент спроектирован так, что он не получает параметры
и не возвращ ает вторую ф ункцию , м ож но использовать вы зов a p p .u se(m essag e s)
вместо a p p .u s e (m e s s a g e s ()). Д л я надеж ности в пром еж уточны х ком понентах от
сторонних разработчиков лучш е использовать app.use(m essages()) вне зависимости
от того, получает ком понент парам етры и ли нет:
184 Глава 6. Connect и Express

const re g is te r = r e q u ir e ( './r o u te s /r e g is te r ') ;


const messages = require('./m iddlew are/m essages');

app.use(express.m ethodOverride());
app.use(express.cookieP arser());
app.use(session({
se c re t: 's e c r e t',
resave: fa ls e ,
saveU ninitialized: true
}));
app.use(messages);

Теперь к перем енной m essages и ф ун кц и и rem oveM essages() м ож но обратиться из


любого представления, поэтом у ф айл messages.ejs при вклю чении в лю бой ш аблон
долж ен работать правильно.

Разобравш ись с вы водом на экран ф орм ы регистрации и м еханизм ом передачи об­


ратной связи пользователю , перейдем к обработке регистрационной инф орм ации,
отправленной формой.

Регистрация пользователя
М ы долж ны создать м арш рутную ф ункцию д ля обработки H T T P -запросов P O S T
к /register. Э та ф у н кц и я будет назы ваться subm it.

После отправки данны х форм ы промеж уточный компонент b o d y P a rse r() заполняет
отправленны м и данны м и свойство req.body. В ф орм е регистрации используется
объектны й синтаксис user[nam e], которы й после разбора преобразуется в свойство
re q .b o d y .u se r.n a m e . А налогичны м образом д ля поля ввода пароля используется
свойство re q .b o d y .u s e r .p a s s .

Осталось добавить небольшой фрагмент кода к марш руту отправки, который должен
обеспечивать проверку данных (например, проверку того, что им я пользователя еще
не занято), и сохранить нового пользователя, как продемонстрировано в листинге 6.24.

П осле заверш ения регистрации значение u s e r .id сохраняется в сеансе пользовате­


ля; позднее вы мож ете проверить его и убедиться в том, что пользователь прош ел
аутентиф икацию . Е сли проверка проходит неудачно, сообщ ение предоставляется
ш аблонам в виде перем енной m essages через массив r e s .lo c a ls .m e s s a g e s , а п о л ь­
зователь перенап равляется обратно к ф орм е регистрации.

Ч тобы реализовать эту ф ункциональность, добавьте код из листинга 6.24 в ф айл


routes/register.js.

Листинг 6.24. Создание пользователя по отправленным данным


const User = re q u ire ('../m o d e ls /u s e r');

exports.subm it = (req, re s, next) => {


6.2. Express 185

const data = req.body.user;


User.getByName(data.name, (e rr, user) => { -<---- Проверяет имя пользователя на уникальность.
i f (e rr) return n e x t(e rr); < -------- Передает ошибки подключения кбазе данных идругие ошибки.
/ / red is использует значение по умолчанию
i f (u se r.id ) { •<-------- Имя пользователя уже занято.
res.error('U sernam e already ta k e n !');
r e s .r e d ir e c t( 'b a c k ') ;
} else {
user = new User({ < -------- Создает пользователя на основе данных POST.
name: data.name,
pass: data.pass
});
u se r.sa v e ((err) => { < -------- Сохраняет нового пользователя.
i f (e rr) return n e x t(e rr);
req .sessio n .u id = u se r.id ; < -------- Сохраняет uid для аутентификации.
r e s . r e d i r e c t ( '/ ') ; < -------- Перенаправляет на страницу со списком.
});
}
});
};

Теперь вы смож ете запустить прилож ение, перейти по м арш руту /register и зареги ­
стрировать пользователя. Следую щ ее, что потребуется, — м еханизм возвращ ения
зарегистрированны х пользователей д ля аутен ти ф и каци и через ф орм у /lo g in .

6.2.7. Вход для зарегистрированных пользователей

Д обавить функциональность входа еще проще, чем функциональность регистрации,


поскольку больш ая часть нуж ной логики уж е реализована в определенном ранее
универсальном методе аутентиф икации U s e r .a u th e n tic a te () . В этом разделе в наше
при лож ение будут добавлены:

О логика м арш рута д ля вы вода на экран ф орм ы входа;

О логика аутен ти ф и каци и отправленны х из ф орм ы пользовательских данных.

Ф орм а входа вы глядит так, как показано на рис. 6.14.

Login
Fill in the form below to sign in!

( Username

I Password

Рис. 6.14. Форма входа


186 Глава 6. Connect и Express

Д л я начала изм еним ф айл app.js с вклю чением м арш рутов входа и назначением
м арш рутны х путей:

const login = r e q u ir e ( './r o u te s /lo g in ') ;

a p p .g e t( '/lo g in ', login.form );


a p p .p o s t( '/lo g in ', login.subm it);
a p p .g e t('/lo g o u t', lo g in .lo g o u t);

Затем добавляется ф ункциональность для отображ ения ф орм ы входа на экране.

Вывод на экран формы входа


Р еал и зац и я ф орм ы входа начинается с создания ф айла д ля хранения м арш рутов
входа и вы хода из прилож ения: routes/login.js. М арш рутная логика вы вода на экран
ф орм ы входа практи чески не отли чается от логики вы вода ф орм ы регистрации;
различаю тся только им я ш аблона и заголовок страницы:

exports.form = (req, res) => {


re s .r e n d e r ('lo g in ', { t i t l e : 'Login' });
};

Ф о р м а вх о д а E J S -ш аблона, к о т о р а я о п р ед ел ен а в ф а й л е ./view s/login.ejs (л и с ­


тинг 6.25), такж е очень похож а на ф орм у из ф айла register.ejs. Различаю тся только
текст ин струкций и марш рут, по котором у отправляю тся данные.

Листинг 6.25. Шаблон представления для формы входа


<!DOCTYPE html>
<html>
<head>
<title><%= t i t l e %></title>
<link r e l= 's ty le s h e e t' h r e f = '/s ty le s h e e ts /s ty le .c s s ' />
</head>
<body>
<% include menu %>
<h1><%= t i t l e %></h1>
<p>Fill in the form below to sign in!</p>
<% include messages %>
<form a c tio n = '/lo g in ' method='post'> Пользователь должен ввести
<p> имя пользователя.
<input ty p e = 'te x t' name='user[name]' placeholder='Username' /> < ----
</p>
<p>
<input type='password' name='user[pass]'
placeholder='Password' /> < -------- Пользователь должен ввести пароль.
</p>
<p>
<input type='subm it' value='Login' />
6.2. Express 187

</p>
</form>
</body>
</html>

П осле того как вы добавили м арш рут и ш аблон д ля вы вода ф орм ы входа на экран,
в при лож ение нуж но добавить логику обработки попы ток входа.

Аутентификация попыток входа

Д л я о б р аб о т к и п о п ы то к в х о д а в с и с т е м у вам н у ж н о д о б а в и т ь в п р и л о ж ен и е
марш рутную логику, которая проверяет им я пользователя и пароль, отправленные
из ф орм ы . Е сли и м я п о л ьзо вател я и пароль верны , и д ен ти ф и к атор п о л ьзо вате­
л я п р и сваи вается сеансовой перем енной, после чего пользователь п ерен ап р авля­
ется на дом аш ню ю страницу. Д обавьте эту л оги ку (л и ст и н г 6.26) в ф ай л routes/
login.js.

Листинг 6.26. Маршрут для обработки попыток входа


const User = re q u ire ('../m o d e ls /u s e r');

exports.subm it = (req, re s, next) => {


const data = req.body.user;
U ser.authenticate(data.nam e, d ata.p ass, ( e rr, user) => { < ----- Проверяет учетные данные.
i f (e rr) return n e x t(e rr); < ----- Делегирует обработку ошибок.
i f (user) { < ----- Обрабатывает пользователя с действительными учетными данными.
re q .sessio n .u id = u se r.id ; < ----- Сохраняет идентификатор пользователя для аутентификации.
r e s . r e d i r e c t ( '/ ') ; < ----- Перенаправляет ксписку.
} else {
re s .e rro r('S o rry ! invalid c re d e n tia ls. ') ; < ----- Предоставляет сообщение об ошибке.
r e s .r e d ir e c t( 'b a c k ') ; < ----- Перенаправляет обратно кформе входа.
}
});
};

Е сли пользователь проходит аутен ти ф и каци ю м етодом U s e r .a u th e n tic a te ( ) , то


значение свойства r e q .s e s s i o n .u i d при сваи вается по тому ж е принципу, как и в
случае с P O S T -м арш рутом / r e g i s t e r : это значение сохраняется в сеансе и может
прим еняться в дальнейш ем для вы борки объекта User или других данных, связанных
с пользователем . Е сли соответствие не обнаруж ивается, устан авли вается признак
ош ибки, а ф орм а снова вы водится на экран.

Н екоторы е п ользователи предпочитаю т яв н о заверш ать работу с прилож ением ,


поэтом у нуж но создать соответствую щ ую ссы лку в при лож ении . В ф айле app.js
м арш рут создается следую щ им образом:

const login = r e q u ir e ( './r o u te s /lo g in ') ;

a p p .g e t('/lo g o u t', lo g in .lo g o u t);


188 Глава 6. Connect и Express

З атем в ф айле ./routes/login.js следую щ ая ф у н к ц и я удали т сеанс, обнаруж енны й


пром еж уточны м ком понентом s e s s io n ( ) , что приведет к установке сеанса д ля п о ­
следую щ их запросов:

exports.logout = (req, res) => {


re q .se ssio n .d e stro y ((e rr) => {
i f (e rr) throw e rr;
r e s . r e d i r e c t ( '/ ') ;
})
};

П осле создания страниц регистрации и входа нуж но добавить меню, чтобы п о л ь­


зователи м огли на них попасть. Э тим мы и займемся.

Создание меню для аутентифицированных и анонимных


пользователей
В этом разделе мы создадим меню как д ля анонимны х, так и д ля аутен ти ф и ц и ро­
ванн ы х пользователей. Ч ерез это меню пользователь смож ет входить в систем у
регистрироваться, отправлять записи из формы и выходить из системы. Н а рис. 6.15
представлено меню д ля аноним ного пользователя.

R О M ozilla Fircfox
h t l p : / /1 2 7 .0 .0 .1 :3 0 0 0 / | + [___________________________________________________________________________________________

0.0.13ООО q l_*j lib-]

Рис. 6.15. Меню входа и регистрации обеспечивает доступ к созданным нами формам

К огда п о л ь зо в а т е л ь п р о х о д и т а у тен ти ф и к а ц и ю , на эк р ан е п о я в л я е т с я другое


м еню с им енем п о л ь зо в а те л я и д в у м я ссы лкам и: ссы л кой на стран и ц у д л я о т ­
правки сообщ ений в чат и ссы лкой д л я вы хода из системы. Это меню показано
на рис. 6.16.

О О О M ozilla Fircfox
I <♦ h ttp ://127.0.0.1:3000/
HL+l
(•* )$ 127.0.0.1 3000 ' С Coogle < 0 [ A | | C - |

rick - post logout

Рис. 6.16. Меню для пользователя, прошедшего аутентификацию

К аж ды й создан ны й вам и E JS -ш аблон, п ред ставляю щ и й стран ицу при лож ения,
содерж ит код <% in c lu d e menu %>, располож енны й после тега <body>. Э тот код под ­
клю чает ш аблон ./views/menu.ejs (л и сти н г 6.27).
6.2. Express 189

Листинг 6.27. Шаблон меню для анонимных пользователей и пользователей,


прошедших аутентификацию
<% i f (lo c a ls.u se r) { %>
<div id='menu'> < ----- Менюдля пользователей, выполнивших вход.
<span class='name'><%= user.name %></span>
<a href='/post'> post</a>
<a href='/logout'>logout</a>
</div>
<% } else { %>
<div id='menu'> < ----- Менюдля анонимных пользователей.
<a href= '/login'> login< /a>
<a h re f= '/re g iste r'> re g iste r< /a >
</div>
<% } %>

П редп олагается, что, раз п ерем енн ая u s e r д оступн а д л я ш аблона, пользователь


прош ел аутентиф икацию , поскольку в противном случае эта перем енная не была
бы определена. Е сли перем енная присутствует, им я пользователя мож но вывести
на экран вместе со ссы лкам и д ля ввода данны х и выхода из прилож ения. Если же
сайт посещ ает аноним ны й пользователь, вы водятся ссы лки входа и регистрации.

В озни кает резонн ы й вопрос — откуда берется л о кал ьн ая перем енн ая u se r, если
мы ее еще не создали? В следую щ ем разделе будет написан код, реализую щ ий для
каж дого запроса загрузку данны х пользователя, вош едш его в систему, и о ткры ва­
ю щ ий ш аблонам доступ к этим данным.

6.2.8. Промежуточный компонент для загрузки


пользовательских данных

О дна из типичны х задач веб-прилож ений — загрузка из базы данны х ин ф орм ации
о пользователе, которая обычно предоставляется в виде Jav aS crip t-объекта. Наличие
подобны х данны х облегчает взаим одействие с пользователем. В при лож ении этой
главы для каж дого запроса будут загруж аться пользовательские данны е с исп оль­
зованием промеж уточного компонента.

С ценарий разм ещ ается в ф айле ./lib/middleware/user.js, то есть модель User долж на


находиться в папке более высокого уровня (./lib). Сначала экспортируется ф ункция
промежуточного компонента, затем сеанс проверяется на предмет наличия иденти­
ф икатора пользователя. Е сли идентиф икатор присутствует, значит, пользователь
прош ел аутентиф икацию , поэтом у при лож ение м ож ет безопасно вы полнить вы ­
борку данны х из Redis.

П оскольку N ode я в л яется однопоточной платф орм ой, локальное хранилищ е п ро­
граммных потоков здесь отсутствует. В случае с H T T P -сервером переменные запроса
и ответа яв л яю тся единственны м и доступны м и контекстны м и объектами. В ы со­
коуровневы е ф рейм ворки м огут пользоваться средствам и N ode и предоставлять
190 Глава 6. Connect и Express

до п о л н и тел ьн ы е объекты д л я х р ан ен и я дан ны х аутен ти ф и ц и р о ван н ы х п о л ь зо ­


вателей, однако в Express бы ло при нято реш ение придерж и ваться исходны х объ­
ектов, предлагаем ы х платф орм ой N ode. В результате контекстны е данны е обычно
х р ан ятся в объекте запроса, как показано в ли сти н ге 6.28, где пользовательские
данны е сохраняю тся в свойстве re q .u s e r; последую щ ие программные пром еж уточ­
ны е ком поненты и м арш руты м огут получать доступ к пользовательским данны м
через это свойство.

В озм ож но, вас интересует, д ля чего нуж но при сваи ван и е r e s . l o c a l s . u s e r . r e s .


l o c a l s — объект уро вн я запроса, которы й Express предоставляет д ля получения
доступа к данны м из ш аблонов, что очень напоминает a p p .lo c a ls . Это тож е некая
ф ункц ия, которая м ож ет при м ен яться д ля объединения сущ ествую щ их объектов.

Листинг 6.28. Промежуточный компонент для загрузки данных пользователя,


выполнившего вход

const. User
.. = require(,, ../m. odels/user
. , . ,,); Получает идентификатор пользователя,
выполнившего вход, из сеансовыхданных.
module.exports = (req, re s, next) => {
const uid = req .se ssio n .u id ; ■<­ Получаетданные пользователя,
i f (!uid) return next(); выполнившего вход, от Redis.
U ser.get(uid, (e rr, user) => {
i f (e rr) return n e x t(e rr);
req.user = re s .lo c a ls .u s e r = user;
next(); Предоставляет доступ кданным
}); пользователя объекту запроса.
};

Ч тобы воспользоваться новы м пром еж уточны м компонентом, сначала удалите из


ф ай л а app.js все строки с текстом «user». Затем , как обычно, вклю чите этот модуль
вы зовом r e q u ir e ( ) и передайте его м етоду a p p .u s e ( ). В наш ем при лож ении п ере­
менная u ser используется перед марш рутизатором, поэтому свойство re q .u s e r будет
доступно только для м арш рутов и промеж уточны х компонентов, следую щ их в коде
после u se r. Если вы используете пром еж уточны й компонент, которы й загруж ает
данны е (как в наш ем случае), ком понент e x p r e s s .s t a t i c следует разм естить перед
ним, иначе каж ды й раз при предоставлении статического ф айла будет происходить
лиш нее обращ ение к базе данны х д ля вы борки пользовательских данных.

В листинге 6.29 показано, как вклю чить этот ком понент в ф айл app.js.

Листинг 6.29. Включение компонента, загружающего пользовательские данные


const user = re q u ire ('./m id d le w a re /u se r');

ap p .u se(ex p ress.sessio n ());


a p p .u se (e x p re ss.sta tic (_dirname + '/p u b l i c ') ) ;
app.use(user); < -------- Добавляет компонент в приложение.
app.use(messages);
app.use(app.router);
6.2. Express 191

Е сли вы снова запустите прилож ение и откроете в браузере страницу / l o g i n или


/ r e g i s t e r , на экране появится меню. Чтобы применить к меню стилевое оформление,
добавьте в ф айл public/stylesheets/style.css разм етку CSS из листинга 6.30.

Листинг 6.30. Разметка CSS, добавляемая в файл style.css для оформления меню
#menu {
position: absolute;
top: 15px;
rig h t: 20px;
fo n t-s iz e : 12px;
color: #888;
}
#menu .nam e:after {
content: ' - ';
}
#menu a {
tex t-d eco ratio n : none;
m argin-left: 5px;
color: black;
}

Теперь, когда меню готово, попробуйте зарегистрироваться в качестве пользовате­


ля. П осле этого на экране долж но п оявиться меню д ля пользователя, прош едш его
аутентиф икацию , в котором есть ссы лка Post.

В следующем разделе вы узнаете, как создать открытый R EST A PI для прилож ения.

6.2.9. Создание открытого REST API


В этом разделе д ля при лож ения будет реализован R E S T -совм естимы й откры ты й
A PI, чтобы сторонние при лож ения могли обращ аться к данны м и добавлять новые
запи си. А р х итектурны й стиль R E S T обеспечивает возм ож н ость ч тен и я и и зм е ­
нен ия данны х пр и ло ж ен и я с исп ользован ием «глаголов» и «сущ ествительны х»,
представленны х м етодам и H T T P и U R L -адресами соответственно. З ап рос R EST
обычно возвращ ает данны е в формате, удобном д ля м аш инной обработки, н ап ри ­
мер J S O N и ли XM L.

Р еал и зац и я A P I состоит из следую щ их этапов:

О про екти р о ван и е A P I, с пом ощ ью которого пол ьзовател и см огут отображ ать,
вы водить список, удалять и публиковать записи;

О добавление базовой аутентиф икации;

О реал и зац и я м арш рутизации;

О предоставление ответов в ф орм ате JS O N и XML.

Д л я аутен ти ф и кац и и и сер ти ф и кац и и запросов A P I могут при м ен яться разны е


методы , но р еал и зац и я более слож ны х р еш ен ий вы ходит за рам ки тем ы книги.
192 Глава 6. Connect и Express

Ч тобы продем онстрировать, как а у тен ти ф и кац и я ин тегри руется в прилож ение,
мы воспользуем ся пакетом basic-auth.

Проектирование API
П реж де чем браться за реализацию , ж елательно спланировать необходимые м арш ­
руты. В наш ем при лож ении R E S T -совм естимы й A PI будет снабж аться преф иксом
/ a p i , но это всего лиш ь реш ение из области проектирования, которое вы мож ете
изм енить — например, использовать поддомен вида http://api.myapplication.com.
С ледую щ ий фрагмент демонстрирует, почему ф ункции обратного вы зова стоит вы ­
делить в отдельны е м одули N ode (вм есто определения их во встроенном ф ормате
с вы зовам и a p p . метод()). П ростой список м арш рутов дает четкое представление
о том, что вы и ваш а группа р еал и зо вал и и где находятся р еал и зац и и обратны х
вызовов:

a p p .g e t( '/a p i/u s e r /:id ', a p i.u se r);


a p p .g e t( '/a p i/e n tr ie s /:p a g e ? ', a p i.e n trie s );
a p p .p o s t( '/a p i/e n tr y ', api.add);

Добавление базовой аутентификации


К ак упоминалось ранее, существует много возможных подходов к защ ите A PI и огра­
ничений, вы ходящ их за рам ки темы книги. В этом разделе мы продемонстрируем
процесс на прим ере базовой аутентиф икации.

П роцесс будет абстрагироваться пром еж уточны м ком понентом a p i.a u th , потому


что р еал и зац и я будет находиться в м одуле ./routes/api.js (вскоре мы создадим этот
м о ду л ь). М етод a p p .u s e ( ) м ож ет получать путь, которы й в E xpress н азы вается
точкой монтирования. С этой точкой м о н ти р о в ан и я д ля лю бы х путей, н ач и н аю ­
щ ихся с / a p i , и лю бой ком анды H T T P, будет ак ти ви зи р о ваться пром еж уточны й
компонент.

С трока a p p . u s e ( '/ a p i ', a p i .a u t h ) , как показано в следую щ ем ф рагменте, долж на


располагаться до промежуточного компонента, загружаю щего данные пользователя.
Это нужно для того, чтобы позднее вы м огли изменить компонент загрузки данных,
чтобы загруж ать данны е пользователей, прош едш их аутентиф икацию :

const api = r e q u ir e ( './r o u te s /a p i') ;

a p p .u s e ( '/a p i', ap i.a u th );


app.use(user);

Д л я вы полнения базовой аутентиф икации установите модуль basic-auth командой


npm i n s t a l l - -sav e b a s ic - a u th . Затем создайте ф айл ./routes/api.js и вклю чите как
Express, так и м одель пользователя, как показано в следую щ ем фрагменте. Пакет
6.2. Express 193

b a sic -a u th п о л у ч ает ф у н к ц и ю д л я в ы п о л н е н и я ау т е н ти ф и к а ц и и с си гн атурой


(имя_пользователя, пароль, обратный_вызов). М етод U s e r .a u th e n tic a te идеально
подходит для этой цели:

const auth = re q u ire ('b a s ic -a u th ');


const express = re q u ire ('e x p re s s ');
const User = re q u ire ('../m o d e ls /u s e r');

exports.auth = (req, re s, next) => {


const { name, pass } = auth(req);
User.authenticate(nam e, pass, (e rr, user) => {
i f (user) req.remoteUser = user;
n e x t(e rr);
});
};

А утентиф икация готова к работе. П ерейдем к р еализаци и м арш рутов API.

Реализация маршрутизации

П ервы й марш рут, которы й мы реализуем , — GET / a p i / u s e r / : i d . Л оги ка этого м арш ­


рута долж на сначала получить пользователя по идентиф икатору id и ответить кодом
404 N o t Found, если пользователь не существует. Если пользователь существует, то
данны е пользователя будут переданы r e s .s e n d ( ) д ля сериализации, а прилож ение
ответит представлением этих данны х в ф орм ате JS O N . Д обавьте логику из следу­
ющего ф рагм ента в ф айл routes/api.js:

exports.user = (req, re s, next) => {


U ser.get(req.param s.id, (e rr, user) => {
i f (e rr) return n e x t(e rr);
i f (!u s e r.id ) return res.sendStatus(404);
re s.jso n (u se r);
});
};

Затем добавьте следую щ ий путь м арш рута в ф айл app.js:

a p p .g e t( '/a p i/u s e r /:id ', a p i.u se r);

Все готово к тестированию .

Тестирование выборки данных пользователя

Запустите прилож ение и протестируйте его в программ е ком андной строки cU RL.
Тестирование R E S T -аутен ти ф и каци и при лож ения продем онстрировано в следую ­
щем ф рагменте. Учетные данны е передаю тся в U R L tobi:ferret, по которы м cU R L
строит поле заголовка A uthorization:

$ curl h ttp ://to b i:fe rre t@ 1 2 7 .0 .0 .1 :3 0 0 0 /a p i/u se r/1 -v


<

194 Глава 6. Connect и Express

В листинге 6.31 представлен результат успеш ного тестирования. Д л я вы полнения


аналогичного теста необходимо знать идентификатор пользователя. Если значение 1
не работает, а п ользователь зарегистрирован, попробуйте исп ользовать redis-cli
и ком анду GET u s e r :id s .

Листинг 6.31. Тестовый вывод


* About to connect() to lo cal port 80 (#0)
* Trying 1 2 7 .0 .0 .1 ... connected
* Connected to lo cal (127.0.0.1) port 80 (#0)
* Server auth using Basic with user 'to b i'
> GET /a p i/u s e r/1 HTTP/1.1 < -------- Отправленные заголовки HTTP.
> A uthorization: Basic Zm9vYmFyYmF6Cg==
> User-Agent: c u rl/7 .2 1 .4 (universal-apple-darw in11.0) lib c u rl/7 .2 1 .4
OpenSSL/0.9.8r z lib /1 .2 .5
> Host: local
> Accept: */*
>
< HTTP/1.1 200 OK < -------- Полученные заголовки HTTP.
< X-Powered-By: Express
< Content-Type: applicatio n /Jso n ; charset=utf-8 < -------- Полученные данные JSON.
< Content-Length: 150
< Connection: keep-alive
<
{"id":"1","nam e":"tobi"}

Удаление конфиденциальной информации пользователя


К ак видно из ответа JS O N , в ответе передается как пароль пользователя, так и з а ­
травка. Ч тобы изм енить ситуацию , реализуйте метод .toJSON() в объекте User из
ф ай л а models/user.js:

class User {
// ...
toJSON() {
return {
id: th is .i d ,
name: this.name
};
}

Если метод .toJSON сущ ествует в объекте, он будет использоваться вы зовам и JSON.
s t r i n g i f y для получения ф орм ата JS O N . Е сли бы приведенны й выш е запрос cU R L
был вы дан повторно, на этот раз бы ли бы получены только свойства id и name:

{
"id": "1",
"name": "tobi"
}

Н а следую щ ем ш аге в A P I необходимо добавить возм ож ность создания записей.


6.2. Express 195

Добавление записей
Процессы добавления записи с форм ы H T M L и через A PI практически идентичны,
поэтом у в данном случае стоит снова воспользоваться уж е реализованной логикой
м арш рута e n t r ie s .s u b m it ( ) .

Впрочем, при добавлении записей в логике м арш рута храни тся им я пользователя,
а запись добавляется к прочей информ ации. По этой причине необходимо м оди ф и­
цировать ком понент загрузки данны х пользователя д ля заполнен ия инф орм ации,
загруженной промежуточным компонентом b a sic-au th . Промежуточный компонент
b a sic -a u th возвращ ает эту информацию , которую можно присвоить req.rem oteU ser.
С оответствую щ ая проверка в ком поненте загрузки данны х пользователя в ы п ол ­
няется достаточно прям олинейно; изм ените определение m o d u le .e x p o rts в ф айле
middleware/user.js, чтобы ком понент загрузки пользователей работал с API:

module.exports = (req, re s, next) => {


i f (req.remoteUser) {
re s .lo c a ls .u s e r = req.remoteUser;
}
const uid = req .sessio n .u id ;
i f (!uid) return next();
U ser.get(uid, (e rr, user) => {
i f (e rr) return n e x t(e rr);
req.u ser = re s .lo c a ls .u s e r = user;
next();
});
};

П осле этого вы смож ете добавлять запи си через A PI.

Впрочем, необходим о внести еще одно изм ен ен ие — ответ, совм ести м ы й с A PI,
вм есто п ер ен ап р авлен и я на домаш ню ю стран ицу при лож ен и я. Д л я добавления
этой ф ункц иональн ости приведите вы зов e n tr y .s a v e в ф айле routes/entries.js к сле­
дую щ ему виду:

e n try .sa v e (e rr => {


i f (e rr) return n e x t(e rr);
i f (req.remoteUser) {
res.jso n ({ message: 'Entry added.' });
} else {
r e s .r e d i r e c t ( '/ ') ;
}
});

Н аконец, чтобы активировать A PI добавления записей в ваш ем прилож ении, д о ­


бавьте содерж им ое следую щ его ф рагм ента в раздел r o u tin g ф ай л а app.js:

a p p .p o s t( '/a p i/e n tr y ', en tries.su b m it);


196 Глава 6. Connect и Express

С ледую щ ая команда cU R L позволяет протестировать добавление записи через API.


В этом случае данны е заголовка и тела сообщ ения передаю тся с им енам и полей,
которы е использую тся в ф орм е H TM L:

$ curl -X POST -d "e n try [title ]= 'H o ho ho'&entry[body]='Santa loves you'"


h ttp ://to b i:ferret@ 1 2 7 .0 .0 .1 :3 0 0 0 /ap i/en try

После способности создания записей необходимо добавить возможность получения


данны х записей.

Добавление поддержки списка записей


С ледую щ им будет реализован м арш рут A P I GET / a p i / e n t r i e s / : p a g e ? . Р еализаци я
м арш рута почти не отличается от м арш рута вы вода в ф айле ./routes/entries.js. Также
потребуется промежуточны й компонент страничного вывода — page() в следующих
ф рагментах. Р еал и зац и я p ag e() будет добавлена ниже.

Так как м арш рутная логика будет работать с записям и, в начало ф айла routes/api.js
следует вклю чить м одель Entry:
const Entry = re q u ire ('../m o d e ls /e n try ');

Затем добавьте следую щ ий ф рагм ент в app.js:


const Entry = re q u ire ('./m o d e ls /e n try ');

a p p .g e t( '/a p i/e n tr ie s /:p a g e ? ', page(Entry.count), a p i.e n trie s );

П осле чего добавьте м арш рутную логику из следую щ его ф рагм ента в ф айл routes/
api.js. Р азл и ч и я меж ду этой логикой и аналогичной логикой из routes/entries.js от­
раж аю т тот факт, что вместо визуализац ии ш аблона строится разм етка JSO N :

e x p o rts.e n trie s = (req, re s, next) => {


const page = req.page;
Entry.getRange(page.from, page.to, (e rr, e n trie s) => {
i f (e rr) return n e x t(e rr);
re s .js o n (e n trie s );
});
};

Реализация промежуточного компонента страничного вывода


Д л я разбиения на страницы будет использоваться строка запроса ?page=N (где N —
текущ ая страница). Добавьте ф ункцию (листинг 6.32) промежуточного компонента
в ф айл ./middleware/page.js.

Листинг 6.32. Промежуточный компонент для разбиения на страницы


module.exports = (cb, perpage) => {
perpage = perpage || 10; ■<-------- По умолчанию 10 записей на страницу.
return (req, re s, next) => { < -------- Возвращает функцию промежуточного компонента.
le t page = Math.max(
6.2. Express 197

parseInt(req.param s.page || '1 ', 10)


1
) - 1; < -------- Разбирает параметр page какдесятичное целое число.
c b ((e rr, to ta l) => { -<-------- Активизирует переданнуюфункцию.
i f (e rr) return n e x t(e rr); < -------- Делегирует обработку ошибок.
req.page = re s.lo c a ls.p a g e = { -<-------- Сохраняет свойства page для будущих обращений.
number: page,
perpage: perpage,
from: page * perpage,
to : page * perpage + perpage - 1,
t o ta l: t o t a l,
count: M a th .c e il(to ta l / perpage)
};
n ex t(); < -------- Передает управление следующему промежуточного компоненту.
});
}
};

К ом понент получает значение, присвоенное ?page=N (наприм ер, ?page=1). Затем


он получает общее количество результатов и предоставляет объект page с заранее
вы численны м и значениям и лю бы м представлениям , которы е позднее могут в и зу ­
ализироваться. Э ти зн ачения вы чи сляю тся вне ш аблона, чтобы ш аблон был более
четким и содерж ал м еньш е логики.

Тестирование маршрута entries


С ледую щ ая ком анда cU R L запраш ивает данны е записей из API:

$ curl h ttp ://to b i:fe rre t@ 1 2 7 .0 .0 .1 :3 0 0 0 /a p i/e n trie s

Э та ком анда cU R L долж на вы вести данны е J S O N следую щ его вида:

[
{
"username": "rick",
" t i t l e " : "Cats c a n 't read minds",
"body": "I think you're wrong about the cat th in g ."
},
{
"username": "mike",
" t i t l e " : "I think my cat can read my mind",
"body": "I think cat can hear my thoughts."
},

Разобравш и сь с базовой р еализаци ей A PI, перейдем к тому, как A P I м ож ет под ­


держ ивать м нож ественны е ф орм аты ответов.

6.2.10. Согласование контента


Согласование контента позволяет клиенту указать форматы, которые он готов п ри­
нять и которы е он считает предпочтительны м и. В этом разделе мы предоставим
198 Глава 6. Connect и Express

контент A P I в ф орм атах JS O N и XM L, чтобы пользователи A P I м огли реш ить, что


им нужно.

H T T P поддерж ивает механизм согласования контента через поле заголовка Accept.


Н априм ер, клиент, предпочитаю щ ий H T M L , но готовы й при нять простой текст,
мож ет задать следую щ ий заголовок запроса:

Accept: te x t/p la in ; q=0.5, text/htm l

Зн ач ен и е qvalue (q= 0.5 в данном при м ере) указывает, что, хотя ф орм ат te x t/h tm l
указан на втором месте, он на 50% предпочтительнее ф орм ата t e x t / p l a i n . Express
разбирает эту информацию и предоставляет нормализованны й массив req .accepted:

[{ value: 'te x t/h tm l', q u ality : 1 },


{ value: 't e x t / p l a i n ', q u a lity : 0.5 }]

Express такж е предоставляет метод r e s .f o r m a t( ) , которы й получает массив типов


M IM E и обратны х вызовов. Express определяет, какую инф орм ацию хочет п о л у ­
чить клиент и какую инф орм ацию вы готовы предоставить, после чего вы зы вает
соответствую щ ую ф ункцию обратного вызова.

Реализация согласования контента


Р еали зац и я согласования контента для м арш рута GET / a p i / e n t r i e s из ф айла routes/
api.jsм ож ет вы глядеть при близи тельн о так, как показано в листинге 6.33. JS O N
поддерж ивается так же, как и прежде, — записи сериализую тся в ф орм ат JS O N вы ­
зовом r e s .s e n d ( ) . О братны й вы зов X M L перебирает запи си и записы вает данны е
в сокет в процессе перебора. Учтите, что явно задавать значение C ontent-T ype не
нуж но; r e s .f o r m a t( ) присваивает нуж ное значение автоматически.

Листинг 6.33. Реализация согласования контента


e x p o rts.e n trie s = (req, re s, next) => {
const page = req.page;
Entry.getRange(page.from, page.to, (e rr, e n trie s) => { <.----- Получает данные записей.
i f (e rr) return n e x t(e rr);
res.form at({ -<-------- Отвечает по-разному в зависимости от значения заголовка Accept.
'a p p lic a tio n /js o n ': () => { Ответ JSON.
re s.se n d (e n trie s);
},
'a p p lic a tio n /x m l': () => { < -------- Ответ XML.
re s .w rite ('< e n trie s > \n ');
en tries.fo rE ach ((en try ) => {
r e s . w r i t e ( '''
<entry>
< title > $ { e n try .title } < /title >
<body>${entry.body}</body>
<username>${entry.username}</username>
</entry>

);
6.2. Express 199

});
re s .e n d ('< /e n trie s > ');
}
})
});
};

Е сли вы задали обратны й вы зов д ля ф орм ата ответа по умолчанию , он будет вы ­


полнен в том случае, если п о л ьзо в ател ь не зап р о си л ф орм ат, которы й вы явн о
предоставляете.

М етод r e s .f o r m a t ( ) такж е получает имя, которое соответствует заданном у типу


M IM E . Н априм ер, вм есто a p p l i c a t i o n / j s o n и a p p lic a tio n /x m l м огут и сп о льзо ­
ваться js o n и xml:

res.form at({
j s °n: () => {
re s.se n d (e n trie s);
},
xml: () => {
re s .w rite ('< e n trie s > \n ');
en tries.fo rE ach ((en try ) => {
re s.w rite (
<entry>
< title > $ { e n try .title } < /title >
<body>${entry.body}</body>
<username>${entry.username}</username>
</entry>

);
});
re s .e n d ('< /e n trie s > ');
}
})

Ответ в формате XML


П ож алуй, написание специ али зированн ой логики в м арш руте для вы дачи ответа
в ф орм ате X M L — не самое элегантное реш ение. П осм отрим , как воспользоваться
систем ой представлений д ля реш ен ия этой проблемы.

С оздайте ш аблон с им енем ./views/entries/xml.ejs со следую щ им кодом EJS, п ере­


бираю щ им запи си д ля генерирования тегов <entry> (л и сти н г 6.34).

Листинг 6.34. Использование шаблона EJS для генерирования разметки XML


<entries>
<% en tries.fo rE ach (en try => { %> < -------- Перебирает все записи.
<entry>
<title><%= e n t r y .t i t l e %></title> <.-------- Выводит поля.
200 Глава 6. Connect и Express

<body><%= entry.body %></body>


<username><%= entry.username %></username>
</entry>
<% }) %>
</entries>

О братны й вы зов X M L теперь м ож но зам енить одним вызовом r e s .r e n d e r ( ) с пере­


дачей м ассива e n tr ie s :

xml: () => {
re s .re n d e r('e n trie s /x m l', { e n trie s : e n trie s });
}
})

Теперь все готово для тестирования X M L -версии API. Введите следующую команду
в ком андной строке, чтобы просм отреть вы вод в ф орм ате XML:

curl - i -H 'Accept: application/xm l'


h ttp ://to b i:fe rre t@ 1 2 7 .0 .0 .1 :3 0 0 0 /a p i/e n trie s

6.3. Заключение
О C o n n ect — ф рейм ворк H TTP, которы й позволяет строить цепочки пром еж уточ­
ны х ком понентов до и после обработки запросов.

О П ром еж уточны е ком поненты C o n n ect представляю т собой ф ункции, которые


получаю т объекты запроса и ответа N ode, а такж е ф ункцию , которая вызывает
следую щ ий компонент, и необязательны й объект ош ибки.

О В еб-прилож ения Express такж е строятся из пром еж уточны х компонентов.

О В Express можно строить R E ST API, используя команды H T T P для определения


марш рутов.

О М арш руты Express могут отвечать данны м и JS O N , H T M L и ли в других ф о р ­


матах данных.

О Express содержит простой A PI ш аблонизации с поддерж кой многих разны х ядер.


7 Шаблонизация
веб-приложений

В главах 3 и 6 рассм атр и вал и сь основы и сп о льзо в ан и я ш аблонов д л я создан ия


п р ед став л ен и й в п р и л о ж е н и я х E xpress. В этой главе, полностью посвящ ен н ой
ш аблонам, мы рассм отрим три поп улярн ы х ш аблонизатора, а такж е узнаем, как
ш аблоны помогают «очистить» код веб-прилож ений за счет отделения программной
л огики от разм етки визуализации.

Е сли вы знаком ы с ш аблонизацией и паттерном M VC (M odel-V iew -C ontroller —


М одель-П редставление-К онтроллер), мож ете сразу переходить к разделу 7.2, с ко­
торого начинается подробное рассм отрение ш аблонизаторов, вклю чая EJS, H ogan
и Pug. Если ж е ш аблони зация вам в новинку, продолж айте читать — в следую щ их
нескольких разделах излож ены концептуальны е основы работы с ш аблонами.

7.1. Поддержка чистоты кода путем


шаблонизации
С паттерном M VC м ож но разрабаты вать традиционны е п ри лож ения как на базе
N ode, так и практи чески лю бой другой веб-технологии. О дна из клю чевы х к о н ­
цепций M VC заклю чается в разделен ии логики, данны х и представления. В M V C -
п р и л о ж е н и я х п о л ь зо в а т е л ь о б ы чн о за п р а ш и в а е т н у ж н ы й р ес у р с на сервере,
затем контроллер (c o n tro lle r) запраш ивает данны е п ри лож ен и я у модели (m odel)
и передает их данны е представлению (view ), которое осущ ествляет окончательное
ф орм атирован ие данны х д ля конечного пользователя. M V C -представления часто
реал и зу ю тся с пом ощ ью одного из я зы к о в ш аб л он и зац и и . Е сли в п ри лож ен и и
и сп о льзу ется ш аб лон и зац и я, п р ед ставлени е передает шаблонизатору (tem p la te
engine) значения, возвращ енны е моделью , и указы вает ф айл ш аблона, опред еля­
ю щ ий способ отображ ения этих значений.

Н а рис. 7.1 показано, как логика ш аблонизации вписы вается в общую архитектуру
M V C -прилож ения. Ф ай л ы ш аблонов обычно содерж ат заполни тели для значений
Рис. 7.1. Последовательность операций при работе МѴС-приложения и его взаимодействие с уровнем шаблона
7.1. Поддержка чистоты кода путем шаблонизации 203

прилож ений, а такж е ф рагм енты H T M L -, CSS- и иногда клиентского J a v a S c rip t-


кода, п р ед н азн ач ен н о го д л я р е а л и за ц и и д и н ам и ч еского п о в ед ен и я (в ы во д а на
экран сто р о н н и х видж етов вроде кн о п к и Like в F aceb o o k ) и ли д л я вкл ю чен и я
специального реж и м а работы ин терф ейса (наприм ер, скры тия или показа частей
страницы ). А так как ф айлы ш аблонов больш е связаны с уровнем представления,
чем с програм м ной логикой, разработчикам кли ен тских и серверны х прилож ений
эти ф айлы помогаю т организовать разделение труда.

В этом разделе мы займ ем ся в и зу ал и зац и ей разм етки H T M L , вы полняем ой как


с помощ ью ш аблонов, так и без них, чтобы вы смогли почувствовать разницу. О д ­
нако сначала давайте разберем практический прим ер исп ользован ия ш аблонов.

7.1.1. Шаблонизация в действии


В качестве простого пр и м ер а п р и м ен ен и я ш аблонов мы рассм отри м проблем у
элегантного H T M L -вы вода из простого п р и л о ж ен и я д л я блога. К аж д ая запись
блога вклю чает в себя заголовок, дату создания и текст. В окне браузера блог будет
вы глядеть так, как показано на рис. 7.2.

I am getting old, but thankfully I'm not in ja il!

Movies are pretty good

Гѵе been watching a lot o f movies lately. It's relaxing,


except when they have clowns in them.

Рис. 7.2. Пример вывода приложения блога в браузере

Запи си блога читаю тся из текстового файла, отформатированного так, как фрагмент
из ф айла entries.txt (листинг 7.1). С им волы — показываю т, где заканчивается одна
запись и начинается другая.

Листинг 7.1. Текстовый файл с записями в блоге


t i t l e : I t 's my birthday!
date: January 12, 2016
I am g ettin g old, but thankfully I'm not in J a il!

t i t l e : Movies are p re tty good


date: January 2, 2016
I'v e been watching a lo t of movies la te ly . I t 's relaxing,
except when they have clowns in them.
204 Глава 7. Шаблонизация веб-приложений

К од пр и ло ж ен и я в ф айле blog.js начинается с вклю чени я необходим ы х модулей


и ч тен и я записей блога, как показано в листинге 7.2.

Листинг 7.2. Логика разбора записей блога из текстового файла


const f s = r e q u i r e ( 'f s ') ;
const h ttp = r e q u ir e ( 'h ttp ') ;
function g etE n tries() { -<------ Функция для чтения и разбора текста записи. Читает данные
const e n trie s = []; записи из файла.
le t entriesRaw = f s .re a d F ile S y n c ('./e n trie s .tx t 'u tf 8 ') ;
entriesRaw = e n trie s R a w .s p lit('— ') ; -<-------- Разбирает текст на отдельные записи блога.
entriesRaw.map((entryRaw) => {
const entry = {};
const lin e s = e n try R a w .s p lit('\n '); Разбирает текст записи на строки.
lin es.m ap ((lin e) => { < -------- Разбирает строки на свойства entry
i f (lin e .in d e x O f ( 'title : ') === 0) {
e n t r y .t i tl e = li n e .r e p l a c e ( 't it l e : '') ;
else i f (lin e.in d ex O f('d ate: ') === 0) {
en try .d ate = lin e .re p la c e ('d a te : ', ' ' ) ;
else {
entry.body = entry.body || ' ' ;
entry.body += lin e;

});
en trie s.p u sh (e n try );
});
return e n trie s ;
}
const e n trie s = g e tE n trie s();
co n so le .lo g (e n trie s);

С ледую щ и й код, д о б ав л ен н ы й в при ло ж ен и е, о п р ед ел яет сервер H T T P . Когда


сервер получает зап рос H T T P, он возвращ ает страницу, содерж ащ ую все запи си
блога. Э та стран ица ви зу ал и зи р у ется ф ун кц и ей blogPage, которую мы определим
чуть позже:

const server = h ttp .c re a te S e rv e r((re q , res) => {


const output = blogPage(entries);
res.writeHead(200, {'Content-Type': 'te x t/h tm l'} );
res.end(output);
});
se rv e r.liste n (8 0 0 0 );

Теперь нуж но определить ф ункцию blogPage, которая преобразует запи си блога


в H T M L -страницу, отправляемую браузеру пользователя. Д л я этого мы используем
два подхода:

О в и зу ал и зац и я H T M L -кода без ш аблона;

О в и зу ал и зац и я H T M L -кода с помощ ью ш аблона.

С начала рассм отрим визуализац ию H T M L -кода без шаблона.


7.1. Поддержка чистоты кода путем шаблонизации 205

7.1.2. Визуализация HTML без шаблона


П рилож ение д ля блога могло бы непосредственно вы водить H T M L -код на экран,
но см еш ивание H T M L -разм етки с програм м ной логикой п ри лож ения привело бы
к хаосу. В листинге 7.3 ф у н кц и я blogPage дем онстрирует вы вод на экран записей
блога без помощ и ш аблона.

Листинг 7.3 Шаблонизатор отделяет подробности визуализации


от программной логики
function blogPage(entries) {
le t output = '
<html>
<head>
<style type="text/css">
.e n tr y _ title { font-w eight: bold; }
.entry_date { fo n t-s ty le : i t a l i c ; }
.entry_body { margin-bottom: 1em; }
</style>
</head>
<body>
;
entries.m ap(entry => {
output += ' < -------- Разметка HTMLслишком сильно смешивается с программной логикой.
<div class= "en try _ title"> $ {en try .title}< /d iv >
<div class="entry_date">${entry.date}</div>
<div class="entry_body">${entry.body}</div>

} )/
output += '</body></html>';
return output;
}

О б р ати те в н и м ан и е: весь кон тен т, о т н о с я щ и й с я к о ф о р м л ен и ю вы вода, C S S -


о п р е д е л ен и я и H T M L -р азм етк а зн а ч и те л ьн о у вел и ч и ваю т ч и сл о стр о к в коде
прилож ения.

Визуализация HTML с помощью шаблона


В и зуали зац и я разм етки H T M L с прим енением ш аблона позволяет убрать из п р и ­
кладной л огики H T M L -разметку, что делает код п ри лож ения сущ ественно чище.

Д л я тестирования примеров, приведенны х в этом разделе, нуж но установить в папку


прилож ения модуль для работы с EJS (E m bedded Jav aS crip t). Д л я этого в командой
строке введите следую щ ую команду:

npm in s t a l l ejs

С ледую щ ий ф рагм ент загруж ает ш аблон из ф айла, а затем определяет новую вер­
сию ф ун кц и и blogPage, которая теперь использует ш аблонизатор EJS (о нем мы
поговорим в разделе 7.2):
206 Глава 7. Шаблонизация веб-приложений

const f s = r e q u i r e ( 'f s ') ;


const e js = r e q u ir e ( 'e js ') ;
const template = fs.re a d F ile S y n c ('./te m p la te ss/b lo g _ p a g e .e js', 'u t f 8 ') ;
function blogPage(entries) {
const values = { e n trie s };
return ejs.ren d er(tem p late, values);
}

П олны й листинг находится в архиве кода, прилагаем ом к книге, в каталоге ch07-


templates/listing7_4/. Ф а й л E JS -ш аблона содерж ит H T M L -разм етку (отделенную
от п р и кл ад н о й л о ги ки ), а такж е зап о л н и тел и , которы е указы ваю т, где долж ны
располагаться переданны е ш аблонизатору данные. В листинге 7.4 приведен ф айл
E J S -ш аблона, п р и м ен я ем ы й д л я вы вода зап и сей блога на экран, вклю чаю щ и й
H T M L -разм етку и заполнители.

Листинг 7.4. Шаблон EJS для вывода записей блога


<html>
<head>
<style type="text/css">
.e n tr y _ title { font-w eight: bold; }
.entry_date { fo n t-s ty le : i t a l i c ; }
.entry_body { margin-bottom: 1em; }
</style>
</head>
<body>
<% entries.m ap(entry => { %> < -------- Заполнитель, перебирающий записи блога
<div class="entry_title"><%= e n tr y .ti t l e %></div> ■<-
<div class="entry_date"><%= en try .d ate %></div> Заполнители для фрагментов
<div class="entry_body"><%= entry.body %></div> данных в каждой записи.
<% }); %>
</body>
</html>

В модулях, разработанны х сообщ еством Node, такж е реализованы ш аблонизаторы,


причем достаточно разнообразны е. Е сли вы полагаете, что H T M L - и (и л и ) C S S -
р азм етк е не х ватает элегантности, так как H T M L требует закр ы ваю щ и х тегов,
а CSS — откры ваю щ их и закры ваю щ их скобок, обратите вним ание на ш аб лони за­
торы. О ни позволяю т ф айлам ш аблонов использовать специальны е язы к и (такие,
как язы к Pug, о котором мы поговорим в этой главе позж е), предлагаю щ ие способы
сокращ енной запи си H T M L и (и л и ) CSS.

Х отя ш аб лони заторы м огут сделать ш аблоны более «чисты м и», возм ож но, вам
не захочется трати ть врем я на изучени е альтерн ати вны х способов зап и си H T M L
и CSS. В конце концов, о кон чательное реш ен ие зави си т от ваш их л и ч н ы х п р ед ­
почтений.

В оставш ейся части главы мы поговорим о том, как встроить ш аблонизацию в ваши
N o d e-прилож ения, на прим ере трех популярны х ш аблонизаторов:
7.2. Шаблонизация с EJS 207

О ш аблонизатор EJS (E m b ed d ed Jav aS crip t);

О м ин им алистский ш аблонизатор Hogan;

О ш аблонизатор Pug.

К аж ды й из этих ш аблонизаторов предлагает альтернативны й способ запи си р а з­


м етки H T M L . Н ачнем с EJS.

7.2. Шаблонизация с EJS


EJS (E m bedded JavaScript, https://github.com/visionmedia/ejs) предлагает достаточно
прям олинейны й подход к ш аблонизации, которы й будет близок разработчикам, ис­
пользую щ им ш аблоны в других язы ках, таких как J S P (Jav a Server Pages), Sm arty
(P H P ), ERB (E m bedded R uby) и подобных. Теги EJS внедряю тся в разметку H T M L -
кода в качестве заполни телей д ля данны х. К роме того, EJS позволяет вы полнять
в ш аблонах простейш ую логику Ja v a S c rip t д ля таких операций, как условное вет­
вление и итерация, как это делается в PH P.

В этом разделе вы узнаете:

О как создавать E JS -ш аблоны;

О как и сп о льзо вать E JS -ф ильтры д ля поддерж ки обы чной ф ун кц и он ал ьн ости


представлений, такой как м анип улирование текстом, сортировка и итерация;

О как интегрировать EJS с п р и лож ениям и Node;

О как использовать EJS д ля кли ен тских прилож ений.

А теперь давайте глубж е погрузим ся в м ир ш аблонов EJS.

7.2.1. Создание шаблона


В м и р е ш аб л о н и зац и и данны е, отсы лаем ы е ш аб л о н и зато р у д л я ви зуал и зац и и ,
иногда назы ваю т контекстом (co n tex t). Вот пример использования EJS в N ode для
визуал и зац и и простого ш аблона с использованием контекста:

const e js = r e q u ir e ( 'e js ') ;


const template = '<%= message %>';
const context = { message: 'Hello tem plate!' };
co nso le.lo g (ejs.ren d er(tem p late, context));

О б рати те в н и м ан и е на и сп о л ьзо в ан и е l o c a l s во втором аргум енте, п ер ед авае­


мом ren d er. В торой аргум ент м ож ет вклю чать парам етры визуализации, а такж е
кон тек стн ы е данны е. П ар ам етр l o c a l s гарантирует, что отдельн ы е ф рагм енты
кон текстн ы х дан ны х не будут и н тер п р ети р о ваться как E JS -парам етры . О днако
208 Глава 7. Шаблонизация веб-приложений

в больш инстве случаев в качестве второго парам етра мож но передавать сам к о н ­
текст, как в следую щ ем вы зове render:

co n so le.lo g (ejs.ren d er(tem p late, context));

Е сли вы передаете кон текст E JS -ш аблону неп осредствен но во втором аргум енте
вы зова render, убедитесь, что при и м енован ии кон текстн ы х зн ач ен и й не и с п о л ь­
зу ю тся следую щ и е клю чевы е слова: cache, c l i e n t , c lo s e , com pileD ebug, debug,
file n a m e , open и л и sco p e. О н и за р е зе р в и р о в ан ы с целью и зм е н ен и я н астроек
ш аблонизатора.

Экранирование символов

В проц ессе в и зу а л и зац и и E JS экранирует (escap es) все сп ец и альн ы е сим волы
в кон текстн ы х значениях, зам ен яя их кодам и H T M L -сущ ностей. Это позволяет
предотвратить атаки м еж сайтового вы п олн ен и я сценариев (C ro ss-S ite Scripting,
X SS), в ходе которы х зл о у м ы ш л ен н и к и пы таю тся в качестве дан ны х отправить
вредоносны й код J a v a S c rip t в надежде, что этот код будет вы полнен при выводе
данны х на экран в браузере другого пользователя. С ледую щ ий ф рагм ент д ем он ­
стрирует, как работает м еханизм экранировани я сим волов в EJS:

var e js = r e q u ir e ( 'e js ') ;


var template = '<%= message %>';
var context = {message: "<script>alert('X SS a tta c k !');< /s c rip t> " } ;
co n so le.lo g (ejs.ren d er(tem p late, context));

В результате вы полнения этого кода на экране появится следую щ ий текст:

& lt;script& gt;alert('X S S a tta c k !');& lt;/s c rip t& g t;

Е сли вы д оверяете исп ользуем ы м в ш аблоне данны м и не хотите экранировать


контекстны е значения в EJS, используйте в теге ш аблона сим волы <%- вместо <%=,
как в следую щ ем примере:

const e js = r e q u ir e ( 'e js ') ;


const template = '<%- message %>';
const context = {
message: "< scrip t> alert('T ru sted J a v a S c rip t!');< /sc rip t> "
};
co n so le.lo g (ejs.ren d er(tem p late, context));

Е сли вам не нравятся символы , используем ы е в E JS -ш аблоне д ля специф икации


тегов, вы мож ете легко их изменить:

const e js = r e q u ir e ( 'e js ') ;


e js .d e lim ite r = '$ '
const template = '<$= message $>';
const context = { message: 'Hello tem plate!' };
co n so le.lo g (ejs.ren d er(tem p late, context));
7.2. Шаблонизация с EJS 209

И так, вы ознако м и л и сь с основам и EJS; давай те посм отрим , как EJS позволяет
упростить управление представлением данных.

7.2.2. Интеграция шаблонов EJS в приложение


Х ранить ш аблоны в ф ай л ах вм есте с кодом пр и лож ен и я неудобно, вдобавок это
приводит к загром ож дению кода. М ы воспользуем ся A P I ф айловой системы N ode
д ля загрузки ш аблонов из отдельны х файлов.

П ерейдите в рабочую папку и создайте ф айл app.js с кодом из листинга 7.5.

Листинг 7.5. Хранение шаблонного кода в файлах


const e js = r e q u ir e ( 'e js ') ;
const fs = r e q u i r e ( 'f s ') ;
const h ttp = r e q u ir e ( 'h ttp ') ;
const filename = './te m p la te s /s tu d e n ts .e js '; < ---- Обозначает местонахождение файла шаблона.
const students = [ ■<-------- Данные для передачи шаблонизатора.
{ name: 'Rick LaRue', age: 23 },
{ name: 'Sarah C athands', age: 25 },
{ name: 'Bob Dobbs', age: 37 }
];
const server = h ttp .c re a te S e rv e r((re q , res) => { ■<-------- Создает сервер HTTP.
i f (re q .u rl === ' / ' ) {
fs.read F ile(filen am e, (e rr, data) => { ■<-------- Читает шаблон из файла.
const template = d a ta .to S trin g ();
const context = { students: students };
const output = ejs.ren d er(tem p late, context); <.----- Выполняет визуализацию шаблона.
res.setH ead er('C o n ten t-ty p e', 'te x t/h tm l') ;
res.end (o u tp u t); -<-------- Отправляет ответ HTTP.
});
} else {
res.statusC ode = 404;
res.end('N ot found');
}
});
serv e r.liste n (8 0 0 0 );

Затем создайте дочерний каталог с им енем templates. В этом каталоге будут х р а­


ни ться ш аблоны. С оздайте ф айл students.ejs в каталоге templates. В клю чите в ф айл
tem plates/students.ejs код из листинга 7.6.

Листинг 7.6. Шаблон EJS для построения массива


<% i f (stu d en ts.len g th ) { %>
<ul>
<% students.forE ach((student) => { %>
<li><%= student.name %> (<%= student.age %>)</li>
<% }) %>
</ul>
<% } %>
210 Глава 7. Шаблонизация веб-приложений

Кэширование шаблонов EJS


EJS поддерж ивает кэш и р о ван и е ф у н к ц и й ш аблона в пам яти. Э то означает, что
после однократного разбора ф айла ш аблона созданная в результате ф ункц ия сохра­
няется в пам яти. В изуали зация кэш ированного ш аблона осущ ествляется быстрее,
поскольку этап разбора пропускается.
Е сл и вы зан и м аетесь р азр аб о тк о й в еб -п р и л о ж ен и я в N ode и хотите, чтобы и з ­
м енени я, внесенн ы е в ф ай л ы ш аблона, нем едлен но о траж ал и сь в при лож ении ,
кэш ирование следует отклю чить. Если ж е вы уж е развернули прилож ение в р або­
чей среде, кэш ирование позволяет быстро и просто получить ощ утим ы й вы игры ш
в производительности. У словное вклю чение кэш и рования осущ ествляется через
перем енную окруж ения NODE_ENV.

Ч тобы проверить, как работает кэш ирование, изм ените вызов E JS-ф ункц ии render
из преды дущ его примера:

const cache = process.env.NODE_ENV === 'p ro d u ctio n ';


const output = e js.re n d e r(
tem plate,
{ students, cache, filename }
);

О братите вним ание на то, что в параметре filenam e не обязательно указы вать файл.
В данном случае мож ет при м ен яться уникальное значение, определяю щ ее, какой
ш аблон вы визуализируете.

Теперь, когда вы знаете, как интегрировать ш аблоны EJS в N o d e-прилож ение, д а ­


вайте рассм отрим другую возм ож ность прим енения EJS в браузерах.

7.2.3. Использование EJS в клиентских приложениях


Ч тобы воспользоваться EJS на стороне клиента, сначала нуж но загрузить ш абло-
низатор EJS в рабочий каталог с помощ ью следую щ их команд:

cd /your/w orking/directory
curl -O h ttp s ://ra w .g ith u b u s e rc o n te n t.c o m /tj/e js /m a ste r/lib /e js.js

П осле загр у зки ф ай л а ejs.js EJS м ож но исп ользовать в кли ен тском коде. В л и с ­
тинге 7.7 представлено простое клиентское прилож ение с ш аблоном EJS. Сохранив
код в ф айле с именем index.html, вы смож ете откры ть его в браузере и просмотреть
результаты.

Листинг 7.7. Добавление шаблонизации на стороне клиента средствами EJS


<html>
<head>
<title>EJS exam ple</title>
< script src= "ejs.js"> < /scrip t>
7.3. Использование языка Mustache с шаблонизатором Hogan 211

< script < -------- Включает библиотеку jQueryдля операций с DOM.


src="h ttp ://a ja x .g o o g le a p is.c o m /a ja x /lib s/jq u ery /1 .8 /jq u e ry .js">
</script>
</head>
<body>
<div id="output"></div> -<-------- Заполнитель для результатов визуализации шаблона.
<script>
const template = "<%= message %>"; ■<----- Шаблон, используемый для визуализации контента.
const context = { message: 'Hello tem plate!' }; ■<-------- Данные для шаблона.
$(document).ready(() => { -<-------- Ожидает загрузки страницы в браузере.
$ ('# o u tp u t').h tm l(
ejs.ren d er(tem p late, context) -< і Выполняет визуализацию шаблона
); в div с идентификатором «output».
});
< /script>
</body>
</html>

Теперь вы знаете, как использовать в N ode п олноф ункци ональны й ш аблонизатор.


П риш ло врем я познаком иться с ш аблонизатором H ogan, в котором ф ун кц и он ал ь­
ность, доступная в коде ш аблона, нам еренно ограничена.

7.3. Использование языка Mustache


с шаблонизатором Hogan
Ш аблонизатор hogan.js (https://github.com /twitter/hoganjs) был создан в компании
T w itter д ля собственны х потребностей в работе с ш аблонами. H ogan п ред ставл я­
ет собой р еализаци ю поп улярн ого стандарта я зы к а ш аблонов M u stach e (h ttp ://
mustache.github.com/), разработанного К рисом В анстратом (C h ris W a n stra th ) для
проекта G itH ub.

В M ustache выбран минималистский подход к ш аблонизации. В отличие от EJS стан­


дарт M ustache сознательно не вклю чает ни условную логику, ни встроенные средства
фильтрации контента (кроме экранирования контента для предотвращения XSS-атак).
Разработчики M ustache постарались сделать код ш аблона максимально простым.

В этом разделе мы узнаем:

О как создать и реализовать в при лож ении ш аблоны M ustache;

О какие теги ш аблонов определены стандартом M ustache;

О как организовать ш аблоны с помощ ью «компонентов»;

О как вы полнить точную настройку H ogan с помощ ью нестандартны х ограничи­


телей и других параметров.

И так, давайте взглянем на альтернативны й подход к ш аблонизации, которы й пред­


лагает H ogan.
212 Глава 7. Шаблонизация веб-приложений

7.3.1. Создание шаблона


Ч то бы исп о льзо вать H o g an в п р и ло ж ен и и и л и вы п ол н и ть лю бой из прим еров,
пр ед л агаем ы х в этом разделе, нуж но устан ови ть H ogan в катал ог п р и л о ж ен и я
(ch07-tem plates/hogan-snippet). Д л я этого в ком ан дной строке введи те следую щ ую
команду:

npm i --save hogan.js

Д алее представлен простейш ий прим ер исп ользован ия H ogan в при лож ении N ode
д л я ви зу ал и зац и и простого ш аблона на основе контекста. П ри его вы полнен ии
вы водится текст «H ello tem plate!».

const hogan = re q u ire ('h o g a n .js ');


const templateSource = '{{message}}';
const context = { message: 'Hello tem plate!' };
const template = hogan.compile(templateSource);
co n sole.log(tem plate.render(context));

Теперь, когда вы знаете, как с помощ ью H ogan обрабаты вать ш аблоны M ustache,
перейдем к рассм отрению тегов, предлагаем ы х стандартом M ustache.

7.3.2. Теги Mustache


К онцептуально теги M ustache не отличаю тся от тегов EJS. Теги M ustache р езер ­
вирую т м есто д л я зн ач ен и й перем енны х, а такж е указы ваю т на необходим ость
перебора, что п о зво л яет р асш и р и ть ф у н кц и о н ал ьн о сть M u sta ch e и ли добавить
в ш аблон ком м ентарий.

Вывод простых значений


Ч тобы вы вести контекстное значение в ш аблоне M ustache, заклю чите им я зн ач е­
ни я в двойны е ф игурны е скобки. П одобные скобки среди M ustache-разработчиков
п о л у ч и л и ж аргонн ое н азван ие « m u staches» (д осл овн о — усы ). Если, наприм ер,
нуж но вы вести значение д л я контекстного элем ента name, воспользуйтесь тегом
H ogan {{name}}.

П одобно больш инству других ш аблонизаторов, H ogan по ум олчанию экранирует


контент, чтобы предотвратить возм ож ны е X SS-атаки. Д л я вы вода неэкранирован-
ного значения в H ogan м ож но либо добавить третью ф игурную скобку, либо пред­
варить контекстны й элемент сим волом &. Так, в предыдущ ем прим ере д ля вывода
неэкранированного контекстного зн ач ен и я будет и сп ользоваться тег {{{name}}}
и ли {{&name}}.

Д л я д обавления в ш аблон M ustache ком м ентария при м ен яется ф орм ат вида

{{! This is a comment }}


7.3. Использование языка Mustache с шаблонизатором Hogan 213

Секции — перебор нескольких значений


Х отя H ogan не позволяет вклю чать логику в ш аблоны, этот ш аблонизатор предла­
гает элегантны й механизм перебора нескольких значений в контекстном элементе
с помощ ью секций (sectio n s). Н априм ер, следую щ ий контекст содерж ит элемент
с массивом значений:

var context = {
students: [
{ name: 'Jane Narwhal', age: 21 },
{ name: 'Rick LaRue', age: 26 }
]
};

Д опустим , вы реш или создать ш аблон, которы й вы водит на экран данны е каждого
студента в отдельном H T M L -абзаце:

<p>Name: Jane Narwhal, Age: 21 years old</p>


<p>Name: Rick LaRue, Age: 26 years old</p>

С ш аблонам и H ogan эта задача реш ается тривиально:

{{#students}}
<p>Name: {{name}}, Age: {{age}} years old</p>
{{/students}}

Инвертированные секции — HTML по умолчанию


при отсутствии значений
Что произойдет, если элемент stu d e n ts в контекстных данных не является массивом?
Если, например, это один объект, то ш аблон просто вы ведет его. О днако секции не
отображ аю тся, если значение соответствую щ его элем ента не определено, лож но
или представляет собой пустой массив.

Ч тобы ш аблон вы водил на экран сообщ ение об отсутствии зн ач ен и я в какой-то


секции, H ogan пред о ставл яет то, что в тер м и н ол оги и M u sta ch e н азы вается и н ­
вертированными секциями (in v e rte d sections). С ледую щ ий код после вклю чения
в описанны й ранее ш аблон с данны м и студентов выведет на экран сообщ ение о том,
что в контексте отсутствую т данны е о студенте:
{{Astudents}}
<p>No students found.</p>
{{/students}}

Лямбда-секция — включение в секции нестандартной


функциональности
Ч тоб ы р а зр а б о т ч и к и м о гл и р а с ш и р я ть ф у н к ц и о н ал ьн о сть M u stach e, стан дарт
допускает определен ие тегов секций, обрабаты ваю щ их кон тен т ш аблона путем
214 Глава 7. Шаблонизация веб-приложений

вы зова ф ун кц и й , а не перебором элем ентов м ассива. Такие секц ии назы ваю тся
лямбда-секциями (sectio n lam bda).
В л и сти н ге 7.8 при веден при м ер того, как с пом ощ ью л я м б д а-сек ц и и д обавить
поддерж ку парсера M arkdow n в м еханизм визуализац ии ш аблона. В этом примере
используется модуль github-flavored-m arkdow n, которы й нуж но установить, введя
в ком андной строке команду:
npm in s t a l l github-flavored-markdown --dev

Е сли вы используете архив исходного кода книги, вы полните команду npm i n s t a l l


из каталога ch07-templates/listing7_8.

В л и сти н ге 7.8 к о н с тр у к ц и я **Name** в ш аб л он е в и зу а л и з и р у е т с я в разм етк у


<strong>N am e</strong> при передаче через парсер M arkdow n, которы й вы зы вается
кодом лям бда-секции.

Листинг 7.8. Использование лямбда-секции в Hogan


const hogan = re q u ire ('h o g a n .js ');
const md = require('github-flavored-m arkdow n'); < -------- Включает парсер Markdown.
const templateSource = '
{{#markdown}}**Name**: {{name}}{{/markdown}} Шаблон Mustache также содержит
форматирование Markdown.
const context = {
name: 'Rick LaRue',
markdown: () => te x t => m d.parse(text) Контекст шаблона включает
}; лямбда-секциюдля разбора
const template = hogan.compile(templateSource); разметки Markdown в шаблоне.
co n sole.log(tem plate.render(context));

С помощ ью лям бда-секций вы смож ете легко реализовать в своих ш аблонах такие
вещи, как кэш ирование и преобразования.

Компоненты — использование шаблонов в других шаблонах


П ри н ап и сан и и ш аблонов м ож но избеж ать неж елательного п овторен и я одного
и того ж е кода в нескольких ш аблонах. О дин из способов добиться этого — создать
компоненты (p artials). К ом поненты — это ш аблоны, которы е в качестве строитель­
ны х блоков могут вклю чаться в другие ш аблоны. Ещ е один вариант прим енения
ком понентов — разбиение слож ны х ш аблонов на более простые.

Н априм ер, в листинге 7.9 с помощ ью ком понента код ш аблона, прим еняем ы й для
вы вода на экран данны х о студентах, вы деляется из главного шаблона.

Листинг 7.9. Использование компонентов в Hogan


const hogan = re q u ire ('h o g a n .js ');
const studentTemplate = ' < -------- Шаблонный код, используемый для компонента.
<p>
Name: {{name}},
7.4. Шаблоны Pug 215

Age: {{age}} years old


</p>
;
const mainTemplate = ' < -------- Код основного шаблона.
{{#students}}
{{>student}}
{{/students}}
;
const context = {
students: [{
name: 'Jane Narwhal',
age: 21
}, {
name: 'Rick LaRue',
age: 26
}]
}; Компиляция основного
const template = hogan.compile(mainTemplate); •<— ичастичного шаблона.
const p a r tia l = hogan.compile(studentTemplate);
const html = tem plate.render(context, {student: p a r tia l }); ■<- Визуализация основного
console.log(htm l); и частичного шаблона.

7.3.3. Тонкая настройка Hogan


И сп о л ьзо вать H o g an очень просто — д остаточно и зучить набор тегов, и м ож но
приступать к работе. Возможно, в процессе работы вам потребуется изменить всего
несколько параметров.

Е сли вам не н р авятся ф игурны е скобки в стиле M ustache, достаточно переопреде­


лить используем ы й разделитель путем передачи методу com pile нуж ного разд ел и ­
теля в качестве параметра. В следующем примере показано, как в H ogan выполнить
ком п иляцию с разделителем в стиле EJS:

hogan.com pile(text, { d elim iters: '<% %>' });

Кроме M ustache, существуют и другие язы ки шаблонов. Одним из проектов, который


старается по возм ож ности избавить разработчика от рутины H TM L, явл яется Pug.

7.4. Шаблоны Pug


Ш аблони затор P u g (http://pugjs.org), ранее известны й под названием Jad e, предо­
ставляет альтернативный механизм определения H TM L. Ключевое различие между
P ug и больш инством других систем ш аблонизации заклю чается в том, что в Pug
при м ен яю тся содерж ательны е пробельны е сим волы (w hitespace). П ри создании
ш аблонов P ug задаю тся отступы, определяю щие уровень влож енности тегов HTM L.
К ром е того, теги H T M L не обязательно явно закры вать, что позволяет исклю чить
проблему случайной преж девременной вставки закры ваю щ их тегов или их полного
216 Глава 7. Шаблонизация веб-приложений

отсутствия. Благодаря отступам ш аблоны становятся визуально более свободными,


что облегчает их сопровож дение.

Ч тобы вы лучш е поняли, как это работает, посмотрим, как представляется следу­
ю щ ий ф рагм ент H TM L:

<html>
<head>
<title>W elcome</title>
</head>
<body>
<div id="main" class="content">
<strong>"Hello world!"</strong>
</div>
</body>
</html>

Д л я моделирования этой разм етки H T M L м ож но задействовать такой ш аблон Pug:

html
head
t i t l e Welcome
body
div.content#main
strong "Hello world!"

В Pug, как и в EJS, допускается внедрение кода Ja v a S c rip t как на стороне сервера,
так и на стороне клиента. P u g предлагает такж е дополнительны е механизмы , т а ­
кие как наследование ш аблонов и прим еси (m ixins). Б л агодаря прим есям можно
определять просты е м ногократно и сп ользуем ы е м ин и-ш аблоны , п ред н азн ач ен ­
ны е д ля представления в разм етке H T M L наиболее востребованны х визуальны х
элементов, таких как списки и поля. П рим еси (m ixins) напоминаю т прим еняем ы е
в H ogan.js ком п оненты , у п о м и н ав ш и еся в преды дущ ем разделе. Н асл ед ован и е
ш аблонов упрощ ает организацию ш аблонов Pug, необходимы х д ля визуализац ии
одной страницы H T M L в нескольких ф айлах. Д алее мы подробно рассм отрим эти
механизмы . Ч тобы установить P u g в папку N ode-прилож ения, в ком андной строке
введите следую щ ую команду:

npm in s ta l l pug --save

В этом разделе мы узнаем:

О основны е принципы Pug: как задаю тся им ена классов, атрибуты и блочное рас­
ш ирение;

О к а к д о бави ть пр о гр ам м н у ю л о ги к у в ш аб лон ы P u g с пом ощ ью встроен н ы х


клю чевы х слов;

О как организовать ш аблоны с прим енением наследования, блоков и примесей.

Д л я начала рассм отрим основы исп ользован ия и синтаксиса Pug.


7.4. Шаблоны Pug 217

7.4.1. Основные сведения о Pug


В P u g исп о льзу ю тся те ж е им ена тегов, что и в H T M L , но в P u g м ож но не п р и ­
м е н я т ь о ткр ы в аю щ и е и за к р ы в аю щ и е си м в о л ы < и >, а у р о вен ь в л о ж ен н о сти
тегов м ож ет вы р аж аться отступам и. Тег м ож ет вклю чать в себя один и ли больш е
C S S -классов, связан н ы х с ним посредством и н стр у к ц и и .< classnam e> . Э лемент
d iv, к котором у при м ен ены классы c o n te n t и s id e b a r, м ож ет быть представлен
следую щ им образом:

div .co n ten t.sid eb ar

И д ен ти ф и к ато р ы CSS назн ачаю тся тегам с пом ощ ью п р еф и к са #<ID>. Д л я н а ­


зн ач ен и я ид ен ти ф и катора CSS f e a tu r e d _ c o n te n t в преды дущ ем прим ере мож ет
использоваться следую щ ая кон струкция Pug:

div.content.sidebar#featured_content

Сокращенная запись тега div


П оскольку тег d iv очень часто используется в разм етке H T M L , в P ug предусм о­
трена сокращ енная ф орм а его записи. С ледую щ ий прим ер кода генерирует ту же
разм етку H T M L , что и предыдущ ий:

.content.sidebar#featured_content

Теперь, когда вы научились записы вать теги H T M L и соответствую щ ие им классы


CSS и идентиф икаторы , перейдем к назначению атрибутов тегов H TM L.

Запись атрибутов тегов


А трибуты тегов записы ваю тся в круглы х скобках и разделяю тся запяты м и. Ч т о ­
бы определить гиперссылку, которая откроется в другой вкладке, воспользуйтесь
следую щ ей записью Pug:

a (h re f= 'h ttp ://n o d e js .o r g ', ta rg e t= '_ b la n k ')

П оскольку перечисление атрибутов тегов в P ug может привести к появлению д ли н­


ны х строк, ш аблонизатор предлагает более гибкий подход. С ледую щ ая запись Pug
вполне корректн а и эквивалентна предыдущ ей:

a (h re f= 'h ttp ://n o d e js.o rg '


ta rg e t= '_ b la n k ')

М ожно такж е указы вать атрибуты, которые не требую т значений. С ледую щ ий п ри­
мер кода P u g дем онстрирует определение ф орм ы H T M L , которая вклю чает в себя
элемент s e l e c t вместе с предварительно вы бранны м параметром:

strong Select your fa v o rite food:


form
218 Глава 7. Шаблонизация веб-приложений

select
option(value='C heese') Cheese
option(value='T ofu', selected) Tofu

Запись контента тегов


В преды дущ ем ф рагм енте кода вы уж е вид ел и при м еры кон тен та тегов: S e le c t
your f a v o r i t e food после тега stro n g , Cheese после первого тега o p tio n и Tofu после
второго тега o p tio n .

Это стандартный, но не единственный способ определения контента тегов Pug. Хотя


подобны й стиль прекрасно подходит для записи небольш их ф рагм ентов контента,
если контента окаж ется много, это м ож ет привести к появлению ш аблонов P u g со
слиш ком дли нны м и строками. К ак видно из следую щ его примера, д ля предотвра­
щ ения подобных проблем при записи контента в P ug можно использовать символ | :

tex tarea
| This is some d efau lt te x t
| th a t the user should be
| provided with.

Е сли тег H T M L (такой, как s t y l e и ли s c r i p t ) приним ает только текст (то есть
вл о ж е н и е эл ем ен то в H T M L не д о п у с к а е т с я), си м вол ы | м ож н о оп устить, как
в следую щ ем примере:

sty le
h1 {
fo n t-siz e : 6em;
color: #9DFF0C;
}

Н аличие двух разны х способов вы раж ени я длинного и короткого контента тегов
упрощ ает создание элегантны х ш аблонов Pug. К роме того, в P ug поддерж ивается
альтернативны й м еханизм опи сан ия влож енности, которы й назы вается блочным
расширением (block expansion).

Упорядочение кода путем блочного расширения


В P u g влож енны е теги обычно вы деляю тся отступами, но иногда это мож ет п р и ­
вести к слиш ком больш ом у количеству пробельны х символов. Н апример, в этом
ш аблоне P u g с помощ ью отступов определяется простой список ссылок:

ul
li
a (h re f= 'h ttp ://n o d e js .o r g /') Node.js homepage
li
a (h re f= 'h ttp ://n p m js .o rg /') NPM homepage
li
a (h re f= 'h ttp ://n o d e b its .o r g /') Nodebits blog
7.4. Шаблоны Pug 219

P ug позволяет записать этот прим ер в более ком пактной ф орм е с использованием


блочного расш ирения. В случае блочного расш и рения д ля иллю страци и вл ож ен ­
ности после тега указы вается двоеточие. Следую щ ий код генерирует ту же разметку,
что и преды дущ ий, но вместо семи строк кода остается только четыре:

ul
l i : a (h re f= 'h ttp ://n o d e js .o r g /') Node.js homepage
l i : a (h re f= 'h ttp ://n p m js .o rg /') NPM homepage
l i : a (h re f= 'h ttp ://n o d e b its .o r g /') Nodebits blog

Теперь, когда мы узнали, как в P u g представляется разм етка, давайте выясним, как
интегрировать ш аблон P u g в веб-прилож ение.

Включение данных в шаблоны Pug


Д анны е передаю тся ш аблонам P u g так же, как и ш аблонам EJS. С начала ш аблон
ком п и л и р у ется в ф ункцию , ко торая затем вы зы вается вм есте с кон текстом для
визуал и зац и и вы вода H T M L . Пример:

const pug = re q u ire ('p u g ');


const template = 'stro n g #{message}';
const context = { message: 'Hello tem plate!' };
const fn = pug.compile(template);
conso le.lo g (fn (co n tex t));

В этом прим ере кода кон струкция #{message} в ш аблоне определяет заполнитель,
которы й зам ен яется контекстны м значением.

С помощ ью контекстны х значений можно такж е предоставлять значения для атри­


бутов. Н априм ер, следую щ ий ш аблон ви зуали зи рует разм етку <a h r e f = " h t t p : / /
google.com "></a>:

const pug = re q u ire ('p u g ');


const template = 'a (h re f = u r l ) ';
const context = { u rl: ' http://google.com ' };
const fn = pug.compile(template);
conso le.lo g (fn (co n tex t));

Теперь, когда мы узнали, как в P u g представляется разм етка H T M L и как данны е


п р и л о ж е н и й вкл ю ч аю тся в ш аб л о н ы P ug, п р и ш л о врем я узн ать, к ак встроить
в ш аблон P u g програм м ную логику.

7.4.2. Программная логика в шаблонах Pug


К огда вы передаете ш аблонам P u g д ан ны е п р и ло ж ен и я, вам требуется логика,
чтобы как-то обрабаты вать эти данные. P u g позволяет непосредственно внедрять
в ш аблоны строки кода Ja v a S c rip t — им енно с помощ ью Ja v a S c rip t вы определяете
програм м ную логику в своих ш аблонах. Ч ащ е всего использую тся ин струкции if ,
220 Глава 7. Шаблонизация веб-приложений

ци клы f o r и объ явл ен и я var. Н о преж де чем углубиться в детали, давайте рассм о­
трим прим ер ш аблона, визуализирую щ его список контактов. Э тот прим ер хорош о
иллю стрирует то, как логика P u g мож ет при м ен яться в прилож ении:

h3.contacts-header My Contacts
i f contacts.len g th
each contact in contacts
- var fullName = contact.firstN am e + ' ' + contact.lastName
.contact-box
p fullName
i f co n tact.isE d itab le
p: a (h re f= '/e d it/+ c o n ta c t.id ) Edit Record
p
case c o n ta c t.sta tu s
when 'A ctive'
strong User is activ e in the system
when 'In a c tiv e '
em User is inactive
when 'Pending'
| User has a pending in v ita tio n
else
p You curren tly do not have any contacts

С начала рассм отрим различны е способы обработки вы водим ы х данны х ш аблони-


затором P u g при внедрении кода Jav aS crip t.

Использование JavaScript-кода в шаблонах Pug


Е сли поставить перед строкой кода J a v a S c rip t преф икс -, код Ja v a S c rip t будет вы ­
полнен без возвращ ен ия какого-либо зн ач ен и я в вы водим ы х ш аблоном данных.
Если же использовать преф икс =, возвращ аемое этим кодом значение попадет в вы ­
водим ы е данные, причем оно будет экранировано д ля предотвращ ения X SS-атак.
Если ж е код, генерируем ы й из Jav aS crip t, экранировать не нужно, м ож но снабдить
его преф иксом !=. П еречень доступны х преф иксов приведен в табл. 7.1.

Таблица 7.1. Префиксы, используемые для встраивания JavaScript в Pug

Префикс Вывод
= Выходные данные экранируются (для не заслуживающих доверия
или непредсказуемых значений, для защиты от XSS-атак)
!= Выходные данные не экранируются (для заслуживающих доверия
или предсказуемых значений)

- Выходные данные отсутствуют

В P u g вклю чено несколько наиболее востребованн ы х условны х и итерати вны х


инструкций, которы е м ожно записы вать без префиксов: i f , e ls e , case, when, d e fa u lt,
u n t i l , w h ile, each и u n le ss.
7.4. Шаблоны Pug 221

К ром е того, в P u g м ож н о о п р ед ел ять перем енны е. В следую щ ем при м ере кода


показаны два эквивалентны х способа при сваи вания значений перем енны м в Pug:

- count = 0
count = 0

К ак и упом януты е ранее ин струкции с преф иксом -, ин струкции без преф иксов не
порож даю т никаких данных.

Перебор объектов и массивов


В P ug значения, переданны е в контексте, доступны д ля кода Jav aS crip t. В следую ­
щ ем прим ере кода мы считы ваем ш аблон P u g из ф айла, а затем передаем ш аблону
контекст с парой сообщ ений, которы е мы собираемся показы вать в массиве:

const pug = re q u ire ('p u g ');


const fs = r e q u i r e ( 'f s ') ;
const template = fs.re a d F ile S y n c ('./te m p la te .p u g ');
const context = { messages: [
'You have logged in s u c c e s s fu lly .',
'Welcome back!'
]};
const fn = pug.compile(template);
conso le.lo g (fn (co n tex t));

Ш аблон P u g содерж ит следую щ ий код:

- messages.forEach(message => {
p= message
- })

Ф и н ал ьн ая разм етка H T M L долж на вы глядеть следую щ им образом:

<p>You have logged in successfully.</p><p>Welcome back!</p>

В P ug такж е поддерж ивается отличная от Ja v a S crip t ф орм а итераций, реализуем ая


и н стр у к ц и ей each. С ее пом ощ ью м ож н о легко переб и рать эл ем енты м ассивов
и свойства объектов.

Вот как вы глядит эквивалент преды дущ его примера, в котором используется и н ­
струкци я each:

each message in messages


p= message

Н емного изм енив код, м ож но вы полнить перебор свойств объекта:

each value, key in post


div
strong #{key}
p value
222 Глава 7. Шаблонизация веб-приложений

Условная визуализация в шаблоне


И ногда ш аблон долж ен реш ить, как им енно долж ны вы водиться те или ины е д ан ­
ные, в зависим ости от значения этих данных. В следую щ ем примере кода показано
условие, в котором прим ерно в половине случаев тег s c r i p t вы водится в ф ормате
H TM L:

- n = Math.round(Math.random() * 1) + 1
- i f (n == 1) {
sc rip t
alert('Y o u w in !');
- }

Условные вы раж ени я P u g такж е м огут запи сы ваться в более чистой альтернатив­
ной форме:

- n = Math.round(Math.random() * 1) + 1
i f n == 1
sc rip t
alert('Y o u w in !');

Если в условиях использую тся отрицан ия (наприм ер, i f (n != 1)), мож но восполь­
зоваться клю чевы м словом u n le ss:

- n = Math.round(Math.random() * 1) + 1
unless n == 1
sc rip t
alert('Y o u w in !');

Инструкция case
В P u g такж е поддерж ивается условная кон струкция case, напом инаю щ ая sw itch:
она позволяет вы брать в ш аблоне нуж ны й вариант вы водим ы х данны х д ля одного
из нескольких возм ож ны х сценариев.

С л ед у ю щ и й п р и м ер ш аб л о н а д ем о н стр и р у ет и сп о л ьзо в ан и е и н стр у к ц и и c a se


д л я вы вода результатов пои ска в блоге тр ем я разн ы м и способами. Е сли в р езу л ь­
тате пои ска ничего не найдено, вы во д и тся соответствую щ ая и н ф орм аци я. Если
най дено еди н ствен н ое сообщ ение, оно п о к азы вается полностью . Е сли найдено
несколько сообщ ений, то и н стр у к ц и я each перебирает сообщ ения с вы водом их
заголовков:

case re su lts.le n g th
when 0
p No re s u lts found.
when 1
p= re su lts[0 ].c o n te n t
default
each re s u lt in re s u lts
p= r e s u l t . t i t l e
7.4. Шаблоны Pug 223

7.4.3. Организация шаблонов Pug


П осле определения ш аблонов их нуж но как-то организовать. Как и в случае с п р и ­
кладной логикой, нуж но стрем иться к ум еньш ению разм еров ф айлов ш аблонов.
П ри этом предполагается, что один ф айл ш аблона концептуально долж ен соответ­
ствовать одному структурном у элем енту прилож ения, наприм ер странице, врезке
или контенту сообщ ения в блоге.

В этом разделе мы рассм отрим несколько механизмов, с помощ ью которы х разны е


ф айлы ш аблонов д ля визуализац ии контента объединяю тся вместе:

О структурировани е ш аблонов путем наследования;

О реал и зац и я м акетов путем добавления блока в начало и в конец ш аблона;

О вклю чение ш аблона;

О м ногократное и сп о льзо ван и е п рограм м ной л оги ки ш аблона с помощ ью п р и ­


месей.

Н ачнем с наследования ш аблонов в Pug.

Структурирование нескольких шаблонов путем наследования

Н ас л ед о в а н и е ш аб л о н о в — один из м ех ан и зм о в с т р у к т у р и р о в а н и я . В р ам к ах
этой кон ц еп ц и и ш аблоны рассм атр и ваю тся по анал оги и с классам и в парадигм е
объектно-ориентирован ного програм м ировани я. О дин ш аблон м ож ет расш ирять
д р у го й ш аблон, к о то р ы й в свою о черед ь м о ж ет р а с ш и р я ть ещ е один ш аблон.
К оличество у ровн ей н асл едо ван и я огр ан и ч и вается лиш ь соображ ен и ям и ц е л е­
сообразности.

В качестве простого прим ера давайте вы ясним , как путем наследования ш аблонов
создать базовую H T M L -обертку д ля контента страницы. В рабочем каталоге соз­
дайте папку template, в которой будет находиться P u g -ф ай л примера. Д л я ш аблона
страницы создается ф айл layout.pug со следую щ ей разметкой:

html
head
block t i t l e
body
block content

Ш аб лон layout.pug содерж ит м и н и м ал ьн о е определен ие стран ицы H T M L в виде


двух блоков (b lo ck s). С пом ощ ью блоков п ри н асл едован и и ш аблонов зад ается
м есто, в ко то р о м будет н а х о д и т ь с я ко н тен т, п р е д о с т а в л я е м ы й п р о и зв о д н ы м
ш аблоном . В ф ай л е layout.pug д ля этого служ ат два блока: блок t i t l e позволяет
прои зводн ом у ш аблону задать заголовок, а блок c o n te n t — то, что долж но в ы в о ­
д и ться на странице.
224 Глава 7. Шаблонизация веб-приложений

Затем в папке ш аблона, находящ ейся в рабочем каталоге, создайте ф айл page.pug.
Э тот ф айл ш аблона заполняет блоки t i t l e и co n ten t:

extends layout
block t i t l e
t i t l e Messages
block content
each message in messages
p= message

Н аконец, добавьте програм м ную логику из листинга 7.10 (это м оди ф и цирован ная
версия преды дущ его прим ера данного раздела), которая вы водит результаты п р и ­
м енени я ш аблона и показы вает наследование в действии.

Листинг 7.10. Наследование шаблонов в действии


const pug = re q u ire ('p u g ');
const f s = r e q u i r e ( 'f s ') ;
const tem plateFile = './te m p la te s/p a g e .p u g ';
const iterTem plate = fs.readF ileS ync(tem plateF ile);
const context = { messages: [
'You have logged in s u c c e s s fu lly .',
'Welcome back!'
]};
const iterFn = pug.compile(
iterTem plate,
{ filename: tem plateFile }
);
c o n so le.lo g (iterF n (co n tex t));

А теперь давайте рассм отрим другое прим енение м еханизм а наследован ия ш абло­
нов: вставку блоков в начало и в конец шаблона.

Реализация макетов путем вставки блоков в начало


и в конец шаблона
В преды дущ ем при м ере блоки, определен ны е в ф ай л е layout.pug, не содерж али
контента, что делало задачу вклю чения контента в ш аблон page.pug элем ентарной.
Если же блок в унаследованном ш аблоне содержит контент, этот контент мож ет не
зам ен яться производны м и ш аблонами, а наращ иваться путем добавления блоков
в начало и в конец ш аблона. Это позволит вам определять самый обы чны й контент
и добавлять его в ш аблон без зам ены сущ ествую щ его контента.

С ледую щ ий шаблон, layout.pug, содержит дополнительны й блок s c r ip ts с контентом


(тегом s c r i p t , которы й предназначен для загрузки библиотеки jQ uery):

html
head
- const baseUrl = "h ttp ://a ja x .g o o g le a p is.c o m /a ja x /lib s/jq u ery u i/1 .8 /"
block t i t l e
block sty le
7.4. Шаблоны Pug 225

block s c rip ts
body
block content

Если вы хотите, чтобы ш аблон page.pug дополнительно загруж ал библиотеку jQuery,


мож ете воспользоваться ш аблоном из листинга 7.11.

Листинг 7.11. Использование механизма добавления блока в конец шаблона


для загрузки дополнительного JavaScript-файла
extends layout ■<-------- Расширяет шаблон layout.
block t i t l e
t i t l e Messages
block sty le < -------- Определяет блок style.
lin k (re l= " sty le sh e e t", href=baseU rl+"them es/flick/jquery-ui.
block s c rip ts <.-------- Определяет блок scripts.
scrip t(src= b aseU rl+ "jq u ery -u i.js")
block content
- count = 0
each message in messages
- count = count + 1
s c rip t
$(() => {
$("#message_#{count}").dialog({
height: 140,
modal: tru e
});
});
!= '<div id="message_' + count + '"> ' + message + '< /div>'

Н аследование — не единственны й путь объединения нескольких ш аблонов. Также


мож но воспользоваться ком андой P u g in c lu d e .

Включение шаблонов
Ещ е одним инструм ентом организации ш аблонов яв л яетс я ком анда P u g in clu d e .
Э та ком анда встраивает в ш аблон контент другого ш аблона. Если в ш аблон layout.
pug из преды дущ его прим ера добавить строку in c lu d e f o o te r , получится следую ­
щ ий шаблон:

html
head
block t i t l e
block sty le
block s c rip ts
s c rip t(src = '//a ja x .g o o g le a p is .c o m /a ja x /lib s/jq u e ry /1 .8 /jq u e ry .js')
body
block content
include foo ter

Э тот ш аблон вклю чает контент ш аблона footer.pug в разметку, визуализируем ую


ш аблоном layout.pug, как показано на рис. 7.3.
226 Глава 7. Шаблонизация веб-приложений

Рис. 7.3. Механизм include в Pug предоставляет простой способ включения контента
одного шаблона в другой шаблон во время визуализации

Э тот м еханизм м ож ет при м ен яться, наприм ер, д ля д обавления в ф айл layout.pug


ин ф орм аци и о веб-сайте или элем ентов дизайна. Указав расш ирение, мож но такж е
вклю чать в ш аблон ф айлы , не относящ иеся к Pug, например:

include tw itter_w idget.htm l

Примеси и многократное использование программной


логики шаблона
Х отя для использования готовых фрагментов кода может прим еняться команда Pug
in c lu d e , с ее помощ ью вряд ли удастся создать библиотеку м ногократно исп оль­
зуем ой ф ункциональности, которая м огла бы использоваться как страницам и, так
и при лож ениям и. Д л я реш ения этой задачи в P ug служ ит ком анда mixin, которая
позволяет определять примеси — м ногократно используем ы е ф рагм енты кода Pug.

П рим еси P u g нап ом и наю т ф у н к ц и и Ja v a S c rip t. К ак и ф ун кц и я, прим есь мож ет


получать аргументы, которы е учиты ваю тся при генерировании кода Pug.

П редполож им , например, что наш е при лож ение обрабаты вает следую щ ую стр у к­
туру данных:

const students = [
{ name: 'Rick LaRue', age: 23 },
{ name: 'Sarah Cathands', age: 25 },
{ name: 'Bob Dobbs', age: 37 }
];

Чтобы определить способ вывода списка H TM L, получаемого из значений заданного


свойства каж дого объекта, м ож но задействовать следую щ ую примесь:

mixin list_ o b ject_ p ro p erty (o b jects, property)


ul
each object in objects
li= object[property]
7.5. Заключение 227

З ат ем м ож н о в о сп о л ьзо в аться при м есью д л я вы вода дан н ы х следую щ ей с т р о ­


кой Pug:

mixin list_ o b ject_ p ro p erty (stu d en ts, 'name')

П о л ь зу я с ь н асл едо ван и ем ш аблонов, и н с тр у к ц и я м и in c lu d e и п ри м есям и , вы


м ож ете без проблем м ногократно исп ользовать визуализирую щ ую разм етку без
чрезм ерного «разбухания» ф айлов шаблона.

7.5. Заключение
О Ш аблони заторы помогаю т упорядочить прикладную логику и средства в и зу ­
ализации.

О В N ode поддерж иваю тся некоторы е популярны е ш аблонизаторы , вклю чая EJS,
H ogan.js и Pug.

О EJS поддерж ивает просты е управляю щ ие кон струкции и эк ранировани е при


незащ ищ енной интерполяции.

О H ogan.js — простой ш аб лони затор без у п р авл яю щ и х кон струкций , но с п о д ­


держ кой стандарта M ustache.

О P ug — более сложный язы к шаблонов, которы й может выводить разметку H T M L


без исп ользован ия угловы х скобок.

О Р абота P u g зависи т от пробельны х сим волов при внедрении тегов.


8 Хранение данных
в приложениях

N ode.js о р и ен ти р у ется на н евер о ятн о ш и р о ки й спектр разр аб о тч и ко в со столь


ж е разнообразны м и потребностям и. Н и одна база данны х или технология хран е­
н и я данны х не способна удовлетворить больш инство сценариев использования,
об служ иваем ы х N ode. В этой главе предоставлен ш ироки й обзор возм ож ностей
хранения данны х наряду с некоторы ми важ ны ми вы сокоуровневы м и концепциями
и терминологией.

8.1. Реляционные базы данных


Н а протяж ени и почти всего врем ени сущ ествования веб-технологий реляционны е
базы данны х зани м али ведущ ее место в области хранени я данных. Э та тема уже
рассм атривается во м ногих других книгах и образовательны х программах, поэтому
мы не будем тратить много врем ени на ее рассмотрение.

Р еляц и он н ы е базы данных, базирую щ иеся на м атем атических кон цепц иях р е л я ­
ц и онной алгебры и теори и м нож еств, известны с 1970-х годов. Схема (schem a)
определяет ф орм ат различны х типов данны х и отнош ения, сущ ествую щ ие меж ду
этим и типам и. Н априм ер, при построении социальной сети мож но создать типы
данны х User и P o st и определить отнош ения «один ко многим» меж ду User и Post.
Далее на язы ке SQ L (S tru ctu red Q uery Language) формулирую тся запросы к данным
типа «П олучить все сообщ ения, принадлеж ащ ие пользователю с идентиф икатором
123», или на SQL: SELECT * FROM p o st WHERE user_id=123.

8.2. PostgreSQL
M yS Q L и P o stg reS Q L (P o stg re s) остаю тся самы ми поп улярн ы м и реляционн ы м и
б азам и дан н ы х д л я п р и л о ж ен и й N ode. Р а зл и ч и я м еж ду р ел я ц и о н н ы м и базам и
данны х в основном эстетические, поэтом у этот раздел в равной степени относится
8.2. PostgreSQL 229

и к другим реляционн ы м базам данны х — например, M yS Q L в Node. Н о сначала


разберем ся, как установить P ostgres на м аш ине разработки.

8.2.1. Установка и настройка


С начала нуж но установить P ostgres в ваш ей системе. П ростой команды npm i n s t a l l
д ля этого недостаточно. И нструкции по установке зависят от платформ ы . В macOS
установка сводится к простой последовательности команд:

brew update
brew in s t a ll postgres

Если в ваш ей системе поддерж ка Postgres уже установлена, вы мож ете столкнуться
с проблем ам и при попы тке обновления. В ы полните ин струкции д ля своей п л ат­
ф ормы, чтобы произвести м играцию сущ ествую щ их баз данных, либо полностью
сотрите каталог базы данных:

# WARNING: w ill delete e x istin g postgres configuration & data


rm - r f /u sr/lo c a l/v a r/p o s tg re s

Затем и н ици али зируйте и запустите Postgres:

in itd b -D /u s r/lo c a l/v a r/p o s tg re s


pg_ctl -D /u s r/lo c a l/v a r/p o s tg re s - l lo g file s ta r t

Э ти ком анды запускаю т дем она Postgres. Д ем он долж ен запускаться каж ды й раз,
когда вы перезагруж аете компьютер. Возможно, вам строит настроить автоматиче­
скую загрузку демона Postgres при запуске; во м ногих сетевых руководствах можно
найти описание этого процесса д ля ваш ей операционной системы.

Во многих систем ах сем ейства L inux им еется пакет д ля установки Postgres. Д ля


системы W indow s следует загрузить програм м у установки на сайте postgresql.org
(www.postgresql.org/download/windows/).
В составе P o stg re s у стан ав л и в аю тся н екоторы е адм и н и стр ати вн ы е програм м ы
командной строки. О знакомьтесь с ними; необходимую инф орм ацию мож но найти
в электронной докум ентации.

8.2.2. Создание базы данных


П осле того как демон P ostg res заработает, необходимо создать базу данных. Эту
п роцедуру достаточно вы полни ть всего один раз. П рощ е всего воспользоваться
програм м ой created b из реж и м а ком андной строки. С ледую щ ая ком анда создает
базу данны х с им енем a r t i c l e s :

createdb a r tic le s
230 Глава 8. Хранение данных в приложениях

Е сли операция заверш ается успеш но, ком анда ничего не выводит. Если база д ан ­
ны х с указанны м именем уж е сущ ествует, ком анда ничего не делает и сообщ ает об
ошибке.

К ак прави ло, п р и л о ж ен и я в лю бой м ом ент врем ен и подклю чены только к одной


базе данны х, х отя м ож н о н астр о и ть сразу н еск ольк о баз дан н ы х в зави си м ости
от реж и м а, в котором работает база данны х. Во м ногих п р и л о ж ен и я х р а зл и ч а ­
ю тся по к р а й н е й м ере д ва р еж и м а: р еж и м р азр аб о т к и и р еж и м р е ал ь н о й э к с ­
плуатации .

Ч тобы удали ть все данны е из сущ ествую щ ей базы данны х, вы полни те команду
dropdb с терминала, передав ей им я базы данны х в аргументе:

dropdb a r tic le s

Ч тобы снова использовать базу данных, необходимо вы полнить команду created b .

8.2.3. Подключение к Postgres из Node


С ам ы й поп улярн ы й пакет д ля взаим одействия с P ostgres из N ode назы вается pg.
Его м ож но установить при помощ и npm:

npm in s ta l l pg --save

К огда сервер P ostgres заработает, база данны х будет создана, а пакет pg устан ов­
лен, вы смож ете переходить к использованию базы данны х из Node. П реж де чем
вводить какие-либо ком анды к серверу, необходимо создать подклю чение к нему,
как показано в листинге 8.1.

Листинг 8.1. Подключение к базе данных


const pg = re q u ire ('p g '); Параметры конфигурации
const db = new pg.C lient({ database: 'a r t i c l e s ' }); -<— подключения.
db.con n ect((err, c lie n t) => {
i f (e rr) throw e rr;
console.log('Connected to d atab ase', db.database);
db.end(); -<-------- Закрывает подключение кбазе данных, позволяя процессу node завершиться.
});

П одробную докум ентацию по p g .C lie n t и другим методам мож но найти на вики-


странице пакета pg на G itH u b : https://github.com/brianc/node-postgres/wiki.

8.2.4. Определение таблиц

Ч тобы храни ть дан ны е в P o stg reS Q L , сначала необходим о определить таблицы


и ф орм ат данных, которы е в них будут храниться. П рим ер такого рода приведен
в листинге 8.2 (ch08-databases/listing8_3 в архиве исходного кода книги).
8.2. PostgreSQL 231

Листинг 8.2. Определение схемы


db.query('
CREATE TABLE IF NOT EXISTS snippets (
id SERIAL,
PRIMARY KEY(id),
body te x t
);
' , (e rr, re s u lt) => {
i f (e rr) throw e rr;
console.log('C reated ta b le "sn ip p e ts" ');
db.end();
});

8.2.5. Вставка данных


П осле того как таблиц а будет определена, в нее мож но вставить данны е запросам и
INSERT (л и сти н г 8.3). Если значение id не указано, то P o stg reS Q L вы берет его за
вас. Ч тобы узнать, какой идентиф икатор был вы бран д ля конкретной записи, п ри­
соедините условие RETURNING id к запросу; идентиф икатор будет выведен в строках
результата, переданного ф ун кц и и обратного вызова.

Листинг 8.3. Вставка данных


const body = 'h e llo w orld';
db.query('
INSERT INTO snippets (body) VALUES (
'${body}'
)
RETURNING id
' , (e rr, re s u lt) => {
i f (e rr) throw e rr;
const id = re su lt.ro w s[0 ].id ;
co n so le .lo g ('In se rte d row with id %s', id );
db.query('
INSERT INTO snippets (body) VALUES (
'${body}'
)
RETURNING id
' , () => {
i f (e rr) throw e rr;
const id = re su lt.ro w s[0 ].id ;
co n so le .lo g ('In se rte d row with id %s', id );
});
});

8.2.6. Обновление данных


П осле того как данны е будут вставлены , их можно будет обновить запросом UPDATE
(листинг 8.4). К оличество записей, задействованны х в обновлении, будет доступно
в свойстве rowCount результата запроса. П олны й пример для этого листинга содер­
ж и тся в каталоге ch08-databases/listing8_4.
232 Глава 8. Хранение данных в приложениях

Листинг 8.4. Обновление данных


const id = 1;
const body = 'g re e tin g s, w orld';
db.query('
UPDATE snippets SET (body) = (
'${body}'
) WHERE id=${id};
' , ( e rr, re su lt) => {
i f (e rr) throw e rr;
console.log('U pdated %s ro w s.', result.rowCount);
});

8.2.7. Запросы на выборку данных


О дна из самы х зам ечательны х особенностей реляционн ы х баз данны х — возм ож ­
ность вы полнения сложных произвольны х запросов к данным. Запросы вы полняю т­
ся ком андой SELECT, а простейш ий прим ер такого рода представлен в листинге 8.5.

Листинг 8.5. Запрос данных


db.query('
SELECT * FROM snippets ORDER BY id
' , ( e rr, re su lt) => {
i f (e rr) throw e rr;
co n so le.lo g (resu lt.ro w s);
});

8.3. Knex
М ногие разработчики предпочитаю т работать с ком андам и SQ L в своих п ри лож е­
н и ях не напрямую , а через абстрактную надстройку. Это ж елание вполне понятно:
кон катенаци я строк в ком анды SQ L м ож ет быть громоздким процессом, которы й
услож няет понимание и сопровож дение запросов. Сказанное особенно справедливо
по отнош ению к я зы к у Ja v a S c rip t, в котором не было син такси са представления
м ногострочны х строк до появл ен и я в ES2015 ш аблонны х литералов (см. h ttp s://
developer.mozilla.org/en/docs/Web/favaScript/Reference/Template_literals). Н а рис. 8.1
показана статистика K nex с количеством загрузок, доказы ваю щ их популярность.

16 dependencies version 0 11 7
278 dependents ^ updated 19 days ago

Рис. 8.1. Статистика использования Knex

K nex — пакет N ode, реализую щ ий облегченную абстракцию д ля SQL, известную


как построитель запросов. П остроитель запросов ф орм ирует строки S Q L через
8.3. Knex 233

декл ар ати вн ы й A P I, которы й им еет м ного общего с генерируем ы м и ком андам и


SQL. K nex A P I интуитивен и предсказуем:
knex({ c lie n t: 'mysql' })
.s e le c t()
.fro m ('u se rs')
.where({ id: '123' })
.toSQL();

Э тот вы зов создает парам етризованны й запрос SQ L на диалекте M ySQL:


se le c t * from 'u s e r s ' where 'i d ' = ?

8.3.1. jQuery для баз данных


Х отя стандарты ANSI и ISO SQ L появились еще в середине 1980-х годов, больш ин­
ство баз данны х продолжает использовать собственные диалекты SQL. PostgreSQ L
я вл яется зам етны м исклю чением: эта база данны х мож ет похвастать соблю дением
стандарта SQL:2008. П остроитель запросов способен норм ализи ровать различия
меж ду диалектам и SQL, предоставляя едины й ун и ф и ц и рован н ы й ин терф ейс для
генер и р о ван и я S Q L в р азн ы х технологиях. Такой подход обладает очевидны м и
преим ущ ествам и для групп, регулярн о переклю чаю щ ихся меж ду разны м и техн о­
логиям и баз данных.

В настоящ ее врем я Knex.js поддерж ивает следую щ ие базы данных:

О PostgreS Q L ;

О M SSQ L;

О M ySQ L;

О M ariaD B ;
О SQ Lite3;

О Oracle.

В табл. 8.1 сравниваю тся способы генерирования ком анды INSERT в зависим ости
от вы бранной базы данных.

Таблица 8.1. Сравнение команд SQL, сгенерированных Knex, для разных баз данных

База данных SQL


PostgreSQL, SQLite insert into "users" ("name", "age") values (?, ?)
и Oracle
MySQL и MariaDB insert into 'users' ('n a m e ', 'a g e ') values (?, ?)
Microsoft SQL Server insert into [users] ([name], [age]) values (?, ?)

K nex поддерж ивает обещания (prom ises) и обратны е вы зовы в стиле Node.
234 Глава 8. Хранение данных в приложениях

8.3.2. Подключение и выполнение запросов в Knex


В отличие от м ногих других построителей запросов, K nex такж е мож ет п одклю ­
чаться и вы полнять запросы к вы бранном у драйверу базы данны х за вас.

d b ( 'a r t i c l e s ')
. s e l e c t ( 't i t l e ')
.where({ t i t l e : 'Today's News' })
.th e n (a rtic le s => {
c o n so le .lo g (a rtic le s);
});

П о ум олчанию запросы K nex возвращ аю т обещ ания, но они такж е поддерж иваю т
соглаш ения обратного вы зова N ode с использованием .asC a llb ac k :

d b ( 'a r t i c l e s ')
. s e l e c t ( 't i t l e ')
.where({ t i t l e : 'Today's News' })
.asC allb ack ((err, a r tic le s ) => {
i f (e rr) throw e rr;
c o n so le .lo g (a rtic le s);
});

В главе 3 мы взаимодействовали с базой данных SQLite непосредственно при помощи


пакета sqlite3. Этот A PI можно переписать с использованием Knex. Прежде чем запу­
скать этот пример, сначала проверьте из npm, что пакеты knex и sqlite3 установлены:

npm in s ta l l knex@~0.12.0 sqlite3@~3.1.0 --save

В листинге 8.6 sqlite исп ользуется д ля р еал и зац и и простой м одели A r tic le . С о ­
храните ф айл под именем db.js; он будет исп ользоваться в листинге 8.7 д ля в за и ­
м одействия с базой данных.

Листинг 8.6. Использование Knex для подключения и выдачи запросов к sqlite3


const knex = re q u ire ('k n e x ');

const db = knex({
c lie n t: 's q l i t e 3 ',
connection: {
filename: 't l d r . s q l i t e ' Выбор этого режима по умолчанию
}, лучше работает при смене
useNullAsDefault: true подсистемы баз данных.
});

module.exports = () => {
return d b .sch em a.createT ab leIfN o tE x ists('articles', ta b le => {
ta b le .in c re m e n ts ('id ').p rim a ry (); •<- Определяет первичный ключ с именем
t a b l e . s t r i n g ( 't i t l e ') ; «id», значение которого автоматически
t a b le .te x t( 'c o n te n t') ; увеличивается при вставке.
});
};
8.3. Knex 235

m odule.exports.A rticle = {
a ll ( ) {
return d b ( 'a r t i c l e s ') .o r d e r B y ( 'ti t l e ') ;
},
fin d (id ) {
return d b ('a rtic le s ').w h e re ({ id } ) . f i r s t ( ) ;
},
create(d ata) {
return d b ( 'a r tic le s ') .in s e r t( d a ta ) ;
},
d ele te (id ) {
return d b ('a rtic le s ').d e l().w h e re ({ id });
}
};

Листинг 8.7. Взаимодействие с API на базе Knex


d b ().th e n (() => {
d b .A rtic le .c rea te ({
t i t l e : 'my a r t i c l e ',
content: 'a r t i c l e content'
} ). th en (() => {
d b .A r tic le .a ll( ) .th e n ( a r tic le s => {
c o n so le .lo g (a rtic le s);
p ro c e ss.e x it();
});
});
})
.c a tc h (e rr => { throw err });

S Q L ite требует м ин им альной настройки: вам не нуж но загруж ать демон сервера
или создавать базы данны х за пределам и прилож ения. SQ L ite записы вает все д ан ­
ные в один ф айл. В ы полнив преды дущ ий код, вы увидите, что в текущ ем каталоге
п о яви л ся ф айл articles.sqlite. Ч тобы уничтож ить базу данны х SQ L ite, достаточно
удалить всего один файл:

rm a r t i c l e s .s q l i te

SQ L ite такж е поддерж ивает реж им работы в памяти, при котором запись на диск
вообщ е не осущ ествляется. Э тот реж им обычно используется д ля ускорен ия вы ­
п ол н ен и я авто м атизи рованны х тестов. Д л я н астройки реж и м а работы в пам яти
и сп ользу ется специ альное и м я ф ай л а :memory:. П ри откры тии н ескольких под ­
клю чений к ф ай л у :memory: каж дое подклю чение получает собственную и зо л и р о ­
ванную базу данных:

const db = knex({
c lie n t: 's q l i t e 3 ',
connection: {
filename: ':memory:'
},
useNullAsDefault: true
});
236 Глава 8. Хранение данных в приложениях

8.3.3. Переход на другую базу данных


Благодаря использованию Knex, листинги 8.6 и 8.7 позволяю т легко переклю чаться
с sqlite3 на P ostgreS Q L . Д л я взаим одействия K nex с сервером P o stg reS Q L необ­
ходим а у стан овка и запуск пакета pg. Установите пакет pg в папку ли сти н га 8.7
(ch08-databases/listing8_7 в коде кн иги) и не забудьте создать соответствую щ ую базу
данны х програм м ой ком андной строки P o stg reS Q L crea ted b :

npm in s ta l l pg --save
createdb a r tic le s

Все изм енения кода, необходимые д ля использования новой базы данных, вносятся
в кон ф и гурац ии Knex; в остальном A P I и схема исп ользован ия идентичны:

const db = knex({
c lie n t: 'p g ',
connection: {
database: 'a r t i c l e s '
}
})

С тоит заметить, что в реальной ситуации вам такж е придется вы полнить миграцию
всех сущ ествую щ их данных.

8.3.4. Остерегайтесь ненадежных абстракций


П остроители запросов способны норм ализовать синтаксис SQL, но не справятся
с норм ализацией поведения. Н екоторы е возмож ности поддерж иваю тся только кон­
кретны м и базам и данных, и некоторы е базы данны х могут п роявлять соверш енно
иное поведение при идентичны х запросах. Н апример, ниж е приведены два способа
определения первичного клю ча с использованием Knex:

О t a b l e . i n c r e m e n t s ( 'i d ') . p r i m a r y ( ) ;

О t a b l e . i n t e g e r ( 'i d ') . p r i m a r y ( ) ;

О ба варианта работаю т в S Q L ite3 так, как ож идается, но второй вариант вы зывает


ош ибку в P o stg reS Q L при вставке новой записи:

"null value in column "id" v io la te s n o t-n u ll constraint"

Зн ачениям , вставленны м в S Q L ite с неопределенны м первичны м клю чом, будет


присвоен автом атически увел и ч и ваем ы й и д ен ти ф и катор — незави си м о от того,
было ли явно настроено автоматическое увеличение для столбца первичного ключа.
С другой стороны, P o stg reS Q L требует, чтобы столбцы с автоматическим у в ел и ­
чением о пред елялись явн о. М еж ду базам и дан ны х сущ ествует много подобны х
поведенческих различий, и некоторы е р азл и ч и я могут оставаться незам етны ми,
8.4. MySQL и PostgreSQL 237

не при водя к видим ы м ош ибкам. Е сли вы реш или перейти на другую базу данных,
обязательно проведите тщ ательное тестирование.

8.4. MySQL и PostgreSQL


К ак M ySQ L, так и P o stg reS Q L — мощ ные, проверенны е врем енем базы данных,
и во м ногих проектах выбор одного или другого варианта практически ни на что не
влияет. М ногие различия, которые не играют роли, пока не возникнет необходимость
в масш табировании проекта, существуют на границе интерфейса, предоставляемого
разработчику прилож ений, или ниж е нее.

П одробны й сравнительны й анализ р еляционн ы х баз данны х в основном выходит


за рам ки книги, поскольку д ан н ая тем а достаточно слож на. Н екоторы е важ ны е
разл и ч и я перечислены ниже:

О PostgreS Q L поддерж ивает более вы разительны е типы данных, вклю чая массивы,
JS O N и типы, определяем ы е пользователем.

О P o stg reS Q L поддерж ивает встроенны й м еханизм полнотекстового поиска.

О P o stg reS Q L в полной м ере поддерж ивает стандарт ANSI SQL:2008.

О П оддерж ка репл и кац и и в P o stg reS Q L не настолько мощ на и не так проверена


на практике, как в M ySQ L.

О M yS Q L старш е и обладает более м ногочисленны м сообщ еством. Д л я M ySQ L


сущ ествует больш е совм естимы х инструм ентов и ресурсов.

О Сообщ ество M yS Q L в больш ей степени ф рагм ентировано из-за нетривиальны х


р азл и ч и й м еж ду в етв ям и (н ап р и м ер , M ariaD B и W ebS caleS Q L от Facebook,
Google, T w itter и т. д.).

О Ядро хранения данных M ySQL с подключаемыми модулями создает больше проблем


с пониманием, администрированием и настройкой. Тем не менее его можно рассма­
тривать как полезную возможность для более точной настройки быстродействия.

M yS Q L и P o stg re S Q L п р о я в л я ю т р азн ы е х ар ак тер и сти к и б ы строд ей стви я при


м асш таби ровании в зависи м ости от типа рабочей нагрузки. О собенности ваш ей
нагрузки м огут проявиться только в процессе стан овлен ия проекта.

Н а м ногих и н терн ет-ресурсах представлены гораздо более глубокие сравнени я


реляционн ы х баз данных:

О w w w .digitalocean.com /com m unity/tutorials/sqlite-vs-m ysql-vs-postgresql-a-


comparison-of-relational-database-management-systems;
О https://blog.udemy.com/mysql-vs-postgresql/;
О https://eng.uber.com/mysql-migration/.
238 Глава 8. Хранение данных в приложениях

И сходн ы й вы бор базы дан ны х вряд ли зн ачительно повл и яет на успех проекта,
поэтому не стоит слиш ком сильно беспокоиться по поводу этого реш ения. Позднее
вы сможете переклю читься на другую базу данных, но, скорее всего, возмож ностей
Postgres будет достаточно для лю бых потребностей в ф ункциональности и м асш та­
бируемости. О днако если вы вы бираете м еж ду нескольким и базам и данных, вам
стоит позн аком иться с идеей гарантий ACID.

8.5. Гарантии ACID


С о к р ащ ен и е «A C ID » оп и сы вает набор ж ел ател ьн ы х свойств, которы м и д о л ж ­
ны обладать т р ан закц и и баз данных: атом арность (A to m ic ity ), согласованность
(C o n siste n c y ), и золи рован н ость (Iso la tio n ) и устойчивость (D u ra b ility ). Точные
определения этих терм инов м огут различаться. К ак правило, чем ж естче система
гарантирует свойства A CID , тем значительнее потери по быстродействию . К ласси­
ф и кац и я A C ID — распространенны й способ, которы м разработчик м ож ет быстро
опи сать до сто и н ства и недостатки кон кретного реш ен и я (нап ри м ер, присущ ие
систем ам N oSQ L ).

8.5.1. Атомарность
Атомарная транзакц ия не мож ет быть вы полнена частично: либо вся операция за ­
верш ается, либо база данны х остается без изм енений. Н апример, если транзакц ия
заклю чается в удален ии всех ком м ентариев некоторого пользователя, то либо все
ком м ентарии будут удалены, либо не будет удален ни один. С итуация, при которой
часть ком м ентариев удаляется, а другая часть остается, невозмож на.

П р и н ц и п ато м а р н о с ти д о л ж ен д ей с тв о в а т ь д аж е в случае си стем н о й ош иб ки


и л и сбоя п и тан и я. В д ан ном случае « атом арн ость» следует п он им ать как « н е ­
делим ость».

8.5.2. Согласованность
П р и зав ер ш ен и и усп еш н о й т р а н за к ц и и долж н ы собл ю даться все огран и ч ен и я
согласованности (целостности данны х), определенны е в системе. П рим еры таких
ограничений — уникальность первичны х клю чей, соответствие данны х некоторой
схеме и т. д. Т ранзакции, которы е приводят к некорректном у состоянию данных,
обы чно приводят к откату транзакций, хотя незначительны е проблем ы могут р а з­
реш аться автом атически (н ап ри м ер, приведени е дан н ы х к п рави льн ой ф орм е).
Э тот вид со гласован ности не следует путать с согласован ностью ( C ) в теорем е
CAP, которая относится к единству представления данны х для всех пользователей
распределенного хранилищ а.
8.6. NoSQL 239

8.5.3. Изоляция
Изолированные тр анзакц ии долж ны приводить к одинаковому результату н езави ­
симо от их параллельного и ли последовательного прим енения. Уровень изоляции,
предоставляем ы й системой, напрям ую влияет на ее способность вы полнять парал­
лельны е операции. Н аи вн ая схема и зо л яц и и основана на прим енении глобальной
блокировки ; вся база данны х блокируется на врем я транзакции, в результате чего
все транзакции ф актически вы полняю тся последовательно. Такая схема предостав­
л яет сильны е гарантии изоляци и, но она патологически неэф ф ективна: тр ан зак­
ции, работаю щ ие с полностью несвязанны м и наборам и данных, блокирую тся без
необходим ости (напри м ер, добавление пользователем ком м ентария в идеале не
долж но мешать обновлению п роф иля другим пользователем). Н а практике системы
предоставляю т разны е уровни изо л яц и и с прим енением схем блокировки с разной
избирательностью (н а уровне таблиц, строк или полей). Б олее слож ны е системы
могут даж е оптим истически пы таться проводить все транзакц ии с м иним альной
б локировкой, а затем заново п о вто р ять тр ан зак ц и и со сни ж ением детал и зац и и
в случае вы явл ен и я конф ликтов.

8.5.4. Устойчивость
Устойчивость транзакции определяет степень, в которой ее эф ф ект гарантированно
сохранится даж е при перезапуске, сбое питания, системны х ош ибках и даж е сбоях
об орудовани я. Н априм ер, при лож ение, использую щ ее S Q L ite в реж и м е работы
в памяти, не обладает устойчивостью транзакций; все данны е теряю тся при выходе
из процесса. С другой стороны, S Q L ite в реж им е запи си данны х на диск будет об­
ладать хорош ей транзакц ионн ой устойчивостью , потому что данны е сохраняю тся
даж е после перезапуска маш ины.

К азалось бы, прощ е некуда: зап и ш и те д ан ны е на д иск — вуаля, у вас п о я в л я ю т ­


ся устойчи вы е тр ан закц и и . Н о д и сковы й в в о д /в ы во д я в л я е т с я одной из самых
м едленн ы х операций, вы п о л н яем ы х ваш им прилож ением , и м ож ет бы стро стать
серьезн ы м « у зк и м м естом » ваш его п р и л о ж е н и я даж е п ри у м ерен н ы х ур о вн ях
м асш таби рован и я. Н еко то р ы е базы дан ны х поддерж иваю т разн ы е степени к о м ­
пром и сса по устойчи вости , которы е м огут обеспечивать при ем лем ое б ы стродей­
ствие системы .

8.6. NoSQL
Х ранилищ а данных, которые не вписываю тся в реляционную модель, объединяются
под термином NoSQL. Так как в наш и дни некоторы е базы данны х N oSQ L поддер­
ж иваю т SQ L, смы сл терм ина N oSQ L ближ е к определению «нереляционны й» или
придум анном у задним числом сокращ ению «N ot O nly SQ L» («не только SQ L»).
240 Глава 8. Хранение данных в приложениях

П одмнож ество парадигм и прим еры баз данных, которые мож но отнести к NoSQL:

О пары « к л ю ч -зн а ч е н и е » /к о р т е ж и — D y n am o D B , L evelD B , R edis, etcd , R iak,


A erospike, Berkeley DB;

О граф овы е базы данны х — N eo4J, O rien tD B ;


О документные базы данных — CouchD B, M ongoDB, Elastic (formerly Elasticsearch);

О столбцовы е базы данны х — C assandra, HBase;

О базы данны х врем енны х рядов — G raphite, InfluxD B , R R D tool;

О м ного п ар ад и гм ен н ы е базы дан н ы х — C ouchbase (д о к у м ен тн ая база данны х,


храни ли щ е пар «клю ч-значение», распределенны й кэш ).

З а более подробны м списком баз дан ны х N oS Q L обращ айтесь по адресу h ttp ://
nosql-database.org/.
К онцепции N oSQ L бывает трудно усвоить, если вы работали только с реляционными
базами данных, потому что применение N oSQ L часто противоречит установивш ейся
практике: отсутствие определенных схем. Дублирование данных. Слабое соблюдение
ограничений. С истем ы N oSQ L берут на себя обязанности, обычно закрепляем ы е за
базам и данны х, и вы водят их в область ответственности прилож ения. Н а первы й
взгляд все это вы глядит сомнительно.

О бы чно небольш ое количество схем доступа создает основную нагрузку на базу


данны х — например, запросы , генерирую щ ие начальны й экран прилож ения, для
которого необходим о получить несколько объектов предм етной области. С т ан ­
дартны м прием ом повы ш ения производительности ч тен и я в реляционн ы х базах
данны х яв л яется нормализация — запросы обрабаты ваю тся и при водятся к форме,
сокращ аю щ ей количество операций чтения, необходимых для потребления данных
клиентом.

Д анны е N oS Q L чащ е д ен орм али зирую тся по ум олчанию , и весь этап м од ел и ро­
вани я предм етной области м ож ет быть полностью опущен. Такой подход предот­
вращ ает чрезм ерное техническое услож нение м одели данны х, позволяет быстрее
отрабаты вать изм ен ен ия и в целом приводит к более простой и производительной
архитектуре.

8.7. Распределенные базы данных


П ри лож ен и е м ож ет м асш таби роваться вертикально (за счет повы ш ения п р о и з­
во д и тел ьн о сти м аш и н ) и л и го р и зо н тал ьн о (за счет д об авл ен и я новы х м аш и н).
В е р т и к а л ь н о е м а с ш та б и р о в а н и е обы чно прощ е р еа л и зу ет ся , но в о зм о ж н о сти
о б о р у д о в а н и я о гр ан и ч и в аю т в о зм о ж н о с ти м а с ш т а б и р о в а н и я о д н ой м аш и ны .
К ром е того, вертикальное м асш таби рование такж е бы стро дорож ает. Н апротив,
8.8. MongoDB 241

горизон тальное м асш таби рование обладает нам ного больш им потенц иалом для
роста при д о бавл ен и и новы х процессов и новы х м аш ин. З а все это при ходи тся
платить слож ностью у п равлени я больш им количеством «подвиж ны х частей». Все
растущ ие системы со временем достигаю т точки, в которой приходится проводить
горизонтальное м асш табирование.

Распределенны е базы данны х с самого начала проектирую тся с расчетом на гори ­


зонтальное масш табирование. Х ранение данны х на нескольких м аш инах повыш ает
устойчивость данных за счет устранения «единых точек сбоя». М ногие реляционные
системы в некоторой степени способны вы полнять горизонтальное м асш таби ро­
вание в ф орм е сегментации, р епликации « гл авны й/под чиненн ы й» и ли « гл а в н ы й /
главны й», хотя даж е с этим и ф у н кц и ям и реляционн ы е системы не рассчитаны на
масш табирование более чем для нескольких сотен узлов. Например, верхний предел
кластера M yS Q L составляет 255 узлов. Р аспределенны е базы данных, напротив,
могут м асш табироваться на ты сячи узлов по самой архитектуре.

8.8. MongoDB
M ongoD B — докум ентно-ориентированная распределенная база данных, исклю чи­
тельно популярная среди разработчиков Node. О на стоит на первом месте в модном
технологическом стеке M EAN (M ongoD B , Express, Angular, N ode) и часто становится
одной из первы х баз данных, с которой сталкиваю тся лю ди в начале работы с Node.
Н а рис. 8.2 показано, насколько популярен модуль m ongodb в npm.

1,893 dependents updated 6 hours ago

Рис. 8.2. Статистика использования MongoDB

M ongoD B при влекает более чем справедливую долю кри ти ки ; несм отря на это,
M ongoD B остается надеж ны м хр ан и ли щ ем дан ны х д л я м ногих разработчиков.
П оддерж ка M ongoD B развернута во м ногих видны х ком паниях, вклю чая Adobe,
LinkedIn и eBay, и даже используется в компоненте Больш ого адронного коллайдера
в Е вропейском центре ядерн ы х исследований (C E R N ).

В базе данны х M ongoD B документы хранятся в бессхемных коллекциях. Документ


не обязан строиться по заранее определенной схеме, а докум енты одной ко л л е к ­
ц ии не о бязан ы совм естно и сп о льзо вать одну схему. Таким образом, M ongoD B
н аделяется значительной гибкостью , хотя на прилож ение л ож и тся брем я по под ­
держ анию прогнозируем ой структуры докум ента (гаран ти рован н ая согласован ­
ность — «C» в A C ID ).
242 Глава 8. Хранение данных в приложениях

8.8.1. Установка и настройка


П акет M ongoD B долж ен бы ть у стан овлен в ваш ей систем е. П роцесс устан овки
отличается в зависи м ости от платф орм ы .

В m acO S установка сводится к простой команде:

brew in s t a l l mongodb

С ервер M ongoD B запускается исполняем ы м ф айлом mongod:

mongod --config /usr/local/etc/m ongod.conf

С ам ы м популярны м драйвером M ongoD B явл яется оф ици альн ы й пакет mongodb,


созданны й К ристианом А мором К валхейм ом (C h ristia n A m or Kvalheim):

npm in s ta l l mongodb@A2.1.0 --save

П ользователям W indow s следует учитывать, что для установки драйвера необходим


ф айл msbuild.exe, устанавливаем ы й M icrosoft V isual Studio.

8.8.2. Подключение к MongoDB


П осле установки пакета m ongodb и запуска сервера m ongod вы смож ете подклю ­
читься в качестве кли ен та из Node:

Листинг 8.8. Подключение к MongoDB


const { MongoClient } = require('m ongodb');
M ongoC lient.connect('m ongodb://localhost:27017/articles')
.then(db => {
co n so le.lo g ('C lien t read y ');
d b .clo se();
}, co n so le.erro r);

О бработчику успеш ного заверш ен ия передается экзем пляр кли ен та базы данных,
из которого вы полняю тся все ком анды базы данных.

Больш инство взаимодействий с базой данных осущ ествляется через A PI коллекций:

О c o l l e c t i o n . i n s e r t ( d o c ) — вставка одного или нескольких документов;

О c o l le c ti o n .f i n d ( q u e r y ) — поиск докум ентов, соответствую щ их запросу;

О c o lle c tio n .re m o v e (q u e ry ) — удаление документов, соответствую щ их запросу;

О c o l l e c t i o n .d r o p ( ) — удаление всей коллекции;

О c o lle c tio n .u p d a te (q u e ry ) — обновление документов, соответствую щ их запросу;

О c o lle c tio n .c o u n t( q u e r y ) — подсчет документов, соответствую щ их запросу.


8.8. MongoDB 243

Такие операции, как поиск, вставка и удаление, обычно сущ ествую т в нескольких
разнови дн остях — в зависи м ости от того, работаете л и вы с одним или м ногим и
значениям и. П римеры:

О c o lle c tio n .in s e r tO n e ( d o c ) — вставка одного документа;

О c o lle c tio n .in s e r tM a n y ( [d o c 1 , doc2 ]) — вставка нескольких документов;

О c o lle c tio n .f in d O n e (q u e r y ) — поиск одного докум ента, соответствую щ его з а ­


просу;

О co llectio n .u p d ateM an y (q u ery ) — обновление всех документов, соответствующ их


запросу.

8.8.3. Вставка документов


c o l l e c t i o n . i n s e r t O n e в став л я ет один объект в ко л л екц и ю как д окум ент (л и с ­
тинг 8.9). О бработчику успеш ного заверш ен ия передается объект с метаданными,
относящ и м ися к состоянию операции.

Листинг 8.9. Вставка документа


const a r tic le = {
t i t l e : 'I lik e cake',
content: ' I t is quite good.'
}; Если у документа нет свойства _id, создается
d b .c o lle c tio n ( 'a r tic le s ') новый идентификатор. Сгенерированное
.in se rtO n e (a rticle ) значение хранится в insertid.
.th e n (re s u lt => {
c o n s o le .lo g (re su lt.in se rte d Id ); <-
c o n s o le .lo g (a rtic le ._ id ); B •<— Исходный объект, определяющий
}); документ, изменяется: в него
добавляется поле _id.

Вызов insertM any работает аналогично, если не считать того, что он получает массив
из нескольких докум ентов. О твет in sertM any будет содерж ать массив in s e r te d I d s
в порядке передачи докум ентов вместо одного значения in s e r te d I d .

8.8.4. Получение информации


Методы, читаю щ ие документы из коллекции (такие, как fin d , update и remove), полу­
чаю т аргумент, используем ы й для идентиф икации документов. П ростейш ая форма
аргум ента запроса представляет собой объект, которы й исп ользуется M ongoD B
д ля подбора докум ентов с такой ж е структурой и тем и ж е значениям и. Н апример,
следую щ ий ф рагм ент находит все статьи с заголовком «I like cake»:

d b .c o lle c tio n ( 'a r tic le s ')


.find({ t i t l e : 'I lik e cake' })
244 Глава 8. Хранение данных в приложениях

.to A rra y ().th e n (re su lts => {


c o n so le .lo g (re su lts); < -------- Массив документов, соответствующих запросу.
});

Запр о сы использую тся для поиска объектов по их уни кальном у значению _id:

collection.findO ne({ _id: somelD })

И л и для поиска с прим енением оператора запроса:

d b .c o lle c tio n ( 'a r tic le s ') Заголовок заканчивается словом ‘cake’


.f in d ( { title : { $regex: /cake$/I }) (без учета регистра символов).

В язы ке запросов M ongoD B поддерживаются разные операторы запросов. Например:

О $eq — равно заданном у значению ;

О $neq — не равно заданном у значению ;

О $ in — присутствует в массиве;

О $nin — не присутствует в массиве;

О $ l t , $ l t e , $gt, $ g te — б ольш е/м еньш е или равно;

О $near — геопространственное значение расположено вблизи заданных координат;

О $not, $and, $or, $nor — логические операторы.

Э ти значения могут объединяться для поиска практически по любым условиям. Так


создается в вы сш ей степени понятны й, слож ны й и вы разительны й я зы к запросов.
З а д ополнительной инф орм аци ей о запросах и операторах запросов обращ айтесь
по адресу https://docs.m ongodb.com/manual/reference/operator/query/.

В листинге 8.10 приведен прим ер р еал и зац и и приведенного выш е A P I A r t i c l e s


с M ongoD B ; при этом в н еш н и й и н тер ф ей с остается п р ак ти ч ески идентичны м .
С охраните ф айл под именем db.js (ф ай л listing8_10/db.js в исходном коде книги).

Листинг 8.10. Реализация API Article с MongoDB


const { MongoClient, ObjectID } = require('m ongodb');
l e t db;

module.exports = () => {
return MongoClient
.connect('m ongodb ://lo calh o st:2 7 0 1 7 /articles')
.th e n ((c lie n t) => {
db = c lie n t;
});
};
m odule.exports.A rticle = {
a ll() {
return d b .c o lle c tio n ( 'a r tic le s 2 ') .f in d ( ) .s o r t( { t i t l e : 1 }).toA rray();
8.8. MongoDB 245

},
find(_id) { Добавляет поддержку передачи _id
i f (typeof _id !== 'o b je c t') _id = ObjectID(_id); -<—I в формате String иObjectID.
return d b .c o lle c tio n ('a rtic le s 2 ').fin d O n e ({ _id });
},
create(d ata) {
return d b .c o lle c tio n ('a rtic le s 2 ').in s e rtO n e (d a ta , { w: 1 });
},
d elete(_ id ) {
i f (typeof _id !== 'o b je c t') _id = ObjectID(_id);
return d b .c o lle c tio n ('a rtic le s 2 ').d e le te O n e ({ _id }, { w: 1 });
}
};

С ледую щ ий ф рагм ент показы вает, как исп ользовать л и сти н г 8.10 (listin g 8_1 0 /
index.js в прим ере кода):

const db = r e q u ir e ( './d b ') ;


d b ().th e n (() => {
d b .A rtic le .c rea te ({ t i t l e : 'An a r t i c l e ! ' } ).th e n (() => {
d b .A r tic le .a ll( ) .th e n ( a r tic le s => {
c o n so le .lo g (a rtic le s);
p ro c e ss.e x it();
});
});
});

В этом ф рагменте обещ ание из листинга 8.10 используется для подклю чения к базе
данны х, после чего объект создается методом c r e a te класса A r tic le . П осле этого
ф рагм ент загруж ает все статьи и вы водит их на консоль.

8.8.5. Идентификаторы MongoDB


И дентиф икаторы M ongoD B кодирую тся в формате BSO N (B inary JS O N ). Свойство
_ id докум ента представляет собой объект J a v a S c rip t O b jec t, и н капсулирую щ и й
значение O bjectID в ф орм ате BSON. M ongoD B использует B SO N для внутреннего
представления докум ентов и как ф орм ат передачи данных. Ф орм ат B SO N более
ком пактен, чем JS O N , и бы стрее разбирается; это означает ускорен ие операций
с базой данны х с м еньш им и затратам и ресурсов.

Зн ач ен и е B SO N O bjectID — не простая последовательность байтов; в нем зако д и ­


рована ин ф орм аци я о том, где и как был сгенерирован идентиф икатор. Н апример,
первы е четы ре байта O bjectID содерж ат временную метку, и вам не придется вклю ­
чать свойство c re a te d A t в свои документы:

const id = new ObjectID(61bd7f57bf1532835dd6174b);


id.getTimestamp(); •<-------- getTimestamp возвращает JavaScript / Date: 2016-07-08T14:49:05.000Z.
246 Глава 8. Хранение данных в приложениях

З а дополнительной информ ацией о формате ObjectlD обращайтесь по адресу h ttp s://


docs.mongodb.com/manual/reference/method/ObjectId/.
М ож ет показаться, что объекты O bjectID похож и на строки из-за особенностей их
вы вода на терминал; тем не менее они явл яю тся объектами. О бъекты O bjectID под­
вержены классической проблеме сравнения объектов: полностью эквивалентны е на
первы й взгляд значения считаю тся неэквивалентны м и, потому что они относятся
к разны м объектам.

В следую щ ем ф рагм енте одно зн ач ен и е и зв л ек ается дваж ды . З атем при помощ и


встр о ен н о го м о д у л я N o d e a s s e rt м ы п ы таем ся п р о в ер и ть на эк в и в а л е н т н о с ть
объекты и их и дентиф икаторы , но в обоих случаях проверка дает отрицательны й
результат:

const A rticles = d b .c o lle c tio n ( 'a r tic le s ') ;


A rtic le s .f in d ( ) .th e n (a r tic le s => {
const a r t i c l e l = a r tic le s [ 0 ] ;
return A rticles
.findOne({_id: a rtic le 1 ._ id } )
.th e n (a rtic le 2 => {
a s s e rt.e q u a l(a rtic le 2 ._ id , a rtic le 1 ._ id );
});
});

Д л я эти х п р о вер о к вы д аю тся со о бщ ен и я об ош ибке, которы е сн ач ала каж утся


н еп онятн ы м и, так как ф акти чески е зн ач ен и я на первы й взгляд совпадаю т с ож и ­
даемы ми:

operator: equal
expected: 577f6b45549a3b991e1c3c18
actu al: 577f6b45549a3b991e1c3c18
operator: equal
expected:
{ _id: 577f6b45549a3b991e1c3c18, t i t l e : 'attractive-m oney' . . . }
actu al:
{ _id: 577f6b45549a3b991e1c3c18, t i t l e : 'attractive-m oney' . . . }

Д л я п р ав и л ьн о й п р о вер к и экв и в ал ен тн о сти следует и сп ользовать м етод e q u a l


объектов O bjectID , доступны й для всех _id. Также мож но вы полнить приведение
типа ид ентиф икаторов и сравнить их как строки или ж е воспользоваться методом
deepE quals — вроде того, которы й доступен во встроенном м одуле N ode assert:

a r tic le 1 ._ id .e q u a ls (a rtic le 2 ._ id );
S trin g (a rtic le 1 ._ id ) === S trin g (a rtic le 2 ._ id ); I Выдает исключение, если усл°вие
asse rt.d e e p E q u al(a rtic le 1 ._ id , a r tic le 2 ._ id ) ; -<— ' не выполняется.

И д ен ти ф и к а то р ы , перед ан н ы е д р ай в ер у N ode m ongodb, долж н ы п р ед ставл ять


собой O bjectID в ф орм ате BSO N . С трока преобразуется в O bjectID при помощ и
конструктора ObjectID:
8.8. MongoDB 247

const { ObjectID } = require('m ongodb');


const stringID = '577f6b45549a3b991e1c3c18';
const bsonID = new ObjectID(stringID);

Там, где это возможно, следует поддерживать формат BSON; затраты на марш ализа-
цию в строковую ф орму и обратно снижаю т вы игрыш по быстродействию, которого
M ongoD B стрем и тся достичь за счет работы с кл и ен тск и м и и д ентиф икаторам и
в ф орм ате BSON. З а дополнительной инф орм ацией о ф ормате B SO N обращ айтесь
по адресу http://bsonspec.org/.

8.8.6. Реплицированные наборы

Распределенная ф ункциональность M ongoD B в основном выходит за рам ки книги,


но в этом разделе будут кратко представлены основы работы с репл и ц и рован н ы ­
м и наборами. М ногие процессы m ongod могут вы п олн яться как у зл ы /у ч астн и ки
реплицированного набора (rep lica set). Р еп лиц ированн ы й набор состоит из одного
первичного у зл а и м нож ества вторичны х узлов. К аж дом у узлу реплицированного
набора долж ен бы ть назначен у ни кальны й порт и каталог д ля хранени я данных.
Э кзем п л я р ы не м огут совм естно и сп о льзо в ать порты и л и каталоги, а каталоги
долж ны сущ ествовать до запуска.

В л и сти н ге 8.11 д л я каж дого у ч астн и к а со зд ается у н и к ал ьн ы й каталог, а у ч аст ­


н и к и зап у ск аю тся на портах с посл ед о вател ьн о у в ел и ч и в аю щ и м и ся ном ерам и,
н ач и н ая с 27017. В озм ож но, вам стоит в ы п о л н я ть каж дую ком ан ду m ongod в н о ­
вой в к л ад к е тер м и н ал а без в ы п о л н ен и я в ф оновом реж и м е (б ез заверш аю щ его
си м во л а &).

Листинг 8.11. Запуск реплицированного набора


mkdir -p ./mongodata/db0 ./mongodata/db1 ./mongodata/db2

p k ill mongod < -------- Гарантирует отсутствие других выполняемыхэкземпляров mongod.


sleep 3 < -------- Дает существующим экземплярам время на завершение.

mongod --p o rt 27017 --dbpath ./rs0 -d ata/d b 0 --re p lS et rs0 &


mongod --p o rt 27018 --dbpath ./rs0 -d ata/d b 1 --re p lS et rs0 &
mongod --p o rt 27019 --dbpath ./rs0 -d ata/d b 2 --re p lS et rs0 &

П о сл е то го к а к р е п л и ц и р о в а н н ы й н а б о р за р аб о т ае т , M o n g o D B н ео б х о д и м о
п ро в ести н ек о то р у ю и н и ц и а л и за ц и ю . Н ео б х о д и м о п о д к л ю ч и ться к п о р ту э к ­
з е м п л я р а , к о т о р ы й ст ан е т п е р в ы м первичным у зл о м (2 7 0 1 7 по у м о л ч ан и ю ),
и вы зв ать м етод r s . i n i t i a t e ( ) , как показан о в л и сти н ге 8.12. З а т ем необходим о
д обавить каж ды й эк зем п л яр как уч астн и ка реп л и ц и рован н ого набора. О братите
в н и м ан и е на н ео б х о ди м о сть п еред ачи им ен и хоста той м аш и н ы , к к оторой вы
подклю чаетесь.
248 Глава 8. Хранение данных в приложениях

Листинг 8.12. Инициализация реплицированного набора


mongo --ev al " r s .in it i a te ( ) "
mongo --ev al "rs.ad d (''h o stn am e':2 7 0 1 7 ')" < ----- 1 Команда UNIXhostname выводит
mongo --ev al "rs.ad d (''h o stn am e':2 7 0 1 8 ')" | имя хоста текущей машины.
mongo --ev al "rs.ad d (''h o stn am e':2 7 0 1 9 ')"

К лиенты M ongoD B долж ны располагать инф орм ацией обо всех возм ож ны х участ­
никах реплицированного набора при подклю чении, хотя не все участники долж ны
быть подклю чены в текущ ий момент. П осле подклю чения вы мож ете использовать
кли ен та M ongoD B , как обычно. В листинге 8.13 показано, как создать р еп л и ц и ро­
ванны й набор с трем я участникам и.

Листинг 8.13. Подключение к реплицированному набору


const os = r e q u ir e ( 'o s ');
const { MongoClient } = require('m ongodb');
const hostname = os.hostname();

const members = [
'${hostname}:27018',
'${hostname}:27017',
'${hostname}:27019'
];
M ongoC lient.connect('m ongodb://${m em bers.join(',')}/test?replSet=rs0') ///
.then(db => {
db.ad m in ().rep lS etG etS tatu s().th en (statu s => { / / /
c o n so le.lo g (statu s);
d b .clo se();
});
});

Е сли на лю бом из узл о в m ongod прои зойдет сбой, систем а п родолж ит работать
(п р и усло ви и вы п о л н ен и я по кр ай н ей м ере двух эк зем п л яр о в). Если сбой п р о ­
изой дет на первичном узле, то втори ч н ы й узел будет автом атически повы ш ен до
первичного.

8.8.7. Уровень записи


M ongoD B предоставляет в распоряжение разработчика средства точного управления
ком пром иссам и по бы стродействию и безопасности, доступны е д ля разны х частей
ваш его прилож ения. Ч тобы использовать M ongoD B без неп риятн ы х сю рпризов,
важ но поним ать концепции уровней запи си и ч тен и я — особенно с увеличением
количества узлов в реплицированном наборе. В этом разделе рассматривается только
кон цепц ия значим ости записи, так как она яв л я ет ся более важной.

Уровень записи (w rite concern ) определяет количество экзем пляров m ongod, в к о ­


торы е долж на быть успеш но вы полнена запись, чтобы вся операция бы ла признана
8.8. MongoDB 249

успеш ной. Если значение не задано явно, по ум олчанию уровень запи си равен 1;
он гарантирует, что данны е бы ли записаны по край ней мере в один узел. М ожет
оказаться, что это значение не обеспечивает необходимого уровня защ иты к р и ти ­
ческих данных; если узел отклю чится до того, как данны е будут реплицированы
на других узлах, данны е могут быть потеряны .

М ож но (и даж е ж ел ател ьн о ) устан о ви ть нулевой уровень запи си, при котором


п ри лож ение вообщ е не ож идает никакого ответа:

d b .c o lle c tio n ('d a ta ').in s e rtO n e (d a ta , { w: 0 });

Н улевой уровень запи си обеспечивает наивы сш ее бы стродействие при м ин им аль­


ны х гарантиях устойчивости. О бы чно он при м ен яется только для врем енны х или
некритичны х данны х (наприм ер, д ля запи си ж урналов или кэш и рования).

Е сл и вы п одклю чены к р е п л и ц и р о в ан н о м у набору, м ож н о у стан ови ть уровень


зап и си вы ш е 1. Р е п л и к а ц и я по больш ем у коли честву узлов сниж ает вероятность
потери д ан н ы х за счет п о я в л е н и я д о п о л н и те л ьн о й зад ер ж к и п ри вы п о л н ен и и
операций:

d b .c o lle c tio n ('d a ta ').in s e rtO n e (d a ta , { w: 2 });


d b .c o lle c tio n ('d a ta ').in s e rtO n e (d a ta , { w: 5 });

У ровень зап и си ж ел ательн о м асш таби р о вать при и зм ен ен и и к ол и чества узлов


в кластере. Э та задача м ож ет реш аться динам ически M ongoD B , если вы брать для
уровн я запи си значение m a jo rity . О но гарантирует, что данны е будут записаны
более чем в 50% доступны х узлов:

d b .c o lle c tio n ('d a ta ').in s e rtO n e (d a ta , { w: 'm ajo rity ' });

У ровень за п и с и по у м олчани ю , р ав н ы й 1, м ож ет не обесп ечи вать адекватн ого


уровня защ иты д ля критических данных. Д анны е могут быть потеряны , если узел
отклю чится перед р епликацией на других узлах.

П ри н азн ачени и у р о вн я запи си вы ш е 1 вы полнен ие прод ол ж и тся только после


проверки того, что данны е сущ ествую т на нескольких экзем плярах m ongod. В ы ­
полнение нескольких экзем пляров на одной м аш ине повыш ает уровень защ иты, но
не помогает в случае общ есистемны х сбоев вроде нехватки дискового пространства
или ОЗУ. Ч тобы защ ититься от маш инны х сбоев, следует запустить экзем пляры на
н ескольких м аш инах и следить за тем, чтобы операции запи си распространились
по этим узлам; однако вы полнение запи си при этом зам едлится, и этот реж им не
защ итит от сбоев на уровне центров обработки данны х. Распределен ие узлов по
нескольким центрам обработки данны х обеспечивает защ иту в случае выхода цен­
тров из строя, но обеспечение репликации этих данны х меж ду центрами обработки
данны х серьезно повлияет на быстродействие.
250 Глава 8. Хранение данных в приложениях

К ак обычно, чем больш е защ иты вы добавляете, тем медленнее и сложнее становит­
ся ваш а система. Э та проблем а не я в л яется специф ической только д ля M ongoD B;
она присущ а всем хранилищ ам данны х без исклю чения. И деального реш ен ия не
сущ ествует, и вам придется вы брать при ем лем ы й уровень ри ска д ля различны х
частей ваш его прилож ения.

З а д о п о л н и тел ьн о й и н ф о р м ац и ей о том, как работает р еп л и к ац и я в M ongoD B ,


обращ айтесь к следую щ им ресурсам:

О https://docs.m ongodb.com /m anual/faq/replica-sets/;

О https://docs.m ongodb.com /m anual/faq/concurrency/.

8.9. Хранилища «ключ-значение»


К аж д ая запись в х рани ли щ е «клю ч-значение» состоит из одного клю ча и од н о­
го значения. Во м ногих систем ах « клю ч-зн ачени е» зн ач ен и е м ож ет относиться
к лю бом у типу данных, иметь любую длину или структуру. С точки зрен и я базы
данны х значения явл яю тся непрозрачны м и и атомарными: базе данны х неизвестен
тип данных, и к этим данны м м ож но обращ аться только как к единому целом у без
разбиения на части. С равните с хранением значений в реляционн ы х базах данных:
дан ны е х р ан я тся в таблицах, которы е содерж ат строки данны х, разбиты е на з а ­
ранее определенны е столбцы. В храни ли щ ах «клю ч-значение» ответственность за
управление ф орм атом данны х возлагается на прилож ение.

Х ранилищ а «клю ч-значение» часто встречаю тся в частях прилож ения, критичны х
по бы стродействию . В идеале зн ачения располагаю тся в такую структуру, чтобы
д ля вы полнен ия задачи требовалось м ин им альное количество операций чтения.
Х ранилищ а «клю ч-значение» обладают более простой функциональностью запроса,
чем другие типы баз данных. В идеале слож ны е запросы долж ны обрабаты ваться
заранее; в противном случае они долж ны вы полняться в прилож ении, а не в базе
данных. Это ограничение долж но обеспечивать понятны е и предсказуем ы е хар ак ­
теристи ки бы стродействия.

Самые популярны е хранилищ а «клю ч-значение» — такие, как Redis и Memcached, —


часто исп ользую тся д л я врем енного х р ан ен и я дан ны х (п р и выходе из процесса
данны е теряю тся). О тказ от записи на диск — один из лучш их способов повы ш ения
быстродействия. Это решение может стать приемлемым для функций, позволяю щ их
восстановить или потерять данные без особых проблем (например, для кэш ирования
и пользовательских сеансов).

О б ы ч н о с ч и тается , что х р а н и л и щ а « к л ю ч -зн ач е н и е » не м огут и с п о л ь з о в а т ь ­


ся д л я перви чн о го х р а н е н и я данны х, но это не всегда так. М ноги е х р ан и л и щ а
«клю ч-зн ачени е» обеспечиваю т такую ж е устойчивость, как и «настоящ и е» базы
данны х.
8.10. Redis 251

8.10. Redis
Redis — популярное хранилищ е структур данны х в памяти. И хотя многие разработ­
ч и ки считаю т Redis хранилищ ем «клю ч-значение», клю чи и зн ачения составляю т
лиш ь небольшое подмножество ф ункций Redis среди разнообразных полезны х базо­
вых структур данных. Н а рис. 8.3 приведена статистика использования redis в npm.

II_ П I ®ІІ_ П Н 3 dependencies version 2 6.2


2,260 dependents ^ updated 2^ nonths ago

Рис. 8.3. Статистика использования Redis

К числу структур данных, встроенны х в Redis, относятся:

О строки;

О хеши;

О списки;

О множ ества;

О сортированны е множ ества.

Redis такж е вклю чает в себя м ного других полезны х возмож ностей:

О растровые данные — прям ы е м ан и п у л яц и и с битам и в значениях;

О геопространственные индексы — хранение геопространственны х данны х с р а ­


диальны м и запросам и;

О каналы — м еханизм поставки данны х «п уб ли кац и я/п од п и ска»;

О срок жизни (TTL) — значения могут настраиваться со сроком жизни, по истечении


которого они автом атически уничтож аю тся;

О вытеснение LRU — (нео б язател ьн о е) вы теснение давно не использовавш ихся


значений д ля м аксим ально эф ф ективного исп ользован ия памяти;

О HyperLogLog — вы сокопроизводительная аппроксим ация для вы числения м ощ ­


ности множ ества при небольш их затратах пам яти (без необходимости хранения
каж дого элем ента);

О репликация, кластеризация и сегментация — горизонтальное масш табирование


и устойчивость данных;

О сценарии Lua — расш ирение R edis нестандартны м и командами.

В этом разделе при водятся списки ком анд Redis. Н е стоит относиться к ним как
к справочникам ; они даю т лиш ь некоторое представление о том, что мож но делать
252 Глава 8. Хранение данных в приложениях

в Redis. R edis — невероятно м ощ ны й и гибкий инструмент; за подробностям и об­


ращ айтесь по адресу http://redis.io/commands.

8.10.1. Установка и настройка


Redis может устанавливаться при помощ и системного менеджера пакетов. В macOS
R edis легко устан авли вается в H om ebrew :

brew i n s t a ll redis

Д л я запуска сервера используется исп олн яем ы й ф айл redis-server:

re d is-se rv e r /u s r /lo c a l/e tc /re d is .c o n f

П о ум олчанию сервер вы полняет прослуш ивание на порту 6379.

8.10.2. Выполнение инициализации

Э кзем п ляр кли ен та R edis создается ф ункц ией c r e a te C lie n t из npm -пакета redis:

const red is = r e q u ir e ( 'r e d is ') ;


const db = red is.createC lien t(6 3 7 9 , '1 2 7 .0 .0 .1 ');

В ар гу м ен тах ф у н к ц и и п еред аю тся порт и хост. Н о если вы зап у ск аете сервер


R edis на порту по ум олчанию локальной маш ины , ни каки х аргументов передавать
вообщ е не придется:

const db = re d is.c re a te C lie n t();

Э кзем пляр клиента Redis расш иряет EventEm itter, поэтому вы можете присоединять
слуш ателей для различны х статусны х собы тий Redis, как показано в листинге 8.14.
Вы мож ете сразу начинать вводить команды д ля клиента; введенны е команды бу­
ф еризую тся до готовности подклю чения.

Листинг 8.14. Подключение к Redis и прослушивание статусных событий


const red is = r e q u ir e ( 'r e d is ') ;
const db = re d is.c re a te C lie n t();
db .o n ('co n n ect', () => console.log('R edis c lie n t connected to s e r v e r .') ) ;
d b .o n ('re a d y ', () => console.log('R edis server is re a d y .'));
d b .o n ( 'e r r o r ', e rr => co n so le.erro r('R ed is e r r o r ', e rr ) ) ;

О бработчик e r r o r срабаты вает при возни кн овен ии проблемы с подклю чением или
клиентом . Если при срабаты вании собы тия e r r o r обработчик не присоединен, про­
цесс п р и л о ж ен и я вы дает ош ибку и аварийн о заверш ается; это свойство присущ е
всем объектам E ventE m itter в Node. Если при подклю чении происходит сбой, а об­
работч и к e r r o r определен, то кли ен т R edis п ы тается восстан овить подклю чение.
8.10. Redis 253

8.10.3. Работа с парами «ключ-значение»


Redis мож ет использоваться в качестве обобщ енного храни ли щ а «клю ч-значение»
д л я строк и п р о и зв о л ьн ы х д во и чн ы х данны х. Д л я ч тен и я и зап и си пар «клю ч-
значение» использую тся методы s e t и g e t соответственно:

d b .s e t( 'c o lo r ', 'r e d ', e rr => {


i f (e rr) throw e rr;
});
d b .g e t( 'c o lo r ', (e rr, value) => {
i f (e rr) throw e rr;
co n so le .lo g ('G o t:', value);
});

Если указать сущ ествую щ ий ключ, значение будет заменено. Если вы попытаетесь
прочитать значение с несущ ествую щ им клю чом, значение будет равно n u ll; такое
обращ ение не считается ош ибкой.
С ледую щ ие ком анды могут исп ользоваться д ля чтен и я и изм ен ен ия значений:

О append;

О decr;

О decrby;

О get;

О g e tra n g e ;

О g e ts e t;

О in c r;

О in crb y ;

О in c r b y f lo a t;

О mget;

О mset;

О msetnx;

О p setex ;

О s e t;

О s e te x ;

О setn x ;

О se tra n g e ;

О s trle n .
254 Глава 8. Хранение данных в приложениях

8.10.4. Работа с ключами


Ч тобы проверить, сущ ествует ли клю ч, исп ользуй те метод e x i s t s . О н работает
с лю бы м типом данных:

d b .e x is ts ( 'u s e r s ', (e rr, doesExist) => {


i f (e rr) throw e rr;
co n so le.lo g ('u sers e x i s t s : ', doesExist);
});

К ром е e x i s t s , для работы с клю чам и м огут использоваться следую щ ие команды


(н езави си м о от типа зн ач ен и я — ком анды работаю т со строкам и, м нож ествам и,
спискам и и т. д.):

О d e l;

О e x is ts ;

О rename;

О renamenx;

О s o r t;

О scan;

О type.

8.10.5. Кодирование и типы данных


С ервер Redis хранит клю чи и зн ачения в виде двоичны х объектов; это представле­
ние не зависи т от способа кодирован ия значений, передаваем ы х клиенту. Л ю бая
допустим ая строка Ja v a S c rip t (U C S 2 /U T F 1 6 ) мож ет исп ользоваться как д ей стви ­
тельны й клю ч или значение:

d b .s e t( 'g r e e tin g ', ' # # ' , re d is .p r in t) ;


d b .g e t('g re e tin g ', re d is .p r in t) ;
d b .s e t( 'ic o n ', '? ' , re d is .p r in t) ;
d b .g e t( 'ic o n ', re d is .p r in t) ;

П о ум олчанию клю чи и значения преобразую тся в строки при записи. Н апример,


если задать клю ч в числовом виде, при попы тке получения того ж е клю ча вы п олу­
чите строку:

d b .s e t( 'c o lo r s ', 1, (e rr) => {


i f (e rr) throw e rr;
});
d b .g e t( 'c o lo r s ', (e rr, value) => {
i f (e rr) throw e rr;
console.log('G ot: %s as %s', value, typeof value); < ----- Значение будет иметь тип string.
});
8.10. Redis 255

К лиент R edis незам етно преобразует числа, логические значения и даты в строки;
кром е того, он спокойно приним ает объекты буферов. П опы тка задать в качестве
зн ачения лю бой другой тип Ja v a S c rip t (наприм ер, O bject, A rray, RegExp) приводит
к выдаче предупреж дения, к котором у стоит прислуш аться:

d b .s e t( 'u s e r s ', {}, re d is .p r in t) ;


Deprecated: The SET command contains a argument of type Object.
This is converted to "[object O bject]" by using .to S trin g () now
and w ill return an e rro r from v.3.0 on.
Please handle th is in your code to make sure everything works
as you intended i t to .

В будущем это будет рассм атриваться как ошибка. П рилож ение долж но проследить
за тем, чтобы клиенту R edis передавались допустим ы е типы.

Массивы с одним и несколькими значениями


П ри попы тке при сваи вания м ассива значений клиент вы дает загадочную ош ибку
«R eplyE rror: E R R syn tax error»:

d b .s e t( 'u s e r s ', [ 'A lic e ', 'B ob'], re d is .p r in t) ;

О днако д ля массива, содерж ащ его только одно значение, ош ибки не будет:

d b .s e t( 'u s e r ', [ 'A lic e '] , re d is .p r in t) ;


d b .g e t( 'u s e r ', re d is .p r in t) ;

Такие ош ибки нередко про явл яю тся только в усл ови ях реальной эксплуатации.
О ни остаю тся незам еченны м и, если тестовы й пакет генерирует только м ассивы
с одним значением, типичные для усеченных тестовых данных. Будьте внимательны!

Буферы с двоичными данными


Redis позволяет хранить произвольны е байтовы е данные; ф актически это означает
возможность сохранения лю бых типов данных. Клиент Node поддерж ивает эту воз­
м ож ность за счет специальной обработки типа N ode B uffer. П ри передаче буфера
клиенту Redis в качестве клю ча или значения байты передаю тся в неизменном виде
серверу Redis. Таким образом пред отвращ ается случайное повреж дение данны х
и сниж ение бы стродействия из-за лиш них преобразований м еж ду строкам и и бу­
ферами. Н апример, если вы захотите записать данны е с диска и ли из сети прямо
в Redis, непосредственная запись буферов в Redis вы полняется более эф ф ективно,
чем с предварительны м преобразованием их в строки.

В Redis недавно бы ли добавлены команды д ля м анип уляц ий с отдельны ми битами


строковы х значений, которы е могут пригодиться при работе с буферами:

О b itc o u n t;

О b itfie ld ;
256 Глава 8. Хранение данных в приложениях

О b ito p ;

О s e tb it;

О b itp o s .

БУФЕРЫ

Б у ф е р ы — данны е, ко то р ы е во звр ащ аю тся по ум олчан и ю б азовы м и A P I


N ode д ля работы с ф айлам и и сетям и. О ни представляю т собой кон тей не­
ры д ля см еж ны х блоков двоичны х данных. П оддерж ка буферов появилась
в N ode еще до того, как в Ja v a S c rip t появились встроенны е типы д ля работы
с двоичны м и данны м и (U int8A rray, F loat32A rray и т. д). В наш и дни буферы
реализую тся в N ode специализированны м субклассом U int8A rray. A P I для
работы с буф ерам и доступен в N ode глобально; чтобы пользоваться им, вам
не придется что-то делать дополнительно.
См. h ttp s ://g ith u b .c o m /n o d e js /n o d e /b lo b /m a s te r/lib /b u ffe r.js .

8.10.6. Работа с хешами


Хеш представляет собой коллекцию пар «клю ч-значение». Команда hmset получает
клю ч и объект, представляю щ ий набор пары «клю ч-значение» в хеше. Д л я получе­
ни я пар «клю ч-значение» в виде объекта используется команда hmget (листинг 8.15).

Листинг 8.15. Хранение данных в элементах хешей Redis


db.hm set('cam ping', { -<-------- Задает пары «ключ-значение», входящие в хеш.
sh e lte r: '2-person t e n t ',
cooking: 'campstove'
}, re d is .p r in t) ;
db.hget('cam ping', 'cooking', (e rr, value) => { <.----- Получает значение для «camping.cooking».
i f (e rr) throw e rr;
console.log('W ill be cooking w ith :', value);
});
db.hkeys('cam ping', (e rr, keys) => { < -------- Получает ключихеша в виде массива.
i f (e rr) throw e rr;
keys.forEach(key => co n so le.lo g (' ${key}'));
});

Н е д опускается хранени е влож енн ы х объектов в хеш е R edis — поддерж ивается


только один уровень клю чей и значений.

Д л я работы с хеш ам и использую тся следую щ ие команды:

О hdel ;

О h e x is ts ;
8.10. Redis 257

О hget;
О h g e ta ll;
О hin crb y ;
О h in c r b y f lo a t;
О hkeys;
О hlen;
О hmget;
О hmset;
О h se t;
О hsetnx;
О h s tr le n ;
О h v a ls ;
О hscan.

8.10.7. Работа со списками


Список представляет собой упорядоченную коллекцию строковых значений. Список
м ож ет содерж ать несколько коп ий одного значения. Н а концептуальном уровне
списки бли зки к массивам. Ч ащ е всего списки использую тся за их способность м о­
делировать поведение структур данны х стека (L IF O : «последним приш ел, первым
вы ш ел») или очереди (F IF O : «первы м приш ел, первы м вы ш ел»).

С ледую щ ий ф рагм ент дем онстрирует сохранение и вы борку значений из списка.


К ом анда lp u sh добавляет значение в список. К ом анда lra n g e извлекает диапазон
значений по начальному и конечному индексу. Аргумент -1 в следующем фрагменте
обозначает последний элем ент списка, поэтом у в этом варианте и сп ользован и я
lra n g e извлекаю тся все элем енты списка:

c lie n t.lp u s h ( 'ta s k s ', 'P ain t the bikeshed r e d . ', re d is .p rin t);
c lie n t.lp u s h ( 'ta s k s ', 'P ain t the bikeshed g re e n .', re d is .p rin t);
c lie n t.lr a n g e ( 'ta s k s ', 0, -1, ( e rr, items) => {
i f (e rr) throw e rr;
item s.forEach(item => co n so le.lo g (' $ {item }'));
});

С пи ски не содерж ат встроенны х средств проверки при сутствия значения в списке


или определения индекса конкретного значения в списке. Вы можете вручную пере­
брать элем енты списка д ля получения нуж ной инф орм ации, но это крайне н е эф ­
ф ективное решение, которого следует избегать. Если вам нуж на функциональность
такого рода, лучш е вы брать другую структуру данны х (наприм ер, м нож ество) —
а возможно, даж е использовать ее в дополнение к списку. Д убли рован ие данны х
258 Глава 8. Хранение данных в приложениях

меж ду нескольким и структурам и часто ж елательно для исп ользован ия различны х


характеристик бы стродействия.

Д л я работы со спискам и предназначены следую щ ие команды:


О b lp o p ;
О brpop ;
О lin d e x ;
О lin s e rt;
О lle n ;
О lpop;
О lpush;
О lpushx;
О lra n g e ;
О lrem;
О ls e t;
О ltrim ;
О rpop;
О rpush;
О rpushx.

8.10.8. Работа со множествами


М ножество представляет собой неупорядоченную коллекцию уникальных значений.
П роверка принадлеж ности, а такж е операции д обавления и удален ия элементов
из м нож ества вы полняю тся за врем я O (1 ); это означает, что множ ество я вл яется
вы сокопроизводительной структурой, подходящ ей д ля м ногих задач:

db.sadd('adm ins', 'A lic e ', re d is .p r in t) ;


db.sadd('adm ins', 'B ob', re d is .p r in t) ;
db.sadd('adm ins', 'A lic e ', re d is .p r in t) ;
db.smembers('admins', (e rr, members) => {
i f (e rr) throw e rr;
console.log(members);
});

Д л я работы с м нож ествам и Redis предназначены следую щ ие команды:


О sadd;
О scard ;
О s d iff;
8.10. Redis 259

О s d iffs to re ;
О s in te r;
О s in te rs to re ;
О sismember ;
О smembers ;
О spop;
О srandmember ;
О srem;
О sunion;
О s u n io n s to re ;
О sscan.

8.10.9. Реализация паттерна «публикация/подписка»


на базе каналов
R edis вы ходит за р ам к и т р ад и ц и о н н о й р о л и х р ан и л и щ а данны х, п р ед оставл яя
в распоряж ени е разработчика каналы (chan n els). Этот м еханизм передачи данны х
реал и зу ет ф ун кц и он альн ость п у б л и к ац и и /п о д п и ск и ; его кон ц еп туальн ая схема
изображ ена на рис. 8.4. К аналы хорош о подходят д ля при лож ений реального вре­
м ени — таких, как чаты и игры.

Рис. 8.4. Каналы Redis предоставляют простое решение для стандартного сценария
передачи данных

К л и ен т R edis м ож ет по д п и сы ваться на кан алы и л и п уб л и к овать в них сообщ е­


ния. С ообщ ение, опубликованное в канале, будет доставлено всем подписчикам.
П убликатору не обязательно располагать ин ф орм аци ей о подписчиках, как и под ­
писчикам — о публикаторах. И м енно логическая и зол яц и я меж ду подписчикам и
и публикаторам и делает этот паттерн столь м ощ ны м и элегантным.

В листинге 8.16 приведен прим ер исп ользован ия ф ункциональности п у б л и к ац и и /


подписки в Redis д ля р еализаци и чат-сервера T C P /IP .
260 Глава 8. Хранение данных в приложениях

Листинг 8.16. Реализация простого чат-сервера на базе функциональности


публикации/подписки в Redis
Определяет логику настройки для каждого
const net = r e q u ir e ( 'n e t') ; пользователя, подключающегося кчат-серверу.
const red is = r e q u ir e ( 'r e d is ') ;
Создает клиента подписки
const server = net.createS erver(socket => { ■<— для каждого пользователя.
const subscriber = re d is .c re a te C lie n t();
su b sc rib e r.su b sc rib e ('m a in '); < -------- Подписывается на канал.
su b scrib er. o n (' message ' , (channe1, message) => { -<---- Когда сообщение принимается
socket.w rite (' Channel ${channel}: ${message}' ) ; из канала, оно выводится для
}); пользователя.
const publisher = re d is .c re a te C lie n t(); ■< Создает клиента публикации
s o c k e t.o n ('d a ta ', data => { I для каждого пользователя.
p u b lish er.p u b lish ('m ain ', d ata); ■<-
Публикует сообщение,
}); введенное пользователем.
so c k e t.o n ('e n d ', () => {
su b scrib er.u n su b scrib e('m ain '); •<-
su b scrib er.en d (tru e); Если пользователь отключается,
pu b lish er.en d (tru e); то подключение клиента завершается.
});
});

se rv e r.liste n (3 0 0 0 );

8.10.10. Повышение быстродействия Redis


N p m -пакет hiredis связы вает J a v a S c rip t с парсером п ротокол а из оф и ц и альн о й
библиотеки C H iredis. H iredis м ож ет значительно повы сить бы стродействие п р и ­
лож ений Redis на базе Node, особенно если вы используете операции sunion, s in te r ,
lra n g e и zrange c больш им и базам и данных.

Ч тобы использовать hiredis, просто установите пакет вместе с пакетом redis в вашем
прилож ении; пакет N ode redis обнаруж ит его и автом атически использует его при
следую щ ем запуске:

npm in s ta l l h ired is --save

У hiredis есть свои недостатки. Так как пакет ком п и л и руется из кода C, при по­
строении hiredis для некоторы х платф орм вы мож ете столкнуться с ослож нениям и
и ли ограничениям и. К ак и со всеми встроенны м и дополнениям и, возможно, вам
придется заново построить hiredis ком андой npm r e b u ild после об новления Node.

8.11. Встроенные базы данных


В строенная база данны х не требует установки или адм и нистрировани я внеш них
серверов. О на работает прям о внутри процесса прилож ения. В заим одействие со
8.12. LevelDB 261

встроен ной базой дан ны х обы чно в ы п о л н яется через прям ы е вы зовы процедур
в ваш ем прилож ении, а не через каналы IP C или по сети.

Во м ногих ситуациях прилож ение долж но быть автономны м, поэтому встроенная


база данны х становится единственно возм ож ны м выходом (например, для м оби ль­
ны х или настольны х при лож ений ). В строенны е базы данны х такж е могут исп оль­
зоваться с веб-серверами; они часто обеспечиваю т реализацию таких ф ункций, как
сеансы пользователей или кэш ирование, а иногда даж е использую тся в качестве
первичного храни ли щ а данных.

Н еко то р ы е встроен ны е базы данны х, часто исп ользуем ы е в п ри лож ен и ях N ode


и Electron:
О SQ Lite;

О LevelDB;

О RocksD B;

О Aerospike;
О EJD B;

О NeD B;

О LokiJS;

О Lowdb.

N eD B , LokiJS и Low db написаны на «чистом» Ja v a S c rip t и поэтом у естественным


образом встраиваю тся в прилож ения N o d e/E lectro n . М ногие встроенны е базы д ан ­
ны х представляю т собой простые хранилищ а пар «клю ч-значение» или документов,
хотя встроенное реляционное хранилищ е S Q L ite яв л яется заметны м исклю чением.

8.12. LevelDB
LevelDB — встр аи ваем о е х р ан и л и щ е « клю ч-зн ачени е», р азработан н ое в начале
2011 года ком п анией G oogle и и зн ачально предназначенное д ля исп ользован и я
в качестве вспом огательного храни ли щ а р еализаци и IndexedD B в Chrom e. А рхи­
тектура LevelD B строится на кон цепц иях базы данны х B igtable ком п ании Google.
LevelD B м ож но сравнить с таким и базам и данных, как Berkeley DB, Tokyo/K yoto
C ab in et и A erospike, но в контексте этой книги LevelD B мож но рассм атривать как
встроенную версию Redis с миним альны м набором функций. К ак и многие встроен­
ные базы данных, LevelDB не я вл яется м ногопоточной системой и не поддерживает
использование нескольким и экзем плярам и общего ф айлового хранилищ а, поэтому
эта систем а не будет работать в распределен ной среде без прилож ения-обертки.

LevelD B хранит произвольны е байтовы е массивы, отсортированны е в лексикогра­


ф ическом порядке по ключу. З н ач е н и я сж им аю тся с использованием алгоритма
262 Глава 8. Хранение данных в приложениях

S nappy ко м п ан и и Google. Д ан н ы е всегда сохран яю тся на диске; общ ая емкость


данны х не ограничивается объемом оперативной пам яти на компью тере (в отличие
от систем, хранящ их данны е в пам яти, таких как Redis).

L evelD B п о д д ер ж и в ает н еб о л ьш о й н абор п о н ятн ы х операторов: G et, P u t, D el


и Batch. LevelDB также умеет сохранять «снимки» текущ его состояния базы данных
и создавать двусторонние итераторы д ля перебора данны х в прям ом и обратном
направлении. П ри создании итератора неявно создается снимок данных; данные,
которые видны итератору, не могут изм еняться последую щ ими операциями записи.

LevelD B заклады вает ф ундам ент д ля других баз данных. К оличество засл у ж и ва­
ю щ их вни м ан ия ответвлений LevelD B о бъясн яется простотой системы LevelDB:

О R ocksD B (Facebook);

О H yperL evelD B (H y p erd ex );

О R iak (B asho);

О leveldb-m cpe (M ojang, создатели M inecraft);

О b itc o in /le v e ld b (д л я проекта b itcoind).

З а дополнительной информацией о LevelDB обращайтесь по адресу http://leveldb.org/.

8.12.1. LevelUP и LevelDOWN


П оддерж ка L evelD B в N ode пред о ставл яется пакетам и L evelU P и LevelD O W N ,
которы е написал один из основателей N ode, активн ы й разработчик из А встралии
Род Вэгг (R o d Vagg). L evelD O W N — простая, м и н и м ал ьн ая прослойка C + + для
LevelD B в Node; вряд ли вам придется взаимодействовать с ней напрямую . LevelU P
и н кап су л и р у ет L evelD O W N A P I в более удобном и идиом атичном ин терф ейсе
Node, до бавл яя поддерж ку кодирован ия клю чей и значений, JS O N , буф еризации
записи до откры тия базы данны х и инкапсуляции интерф ейса итераторов LevelDB
в потоках Node. П опулярность L evelU P в npm продем онстрирована на рис. 8.5.

Рис. 8.5. Статистика использования LevelUP в npm

8.12.2. Установка
Главное достоинство исп ользован ия LevelD B в п ри лож ениях N ode — встроенная
природа базы данных: все необходим ое устан авли вается исклю чительно из npm.
8.12. LevelDB 263

Вам не придется устанавливать дополнительное П О ; просто введите следую щую


команду, и все будет готово к использованию LevelDB:

npm in s t a l l lev el --save

П акет level п р ед ставляет собой простую вспом огательную обертку д л я пакетов


L evelU P и LevelD O W N , предоставляю щ ую A P I L evelU P д ля исп ользован ия вн у ­
тренней подсистемы LevelD ow n. Д окум ентаци я A P I LevelUP, предоставляем ого
пакетом level, находится в R eadm e-ф айле LevelUP:

О www.npmjs.com/package/levelup;

О www.npmjs.com/package/leveldown.

8.12.3. Обзор API

О сновны е методы кли ен та LevelD B д ля хранени я и чтени я значений:

О d b .p u t(k e y , v a lu e , c a llb a c k ) — сохраняет значение с заданны м ключом;

О d b .g e t( k e y , c a llb a c k ) — читает значение с заданны м ключом;

О d b .d e l( k e y , c a llb a c k ) — удаляет значение с заданны м ключом;

О d b . b a t c h ( ) .w r i t e ( ) — вы полняет пакетны е операции;

О d b .c re a te K e y S tre a m (o p tio n s) — создает поток клю чей из базы данных;

О d b .c re a te V a lu e S tre a m (o p tio n s ) — создает поток значений из базы данных.

8.12.4. Инициализация

П ри и н и ц и ал и зац и и level необходим о предостави ть путь к каталогу, в котором


будут храни ться данные, как показано ниже; если каталог не существует, то он бу­
дет создан. В сообщ естве сущ ествует неф орм альное правило: присваивать таком у
каталогу расш ирение .db (наприм ер, ./app.db).

Листинг 8.17. Инициализация базы данных level


const level = r e q u ir e ( 'le v e l') ;
const db = le v e l( './a p p .d b ', {
valueEncoding: 'jso n '
});

П осле вы зова l e v e l ( ) возвращ аем ы й экзем пляр L evelU P нем едленно готов к си н ­
хронном у получению команд. Команды, введенны е перед откры тием хранилищ а
LevelD B, буф еризую тся до его откры тия.
264 Глава 8. Хранение данных в приложениях

8.12.5. Кодирование ключей и значений


Так как LevelD B может использовать произвольны е данные любого типа как в клю ­
чах, так и в значениях, за сериализацию и десериализацию вы зы ваю щ их данны х
отвечает прилож ение. LevelU p можно настроить для автоматического кодирования
клю чей и значений следую щ их типов данных:

О u tf8 ;

О jso n ;

О b in a ry ;

О id;

О hex;

О a s c ii ;

О base64;

О ucs2;

О u tf1 6 le .

П о ум олчанию клю чи и зн ач ен и я кодирую тся в строках U T F -8. В листинге 8.17


клю чи остаю тся строкам и U TF-8, а значения кодирую тся/декодирую тся в формате
JSO N . К одирование JS O N позволяет хранить и читать структурированные значения
(наприм ер, объекты или м ассивы ) по аналогии с храни ли щ ам и докум ентов (т а к и ­
ми, как M ongoD B ). Н о учтите, что в отличие от реальны х храни ли щ документов
в базовой версии LevelD B не существует возмож ности обратиться к клю чам внутри
значений; значения непрозрачны . П ользователи такж е могут определять собствен­
ны е кодировки — например, д ля поддерж ки другого структурированного ф орм ата
(наприм ер, M essagePack).

8.12.6. Чтение и запись пар «ключ-значение»


Б азо в ы й A P I прост: вы зов p u t( k e y , v a lu e ) и сп ользуется д л я зап и си значения,
g e t(k e y ) — д ля ч тен и я и d e l(k e y ) — д ля уд ал ен и я зн ачения (л и сти н г 8.18). Код
в листинге 8.18 следует присоединить к коду из листинга 8.17; полны й прим ер со­
д ерж ится в ф айле ch08-data-bases/listing8_18/index.js в архиве исходного кода книги.

Листинг 8.18. Чтение и запись значений


const key = 'u s e r ';
const value = {
name: 'A lice'
};

db.put(key, value, e rr => {


8.12. LevelDB 265

i f (e rr) throw e rr;


db.get(key, (e rr, re s u lt) => {
i f (e rr) throw e rr;
co n so le.lo g ('g o t v a lu e :', re s u lt);
db.del(key, (e rr) => {
i f (e rr) throw e rr;
conso le.lo g ('v alu e was d e le te d ');
});
});
});

Е сли сохранить знач ен и е с уж е сущ ествую щ им клю чом, старое знач ен и е будет
перезапи сано. П о п ы тка ч тен и я зн а ч е н и я с несущ ествую щ и м клю чом приведет
к ош ибке. О бъект ош ибки будет относиться к конкретном у типу N otFoundError то
специальны м свойством e rr.n o tF o u n d , по котором у его мож но отличить от других
типов ош ибок. Н а первы й взгляд это необычно, но поскольку в LevelD B нет встро­
енного метода для проверки сущ ествования, L evelU P необходимо как-то отличать
несущ ествую щ ие значения от неопределенны х. В отличие от g et, попы тка вызвать
d e l с несущ ествую щ им клю чом не приводит к ошибке.

Листинг 8.19. Получение значений с несуществующими ключами


d b .g e t('th is -k e y -d o e s -n o t-e x is t', (e rr, value) => {
i f (e rr && !err.notFound) throw e rr;
i f (e rr && err.notFound) return console.log('V alue was not fo u n d .');
console.log('V alue was fo u n d :', value);
});

Все операции чтени я и записи данны х получаю т необязательны й аргумент o p tio n s


д ля переопределения парам етров кодирован ия текущ ей операции (л и сти н г 8.20).

Листинг 8.20. Переопределение параметров кодирования для конкретных операций


const options = {
keyEncoding: 'b in a ry ',
valueEncoding: 'hex'
};
db.put(new Uint8Array([1, 2, 3 ]), '0xFF0099', options, (err) => {
i f (e rr) throw e rr;
db.get(new Uint8Array([1, 2, 3 ]), options, ( e rr, value) => {
i f (e rr) throw e rr;
console.log(value);
});
});

8.12.7. Заменяемые подсистемы базы данных


П олож ительны й побочны й эф ф ект разделения L evelU P /L evelD O W N заклю чается
в том, что L evelU P не ограничивается использованием LevelDB в качестве внутрен­
ней подсистемы базы данны х. Л ю бая база данны х, которая мож ет быть упакована
266 Глава 8. Хранение данных в приложениях

в M em D ow n A PI, мож ет исп ользоваться в качестве подсистемы хранения данных


для LevelUP. Это позволяет использовать один A PI для взаимодействия со многими
храни ли щ ам и данных.

Н екоторы е прим еры альтернативны х подсистем баз данных:

О M ySQ L;

О Redis;

О M ongoD B ;

О ф айлы JS O N ;

О электронны е таблицы Google;

О AWS D ynam oD B ;

О табличное храни ли щ е W indow s Azure;

О веб-хранилищ е браузера (In d ex ed D B /lo calS to rag e).

В озмож ность простой зам ены среды хранения данны х и даж е написания собствен­
ной подсистемы баз данны х означает, что вы можете использовать один последова­
тельны й набор A P I баз данны х и инструм ентов во м ногих ситуациях и условиях.
О дин A P I баз данны х на все случаи!

Среди альтернативных подсистем баз данных часто используется система memdown,


ко то р ая х рани т зн а ч е н и я исклю чи тел ьн о в пам яти, а не на диске (по аналогии
с S Q L ite в реж им е работы в пам яти). Д анная возм ож ность особенно полезна в т е ­
стовой среде для сни ж ения затрат на организацию тестирован ия и переналадку.

Ч тобы вы полни ть л и сти н г 8.21, убедитесь в том, что у вас устан овлен ы пакеты
L evelU P и m emdown:

npm in s ta l l --save levelup memdown

Листинг 8.21. Использование memdown с LevelUP


const lev el = re q u ire ('le v e lu p ')
const memdown = require('memdown') п г г
Для memdown «путем» может быть любая
строка, так какдиск не используется.
const db = l e v e l ( './ l e v e l - a r t i c l e s .d b ', {-<-----
keyEncoding: 'js o n ',
valueEncoding: 'js o n ',
db: memdown < Единственное реальное отличие -
}); передача memdown в параметре db.

В этом примере можно было использовать тот же пакет level, использовавш ийся ранее,
потому что он представляет собой обертку для LevelUP. Н о если вы не используете
пакет LevelD O W N на базе LevelDB, входящ ий в поставку level, вы можете просто ис­
пользовать LevelUP и избежать двоичной зависимости от LevelDB через LevelDOWN.
8.12. LevelDB 267

8.12.8. Модульная база данных


Бы стродей стви е и м ин им ализм L evelD B наш ли отклик у м ногих разработчиков
N ode и породили движ ение м одульны х баз данны х в сообщ естве Node. К онцепция
заклю чалась в том, чтобы разработчик мог точно выбрать, какие возмож ности нуж ­
ны в его прилож ении, и адаптировать базу данны х под ваш кон кретны й сценарий
использования.

898 results for ‘leveldb’

level-js maxogden
leveldown/TeveMb library for browsers using indexedDB

★ 10 V22.4

level, leveldb

level-http2 aaaristo
Access a Icvddb instance via HTTP2

★ 0 vO.O.4

leveldb. http2, http

leveldown-mobile obastemur
A Node.js LevclOB binding (or Android. iOS and Windows UWP. primary backend for LevdUP

★ 0 vl.1.1

^ leveldb. level

Рис. 8.6. Примеры сторонних пакетов LevelDB в npm

Н есколько примеров м одульной ф ункциональности LevelDB, доступной в пакетах


npm:
О атомарны е обновления;

О автом атическое увеличение ключей;

О геопространственны е запросы;

О оперативно обновляем ы е потоки;

О вы теснение LRU;

О зад ан и я о тображ ен ия/свертки;

О р еп л и к ац и я «главны й/главны й»;

О р еп л и к ац и я «главны й/подчиненн ы й»;


268 Глава 8. Хранение данных в приложениях

О запросы SQL;

О вторичны е индексы;

О триггеры;

О управление версиям и данных.

В вики L evelU P ведется достаточно подробная сводка экосистемы LevelDB: h ttp s://
github.com /Level/levelup/w iki/M odules. Вы такж е м ож ете пои скать ин ф орм аци ю
о leveldb в npm; на м ом ент написания книги там было 898 пакетов. Н а рис. 8.6 п о ­
казан а ин ф орм аци я о поп улярн ости LevelD B в npm.

8.13. Затратные операции сериализации


и десериализации
В ажно помнить, что встроенны е операции JS O N обходятся дорого и вы полняю тся
в блокирую щ ем режиме; ваш процесс не мож ет делать ничего другого в то время,
пока прилож ение преобразует данны е в JS O N и обратно. Это относится к больш ин­
ству других ф орм атов сериализации. С ериализац ия часто становится важ нейш им
«узким местом» веб-сервера. Л у ч ш и й способ сократить ее вли ян и е — вы полнять
ее как м ож но реж е и свести к м иним ум у объем обрабаты ваем ы х данных.

В озможно, вам удастся добиться некоторого повы ш ения скорости за счет перехода
на другой ф орм ат сериализаци и (наприм ер, M essagePack или P ro to co l Buffers), но
рассм атривать альтернативны е ф орм аты стоит только после того, как вы вы ж мете
всю возм ож ную эконом ию за счет со кр ащ ен и я разм еров передаваем ы х данны х
и устран ения избы точны х ш агов сери али зац и и /д есери али зац и и .

В строенны е ф у н кц и и J S O N .s trin g ify и JSO N .parse подверглись тщ ательной о п ­


ти м и зац и и , но п ри о бработке м егабайтов д ан н ы х они начинаю т пасовать. Д л я
дем онстрации в листинге 8.22 вы полняется сериализаци я и десери али зация п р и ­
близительно 10 М байт данных.

Листинг 8.22. Хронометражное тестирование сериализации


const bytes = r e q u ir e ('p re tty -b y te s ');
const obj = {};
fo r ( le t i = 0; i < 200000; i++) {
o b j[i] = {
[Math.random()]: Math.random()
};
}
c o n s o le .tim e ('s e ria lis e ');
const jsonS tring = JSO N .stringify(obj);
c o n so le .tim e E n d ('se ria lise ');
c o n so le .lo g ('S e ria lise d S iz e ', bytes(B uffer.byteL ength(jsonS tring)));
8.14. Хранение данных в браузере 269

c o n s o le .tim e ('d e s e ria lis e ');


const obj2 = JSON.parse(jsonString);
co n so le.tim eE n d ('d eserialise');

В 2015 году на ко м п ью тер е In te l C o re i7 M acB ook P ro 3,1 ГГц с N o d e 6.2.2 на


сери ал и зац и ю п р и б л и зи тел ьн о 10 М байт дан н ы х требовалось п ри бл и зи тел ьн о
140 мс, а на д есер и ал и зац и ю — 335 мс. Н а веб-сервере так ая нагрузка бы ла бы
катастроф ической, потом у что эти операции вы полняю тся в блокирую щ ем р еж и ­
ме, и вы п о л н яю тся последовательно. Такой сервер см ож ет об рабаты вать около
ни ч то ж н ы х сем и зап р о со в в секун ду при с е р и а л и за ц и и и около трех запросов
в секунду при д есери али зации .

8.14. Хранение данных в браузере


А синхронная м одель програм м ирования, исп ользуем ая в N ode, хорош о работает
во м ногих практи чески х сценариях, потом у что у б ольш ин ства веб-прилож ений
самы м значительны м «узким местом» я в л яе т с я ввод /вы вод . Главное, что можно
сделать для одновременного сокращ ения нагрузки на сервер и повы ш ения качества
взаим одействий с пользователем, — организация хранения данны х на стороне к л и ­
ента. Д оволен будет тот пользователь, которому не приходится ожидать заверш ения
ци клической передачи по сети д л я получения результатов. Х ранение данны х на
стороне клиента также улучш ает доступность прилож ения: ваше прилож ение может
сохранить по крайней мере частичную работоспособность, пока пользователь или
ваш сервис не активен.

8.14.1. Веб-хранилище: localStorage и sessionStorage


Веб-хранилище определяет простое х ран и ли щ е «клю ч-значение» и обладает о т ­
личной поддерж кой среди настольны х и м обильны х браузеров. П ри и сп ользова­
нии веб-хранилищ а домен м ож ет сохранить небольш ой объем данны х в браузере
и прочитать их позднее — даж е после о б новления сайта, зак р ы ти я вкл ад ки или
заверш ен ия браузера. В еб-хранилищ е — первы й ш аг на пути хранения данны х на
стороне клиента. Главной сильной стороной этой технологии яв л яется ее простота.

Сущ ествует два A P I веб-хранилищ а: localStorage и sessionStorage. sessionStorage


реализует A PI, идентичны й A P I localStorage, хотя и несколько отличается в поведе­
нии. Как и в случае с localStorage, данные, хранящ иеся в sessionStorage, сохраняются
меж ду перезагрузкам и страниц, но в отличие от localStorage срок дей стви я всех
данны х sessionStorage истекает при заверш ении сеанса страницы (п р и закры тии
вкладки или браузера). Данные sessionStorage недоступны из разных окон браузеров.

A PI веб-хранилищ а бы ли разработаны для преодоления ограничений cookie в брау­


зерах. Если говорить конкретнее, cookie плохо подходят для обмена данны ми между
270 Глава 8. Хранение данных в приложениях

нескольким и активны м и вкладкам и одного домена. Если пользователь вы полняет


некоторы е дей стви я м еж ду вкладкам и, хранилищ е sessionStorage мож ет использо­
ваться для обмена данны м и состояния м еж ду вкладкам и без исп ользован ия сети.

Cookie также плохо подходят для долгосрочного хранения данных, которые должны
«пережить» границы сеансов, вкладок и окон (например, созданны х пользователем
докум ентов или электронной почты ). Д л я таких сценариев было спроектировано
хранилищ е localStorage. О бъем данных, которые могут храниться в веб-хранилищ е,
ограничивается в зависим ости от конкретного браузера. Д л я м обильны х браузеров
этот порог составляет всего 5 М байт.

Сводка API
A P I localStorage предоставляет следую щ ие методы д ля работы с клю чам и и зн а ­
чениям и:

О lo c a lS to r a g e .s e tI te m ( k e y , v a lu e ) — сохраняет значение с заданны м ключом;

О lo c a lS to r a g e .g e tlte m ( k e y ) — получает значение д ля заданного ключа;

О lo c a lS to ra g e .re m o v e lte m (k e y ) — удаляет значение д ля заданного ключа;

О l o c a l S t o r a g e .c l e a r ( ) — удаляет все клю чи и значения;

О lo c a lS to r a g e .k e y (in d e x ) — получает значение с заданны м индексом;

О lo c a lS to r a g e .le n g th — возвращ ает общее количество клю чей в localStorage.

8.14.2. Чтение и запись значений


К ак клю чи, так и значения долж ны быть строками. Если передать значение, кото­
рое не я в л яется строкой, оно будет автом атически преобразовано в строку. Такое
преобразование не генерирует строки JS O N ; это обычное наивное преобразование
с использованием .to S tr in g . О бъекты в конечном итоге сериализую тся в строку
[ o b je c t O b je c t]. Ч тобы разм естить в веб-хранилищ е более слож ны е типы данных,
при лож ение долж но само сериализовать зн ачения в строки и обратно. В л и сти н ­
ге 8.23 показано, как хранить J S O N в localStorage.

Листинг 8.23. Хранение JSON в веб-хранилище


const examplePreferences = {
temperature: 'C elcius'
};
/ / Сериализация при записи
lo c a lS to ra g e .se tItem ('p re fere n c e s', JSON.stringify(examplePreferences));

/ / Десериализация при чтении


const preferences = JSO N .parse(localS torage.getItem ('preferences'));
console.log('Loaded p re fe re n c e s:', preferences);
8.14. Хранение данных в браузере 271

О б ращ ени е к данны м в еб-х р ан и л и щ а осу щ ествл яется д остаточно быстро, хотя
и оно так ж е в ы п о л н я е т с я си н х р о н н о . В еб -х р ан и л и щ е б л о к и р у ет U I -п оток на
врем я в ы п о л н ен и я ч тен и я и запи си. Д л я м алы х н агрузок эти затраты останутся
незам етны м и, но вы долж ны п озаб оти ться о том, чтобы избеж ать л и ш н и х о п ер а­
ц ий ч тен и я и ли зап и си (особен но при б ольш их объемах дан ны х). К сож алению ,
веб-хран илищ е такж е недоступно д ля веб-работников (w eb w orkers), поэтому все
о п ераци и ч тен и я и зап и си д олж ны в ы п о л н яться в одном U I -потоке. П одробны й
анализ в л и я н и я на бы стродействи е р азл и ч н ы х техн ологи й х ран ен и я на стороне
клиента приведен в посте Н олана Л оусона (N olan Lawson), автора PouchD B : h ttp ://
nolanlawson.com/2015/09/29/indexeddb-websql-localstorage-what-blocks-the-dom/.
A P I веб-хранилищ а не предоставляю т встроенны х средств д ля вы полнен ия запро­
сов, вы борки клю чей по диапазонам или поиска по значениям . Р азработчик огра­
ничивается возмож ностью обращ ения с перебором клю чей. Ч тобы провести поиск,
вам придется создавать и поддерж ивать собственные индексы; или, если ваш набор
данны х достаточно мал, м ожно полностью перебрать его элементы. В листинге 8.24
перебираю тся все клю чи localStorage.

Листинг 8.24. Перебор всего набора данных в localStorage


function getAllKeys() {
return O bject.keys(localStorage);
}
function getAllKeysAndValues() {
return getAllKeys()
.reduce((obj, s tr ) => {
o b j[s tr] = lo calS to rag e.g etItem (str);
return obj;
}, {});
}
/ / Получение всех значений
const allV alues = getAllKeys().map(key => localStorage.getItem (key));

/ / В виде объекта
console.log(getAllKeysAndValues());

К ак и у бо льш и н ства х р ан и ли щ «клю ч-зн ачени е», у клю чей сущ ествует только
одно п р о стр ан ств о им ен. Н ап р и м ер , если в базе д ан н ы х х р а н я т с я сообщ ен и я
и ком м ентарии, невозм ож но создать два разны х хран и ли щ а д л я сообщ ений и для
комментариев. Впрочем, достаточно легко реализовать собственные «пространства
им ен», снабди в каж ды й клю ч п р еф и к со м д л я об о зн ач ен и я п р о стр ан ств а имен
(л и ст и н г 8.25).

Листинг 8.25. Ключи, реализующие пространства имен


lo c a lS to ra g e .se tIte m ('/p o sts/$ { p o st.id } ', post);
localStorage.setItem ('/com m ents/${com m ent.id}', comment);
272 Глава 8. Хранение данных в приложениях

Чтобы получить все элементы из пространства имен, отфильтруйте набор элементов


при пом ощ и ф ун кц и и getA llK eys (л и сти н г 8.26).

Листинг 8.26. Получение всех элементов в пространстве имен


function getNamespaceItems(namespace) {
return g etA llK e y s().filte r(k e y => key.startsW ith(namespace));
}
console.log(getNamespaceItems('/exampleNamespace'));

У ч ти те, ч т о эт о т ф р а г м е н т п е р е б и р а е т в се к л ю ч и и з lo c a lS to ra g e ; п о м н и т е
о в о зм о ж н о м с н и ж е н и и б ы с т р о д е й с т в и я п р и п ер еб о р е б о л ьш о го к о л и ч ес т в а
элем ентов.

И з-за синхронности A P I localStorage существуют некоторые ограничения на то, где


и как этот A P I м ож ет использоваться. Н апример, localStorage мож ет использовать­
ся д ля м ем оизаци и результатов лю бой ф ункц ии, которая получает и возвращ ает
данные, сериализуем ы е в ф орм ат JS O N (л и сти н г 8.27).

Листинг 8.27. Использование localStorage для мемоизации


/ / При последующих вызовах с тем же аргументом
/ / будет извлекаться сохраненный результат.
function memoizedExpensiveOperation(data) {
const key = '/m em oized/${JSO N .stringify(data)}';
const memoizedResult = localStorage.getItem (key);
i f (memoizedResult != n u ll) return memoizedResult;
/ / Затратные операции
const re s u lt = expensiveWork(data);
/ / Сохранение результатов в localStorage,
/ / чтобы их не приходилось вычислять заново
localStorage.setItem (key, re s u lt);
return re s u lt;
}

Учтите, что операци я долж на бы ть достаточно м едленной, чтобы преимущ ества


м ем оизаци и перевесили затраты на процесс сер и ал и зац и и /д есер и ал и зац и и (н а ­
пример, криптограф ически й алгоритм ). С оответственно, localStorage лучш е всего
работает в том случае, если м ем оизаци я эконом ит время, расходуем ое на передачу
данны х по сети.

Технология веб-хранилищ имеет свои ограничения, но д ля правильно вы бранны х


задач она становится мощ ны м и просты м инструментом. Также заслуж иваю т в н и ­
м ан и я некоторы е другие темы, относящ и еся к хранению данны х браузером:

О IndexedD B ;

О Service w orkers;

О O ffline-first.
8.14. Хранение данных в браузере 273

8.14.3. localForage
О сновны е недостатки веб-хранилищ а — синхронны й A P I с блокировкой и огра­
н и ченн ая емкость пам яти в некоторы х браузерах. К ром е веб-хран илищ а многие
современные браузеры поддерживают W ebSQ L и (и л и ) IndexedD B. О ба хранилищ а
данны х работаю т в неблокирую щ ем реж им е и позволяю т хранить гораздо больш е
данны х, чем технологии веб-хранилищ а.

О днако использовать любую из этих баз данны х напрямую , как это делалось с A PI
веб-хранилищ а, не рекомендуется. Технология W ebSQ L считается устаревш ей, а ее
наследник IndexD B имеет неудобны й и гром оздкий A PI, не говоря уж е об и сп рав­
лениях, необходимых для поддерж ки в браузерах. Ч тобы удобно и надежно хранить
данны е в браузере без блокировки, мы реком ендуем использовать нестандартны й
и н стр у м ен т д л я « н о р м а л и за ц и и » и н тер ф ей са. О д н и м из т ак и х и н стр у м ен то в
н орм ал и зац и и я в л я е т с я б и бли отека localF orage от M ozilla (http://m ozilla.github.
io/localForage/).

Обзор API

И нтерф ейс localForage довольно близко воспроизводит интерф ейс веб-хранилищ а,


хотя и в асинхронной (не блокирую щ ей) форме:

О lo c a lf o r a g e .s e tI te m ( k e y , v a lu e , c a llb a c k ) — сохраняет значение с заданны м


ключом;

О l o c a l f o r a g e . g e t I t e m ( k e y j c a l l b a c k ) — п о л у ч ает зн а ч е н и е д л я зад ан н о го
клю ча;

О lo c a lf o r a g e .r e m o v e I te m ( k e y j c a l lb a c k ) — у д ал яет зн ач ен и е д л я зад ан ного


ключа;

О lo c a l f o r a g e .c l e a r ( c a l lb a c k ) — удаляет все клю чи и значения;

О lo c a lf o r a g e .k e y ( in d e x j c a llb a c k ) — получает зн ачение с зад ан ны м индексом;

О lo c a l f o r a g e .l e n g t h ( c a l lb a c k ) — возвращ ает количество клю чей в localForage.

A P I lo c a lF o ra g e так ж е в кл ю чает п о л езн ы е д о п о л н ен и я, не им ею щ и е аналогов


в веб-хранилищ е:

О lo c a lf o r a g e .k e y s ( c a llb a c k ) — удаляет все клю чи и значения;

О l o c a l f o r a g e . i t e r a t e ( i t e r a t o r , c a llb a c k ) — перебирает клю чи и значения.

8.14.4. Чтение и запись


A P I localForage поддерж ивает как обещ ания (prom ises), так и схему обратного вы ­
зова N ode с объектом ош ибки на первом месте.
274 Глава 8. Хранение данных в приложениях

Листинг 8.28. Сравнение получения данных в localStorage и localForage


I localStorage: блокировка,
const value = localStorage.getItem (key); -<------ 1 синхронное выполнение.
console.log(value);

lo calfo rag e. getItem(key) localForage: без блокировки, асинхронное


.then(value => console.log(value)); выполнение с использованием обещаний.
localforage.getItem (key, (e rr, value) => { < - localForage: без блокировки,
console.log(value); асинхронный вызов с использованием
}); обратных вызовов в стиле Node.

Во внутренн ей р еал и зац и и localF orage исп ользует л учш ий м еханизм хранения,
д о ст у п н ы й в те к у щ е й среде б р ау зер а. Е сл и п о д д ер ж к а In d e x e d D B д оступ н а,
localForage использует ее. В противном случае будет сделана поп ы тка п ереклю ­
читься на W ebSQ L и даж е использовать веб-хранилищ е при необходимости. Вы
м ож ете н астрои ть порядок, в котором будут опробованы м еханизм ы хранения,
и даж е исклю чить некоторы г е гварианты: Никогда не возвращается
/ / Не будет использовать localStorage к использованию localStorage.
я
localforage.setDriver([localforage.INDEXEDDB, localforage.WEBSQL]);

В отличие от localStorage, localForage не ограничивается хранением одних лиш ь


строк. П оддерж ивается больш инство прим итивов Ja v a S c rip t (таких, как м ассивы
и объекты ), а такж е двоичны е типы данных: TypedArray, A rra y B u ffe r и Blob. У чти­
те, что IndexedD B — единственная подсистема баз данны х, изначально способная
хранить двоичны е данные: при использовании W ebSQ L и localStorage появляю тся
затраты , связанны е с преобразованием данных:

Prom ise.all([
lo calforage.setItem ('num ber', 3),
lo c a lfo ra g e .s e tIte m ('o b je c t', { key: 'v alu e' }),
lo c a lfo ra g e.s etIte m ('ty p e d arra y ', new Uint32Array([1,2,3]))
]);

П овторение A P I веб-хран илищ а делает работу с localForage более интуитивной,


а такж е исклю чает многие недостатки и проблемы совместимости при организации
хранени я данны х в браузере.

8.15. Виртуальное хранение


Виртуальное хранение (h o ste d sto rag e) — другая тактика, которая позволяет и зб е­
ж ать необходим ости сам остоятельной организации хранени я на стороне сервера.
Сервисы виртуальной инфраструктуры — например, предоставляемые AWS (Amazon
W eb Services) — часто рассм атриваю тся исклю чительно как средство о п ти м и за­
ции бы стродействи я и м асш табирования, но ум ное исп ользован ие виртуальны х
8.15. Виртуальное хранение 275

сервисов с ранней стадии работы мож ет сэконом ить м ного времени, которое было
бы потрачено на неудачную и излиш ню ю реализацию инф раструктуры .

М н о ги е (е с л и не в с е ) базы д ан н ы х , п е р е ч и с л ен н ы е в это й главе, п ред л агаю т


возм ож н ость виртуального хранени я. В ир ту ал ьн ы е сервисы позволяю т быстро
опробовать инструм ентарий и даж е развернуть общ едоступны е при лож ен и я без
хлопот, связанны х с настройкой сам остоятельного хостинга базы данных. Тем не
м енее р азв ер н у ть собственную б азу д ан н ы х стан о ви тся все прощ е. М ноги е о б ­
лачн ы е сервисы предоставляю т готовы е образы серверов со всем необходимы м
програм м ны м обеспечением и конф и гурац ией д ля запуска м аш ины с вы бранной
вам и базой данных.

8.15.1. S3

A m azon S im ple S to rag e Service (S 3 ) — серви с удал ен н ого р азм ещ ен и я ф ай лов,


предоставляем ы й как часть популярного ком плекса сервисов AWS. S3 представ­
ляет собой эф ф екти вн ы й по затратам м еханизм хранения и разм ещ ен ия ф айлов,
доступн ы х по сети. П о сути это ф ай л о в ая систем а в облаке. С исп ользован ием
R E S T -совм естим ы х вы зовов H T T P ф айлы м огут загруж аться в гнезда (b u ck ets)
наряду с 2 К байт метаданны х. Д алее к содерж им ом у гнезд м ож но обращ аться м е­
тодом H T T P G E T или через протокол B itT orrent.

Д л я гнезд и их содерж имого м ож но настроить разны е уровни разреш ений, вкл ю ­


ч ая доступ, кон троли руем ы й по времени. Такж е м ож но задать срок ж и зни д ля со­
держ им ого гнезд; по истечении этого срока содерж имое становится недоступны м
и удаляется из гнезда. Д анны е S3 легко преобразую тся в сеть доставки контента
(C D N , C o n te n t D elivery N etw o rk ). AWS предоставляет сервис C lo u d F ro n t CDN,
которы й м ож но легко связать с ваш им и ф айлам и д ля обращ ения к ним с низкой
задерж кой из лю бой точки мира.

Н е все данны е необходимо (и л и хотя бы ж елательн о) хранить в базе данных. Есть


ли в ваш их данны х компоненты , которы е могут рассм атриваться как ф айлы ? П о ­
сле того как вы сгенерировали результаты слож ны х вы числений д ля пользователя,
возмож но, вам стоит направить эти результаты в S3, а затем навсегда устраниться
от обеспечения доступа к ним.

Распространенны й и наиболее очевидный вариант использования S3 — размещ ение


ресурсов, отправленны х пользователем (наприм ер, граф ики). Ресурсы хранятся во
временном каталоге на маш ине прилож ения, обрабатываю тся таким инструментом,
как ImageM agick, для сокращ ения размера файла, и в дальнейш ем отправляю тся в S3
для хостинга. Этот процесс м ожно еще сильнее упростить, отправляя данные прямо
в S3, где они инициирую т дальнейш ую обработку. К лиентские при лож ения такж е
м огут о тправлять данны е прям о в S3. Н екоторы е сервисы , ориен ти рован ны е на
разработчиков, даже могут не предоставлять собственное хранилищ е; пользователь
276 Глава 8. Хранение данных в приложениях

долж ен предоставить м аркеры доступа, чтобы прилож ение пользовалось собствен­


ны м и гнездам и S3.

S3 не ограничивается хранением графики


С ер ви с S3 м ож ет и с п о л ьзо в атьс я д л я х р ан ен и я лю бы х ти п ов ф ай л о в в лю бом
ф орм ате разм ером до 5 терабайт. S3 лучш е всего подходит д ля больш их объемов
данны х, которы е и зм ен яю тся о тн о си тел ьн о редко, к которы м об ращ аю тся как
к едином у целому.
Х ранени е данны х в S3 обходит все слож ности с н астрой кой и сопровож дением
сервера д ля разм ещ ен ия и хранени я ф айлов. Этот вариант отлично подходит для
сценариев, в которы х относительно редко записы ваю тся больш ие блоки данных,
к которы м происходят атом арны е обращ ения (то есть обращ ения как к единому
целом у), с больш им количеством чтени й из м ногих потенциальны х мест.

8.16. Какую базу данных выбрать?


В этой главе рассм отрены л и ш ь некоторы е из баз данны х, часто исп ользуем ы х
в п р и ло ж ен и ях N ode. Успеш ные п р и ло ж ен и я могут строиться на основе лю бых
из этих баз данных. В одном при лож ении не всегда находится идеальное реш ение
хранени я данных; панацеи не сущ ествует. У каж дой базы данны х есть свои плюсы
и минусы , и разработчик долж ен оценить, какие стороны лучш е подходят для т е ­
кущ его состояния проекта. Ч асто наиболее уместны м реш ением оказы вается ком ­
бинаци я технологий. Вместо того чтобы спраш ивать: «К акую базу данны х следует
использовать?», стоит задаться вопросом: «Н асколько далеко мож но зайти вообще
без использования базы данных?» Какую часть проекта можно построить с м иним у­
мом долгосрочных реш ений? Ч асто реш ения лучш е отлож ить на будущее; вы всегда
смож ете при нять лучш ее реш ение позднее, когда у вас будет больш е инф орм ации.

8.17. Заключение
О В N ode могут использоваться как реляционны е базы данных, так и базы данных
N oSQ L.

О П ростой м одуль N ode pg отлично подходит д ля работы с язы ком SQL.

О М одуль K nex позволяет работать с нескольким и базам и данны х в Node.

О A C ID — набор характеристик тран закц и й баз данных, обеспечиваю щ ий безо­


пасность данных.

О M ongoD B — база данны х N oSQ L, использую щ ая Jav aS crip t.


8.17. Заключение 277

О R edis — храни ли щ е структур данных, которое мож ет исп ользоваться как база
данны х и кэш.

О LevelD B — быстрое хранилищ е пар «клю ч-значение» ком пании Google, ассоци­
ирую щ ее строки со значениям и.

О LevelD B я в л я е т с я м одульной базой данных.

О Технологии веб-хранилищ а, вклю чая localForage и localStorage, могут исп оль­


зоваться д ля хранени я данны х в браузере.

О Такие сервисы, как Am azon S3, могут использоваться для сохранения данны х
у провайдеров облачны х сервисов.
Тестирование
приложений Node

П о мере расш ирения ф ункц иональн ости при лож ения возрастает риск появления
ош ибок. Б ез тести р о в ан и я при ло ж ен и е счи тается незаверш енны м , а поскольку
ручное тестирован ие утом ительн о и чревато новы м и ош ибками, обусл овл ен н ы ­
ми человеческим ф актором , у разработчиков все более поп улярн ы м становится
автом ати зи рован н ое тестирование. В этом случае вм есто того, чтобы проверять
ф ункциональность при лож ения вручную, разработчик пиш ет логику автом ати зи ­
рованного тестирован ия кода.

Е сли вы ранее не сталкивались с концепцией автоматизированного тестирования,


во о б р ази те себе робота, к о то р ы й вм есто вас в ы п о л н я ет всю рути н н у ю работу,
пр ед о ставл яя вам возм ож ность зан я ться более ин тересны м и вещ ами. Тогда при
внесении изм ен ен ий в код робот автом атически проверяет, не вкрались л и туда
ош ибки. Д аж е если вы еще не закон чи ли разработку при лож ения или ж е только
начали создавать свое первое N o d e-прилож ение, вам полезно знать, как р еал и зо ­
вать автом атизированное тестирование, поскольку тогда вы смож ете писать тесты
в процессе разработки прилож ения.

В этой главе рассм атриваю тся два типа автом атизированного тестирования: м о ­
дульное и приемочное. Модульное тестирование направлено на непосредственную
проверку логики прилож ения, обычно на уровне ф ун кц и и или метода; м одульное
тестирование применимо ко всем типам прилож ений. В плане методологии модуль­
ное тестирован ие м ож ет бы ть разделено на две основны е категории: разработка
через тестирование (T est-D riven Developm ent, T D D ) и разработка через реализацию
п о вед ения (B e h a v io r-D riv e n D evelopm ent, B D D ). С п ракти ческой точки зрени я
тесты в стиле T D D и B D D почти во всем одинаковы , несм отря на стилистические
разл и ч и я (которы е могут быть важ ны в зависимости от того, кому придется читать
ваш и тесты ). М еж ду тестам и в стиле T D D и B D D сущ ествую т и некоторы е другие
различия, но их описание вы ходит за рам ки темы книги. Приемочное тестирование
п редставляет собой до п о л н и тел ьн ы й уровень тестирован ия, п ри м ен яем ы й п р е ­
имущ ественно при отладке веб-приложений. Этот вид тестирования подразумевает
9.1. Модульное тестирование 279

сценарное управлени е браузером и проверку ф ункц иональн ости веб-прилож ений


с его помощью.

В этой главе представлены готовы е реш ения, иллю стрирую щ ие модульное и п р и ­


ем очн ое тести р о в ан и е. В р ам к ах м о д у л ьн о го т ест и р о в а н и я м ы п о зн ак о м и м ся
с N od e-модулем assert и со средам и M ocha, Vows и should.js, а такж е Chai. В рамках
приемочного рассм атривается среда Selenium . И нструм енты тестирован ия вместе
с соответствую щ им и м етодологиям и и подходами представлены на рис. 9.1.

Рис. 9.1. Обзор тестовых фреймворков

Н ачнем с м одульного тестирования.

9.1. Модульное тестирование


М одульное тестирование — это такая разновидность автоматического тестирования,
при которой вы пиш ете логику д ля тестирован ия отдель