diff options
author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:42:52 -0500 |
---|---|---|
committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:42:52 -0500 |
commit | 074785cea106179cb3305637055ab0a009ca74f2 (patch) | |
tree | e6ae371cccd642aa2b67f39752a2cdf1fd4eb040 /files/ru/learn/server-side/express_nodejs | |
parent | da78a9e329e272dedb2400b79a3bdeebff387d47 (diff) | |
download | translated-content-074785cea106179cb3305637055ab0a009ca74f2.tar.gz translated-content-074785cea106179cb3305637055ab0a009ca74f2.tar.bz2 translated-content-074785cea106179cb3305637055ab0a009ca74f2.zip |
initial commit
Diffstat (limited to 'files/ru/learn/server-side/express_nodejs')
22 files changed, 4964 insertions, 0 deletions
diff --git a/files/ru/learn/server-side/express_nodejs/development_environment/index.html b/files/ru/learn/server-side/express_nodejs/development_environment/index.html new file mode 100644 index 0000000000..30000c6470 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/development_environment/index.html @@ -0,0 +1,391 @@ +--- +title: Setting up a Node development environment +slug: Learn/Server-side/Express_Nodejs/development_environment +translation_of: Learn/Server-side/Express_Nodejs/development_environment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Introduction", "Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">Теперь, когда вы знаете, что такое Express, мы покажем вам, как настроить и протестировать среду разработки Express для Windows, Linux (Ubuntu) и Mac OS X - какую бы операционную систему вы не использовали, эта статья должна дать вам все, что необходимо для возможности начать разрабатывать приложения Express.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Требования:</th> + <td>Знание как открыть терминал / командную строку, как устанавливать программные пакеты в операционной системе вашего компьютера.</td> + </tr> + <tr> + <th scope="row">Задача:</th> + <td>Создать среду разработки для Express на вашем компьютере.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор_среды_разработки_Express">Обзор среды разработки Express</h2> + +<p><em>Node</em> и <em>Express</em> упрощают настройку вашего компьютера, чтобы вы могли начать разработку веб-приложений. В этом разделе объясняется, какие инструменты нужны, приводятся некоторые из самых простых способов установки Node (и Express) на Ubuntu, macOS, and Windows, и показывается как вы можете протестировать свою установку.</p> + +<h3 id="Что_такое_среда_разработки_Express">Что такое среда разработки Express?</h3> + +<p><span class="tlid-translation translation" lang="ru">Среда разработки Express включает в себя установку Nodejs, менеджера пакетов NPM и (необязательно) Express Application Generator на локальном компьютере.<br> + <br> + Узел и менеджер пакетов NPM устанавливаются вместе из подготовленных двоичных пакетов, установщиков, менеджеров пакетов операционной системы или из исходного кода (как показано в следующих разделах). Затем Express устанавливается NPM как зависимость от ваших отдельных веб-приложений Express (наряду с другими библиотеками, такими как механизмы шаблонов, драйверы баз данных, промежуточное программное обеспечение для аутентификации, промежуточное программное обеспечение для обслуживания статических файлов и т. Д.)<br> + <br> + NPM также можно использовать для (глобальной) установки Express Application Generator, удобного инструмента для создания каркасных веб-приложений Express, которые следуют шаблону MVC. Генератор приложений является необязательным, поскольку вам не нужно использовать этот инструмент для создания приложений, использующих Express, или для приложений для создан Express, имеющих одинаковую архитектурную разметку или зависимости. Мы будем использовать его, потому что это значительно облегчает начало работы и продвигает модульную структуру приложения.</span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание: в отличие от некоторых других веб-сред, среда разработки не включает отдельный веб-сервер разработки.</span> <span title="">В Node / Express веб-приложение создает и запускает собственный веб-сервер!</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Существуют и другие периферийные инструменты, которые являются частью типичной среды разработки, в том числе текстовые редакторы или IDE для редактирования кода и инструменты управления исходным кодом, такие как Git, для безопасного управления различными версиями вашего кода.</span> <span title="">Мы предполагаем, что вы уже установили подобные инструменты (в частности, текстовый редактор).</span></span></p> + +<h3 id="Какие_операционные_системы_поддерживаются"><span class="tlid-translation translation" lang="ru"><span title="">Какие операционные системы поддерживаются?</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Узел может быть запущен в Windows, macOS, во многих «разновидностях» Linux, Docker и т. Д. (Полный список на странице загрузок nodejs).</span> <span title="">Практически любой персональный компьютер должен иметь необходимую производительность для запуска Node во время разработки.</span> <span title="">Express работает в среде Node и, следовательно, может работать на любой платформе, на которой работает Node.</span><br> + <br> + <span title="">В этой статье мы предоставляем инструкции по установке для Windows, macOS и Ubuntu Linux.</span></span></p> + +<h3 id="Какую_версию_Node_Express_следует_использовать"><span class="tlid-translation translation" lang="ru"><span title="">Какую версию Node / Express следует использовать?</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Существует множество выпусков Node - более новые выпуски содержат исправления ошибок, поддержку более свежих версий стандартов ECMAScript (JavaScript) и улучшения API-интерфейсов Node.</span><br> + <br> + <span title="">Как правило, вы должны использовать самый последний выпуск LTS (с долгосрочной поддержкой), поскольку он будет более стабильным, чем «текущий» выпуск, при этом все еще имея относительно недавние функции (и все еще активно поддерживается).</span> <span title="">Вы должны использовать Текущий выпуск, если вам нужна функция, которой нет в версии LTS.</span><br> + <br> + <span title="">Для Express вы всегда должны использовать последнюю версию.</span></span></p> + +<h3 id="Как_насчет_баз_данных_и_других_зависимостей"><span class="tlid-translation translation" lang="ru"><span title="">Как насчет баз данных и других зависимостей?</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Другие зависимости, такие как драйверы баз данных, механизмы шаблонов, механизмы аутентификации и т. д., Являются частью приложения и импортируются в среду приложения с помощью диспетчера пакетов NPM.</span> <span title="">Мы обсудим их в следующих статьях для конкретных приложений.</span></span></p> + +<h2 id="Установка_Node">Установка Node</h2> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Чтобы использовать Express, сначала необходимо установить Nodejs и Node Package Manager (NPM) в вашей операционной системе.</span> <span title="">В следующих разделах описывается самый простой способ установки версии Nodejs с долгосрочной поддержкой (LTS) в Ubuntu Linux 16.04, macOS и Windows 10.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Совет: В следующих разделах показан самый простой способ установки Node и NPM на наши целевые платформы ОС.</span> <span title="">Если вы используете другую ОС или просто хотите увидеть некоторые другие подходы для текущих платформ, см. Установка Node.js через менеджер пакетов (nodejs.org).</span></span></p> +</div> + +<h3 id="Windows_и_macOS">Windows и macOS</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Установка Node и NPM в Windows и macOS проста, потому что вы можете просто использовать предоставленный инсталлятор:</span></span></p> + +<ol> + <li><span class="tlid-translation translation" lang="ru"><span title="">Загрузите необходимый установщик:</span></span> + + <ol> + <li>Перейдите по ссылке <a href="https://nodejs.org/en/">https://nodejs.org/en/</a></li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Нажмите кнопку, чтобы загрузить сборку LTS, которая «Рекомендуется для большинства пользователей».</span></span></li> + </ol> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Установите Node, дважды щелкнув по загруженному файлу и следуя инструкциям по установке.</span></span></li> +</ol> + +<h3 id="Ubuntu_16.04">Ubuntu 16.04</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Самый простой способ установить последнюю версию LTS Node 6.x - это использовать</span></span> <a href="https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions">package manager</a> <span class="tlid-translation translation" lang="ru"><span title="">чтобы получить его из репозитория бинарных дистрибутивов Ubuntu.</span> <span title="">Это можно сделать очень просто, выполнив следующие две команды на вашем терминале:</span></span></p> + +<pre class="brush: bash notranslate"><code>curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - +sudo apt-get install -y nodejs</code> +</pre> + +<div class="warning"> +<p><strong>Внимание:</strong> <span class="tlid-translation translation" lang="ru"><span title="">Не устанавливайте напрямую из обычных репозиториев Ubuntu, поскольку они содержат очень старые версии узла.</span></span></p> +</div> + +<ol> +</ol> + +<h3 id="Проверка_вашей_установки_Nodejs_и_NPM">Проверка вашей установки Nodejs и NPM</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Самый простой способ проверить, установлен ли этот узел, - это запустить команду «версия» в своем терминале / командной строке и проверить, что возвращается строка версии:</span></span></p> + +<pre class="brush: bash notranslate">>node -v +v8.11.3</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Менеджер пакетов Nodejs NPM также должен быть установлен и может быть протестирован таким же образом:</span></span></p> + +<pre class="brush: bash notranslate">>npm -v +5.8.0</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">В качестве немного более захватывающего теста давайте создадим очень простой сервер «чистого узла», который просто печатает «Hello World» в браузере, когда вы посещаете правильный URL в вашем браузере:</span></span></p> + +<ol> + <li><span class="tlid-translation translation" lang="ru"><span title="">Скопируйте следующий текст в файл с именем hellonode.js.</span> <span title="">Здесь используются чистые функции Node (ничего из Express) и некоторый синтаксис ES6:</span></span> + + <pre class="brush: js notranslate">//Загрузка моделя HTTP +const http = require("http"); +<code>const hostname = '127.0.0.1'; +const port = 3000; + +//Создание HTTP-сервера +const server = http.createServer((req, res) => { + + //Установка HTTP-заголовков ответа с HTTP-статусом и Content type + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Hello World\n'); +}); + +//Слушание запросов на порту 3000, и в качестве каллбака функция, которая пишет в лог +server.listen(port, hostname, () => { + console.log('Server running at http://${hostname}:${port}/'); +});</code> + +</pre> + + <p><span class="tlid-translation translation" lang="ru"><span title="">Код импортирует модуль «http» и использует его для создания сервера (createServer ()), который прослушивает HTTP-запросы на порту 3000. Затем сценарий выводит на консоль сообщение о том, какой URL-адрес браузера можно использовать для тестирования сервера.</span> <span title="">Функция createServer () принимает в качестве аргумента функцию обратного вызова, которая будет вызываться при получении HTTP-запроса - она просто возвращает ответ с кодом состояния HTTP 200 («ОК») и простым текстом «Hello World».</span></span></p> + + <div class="note"> + <p><span class="tlid-translation translation" lang="ru"><span title="">Замечание: не беспокойтесь, если вы еще не совсем понимаете, что делает этот код!</span> <span title="">Мы объясним наш код более подробно, как только мы начнем использовать Express!</span></span></p> + </div> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Запустите сервер, перейдя в тот же каталог, что и ваш файл hellonode.js в командной строке, и вызвав узел вместе с именем скрипта, например так:</span></span> + <pre class="brush: bash notranslate">>node hellonode.js +Server running at http://127.0.0.1:3000/ +</pre> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Перейдите к URL-адресу http://127.0.0.1:3000.</span> <span title="">Если все работает, браузер должен просто отобразить строку «Hello World».</span></span></li> +</ol> + +<h2 id="Использование_NPM">Использование NPM</h2> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Помимо самого Node, NPM является наиболее важным инструментом для работы с приложениями Node.</span> <span title="">NPM используется для получения любых пакетов (библиотек JavaScript), которые необходимы приложению для разработки, тестирования и / или производства, а также может использоваться для запуска тестов и инструментов, используемых в процессе разработки.</span></span></p> + +<div class="note"> +<p><strong>Замечание:</strong> <span class="tlid-translation translation" lang="ru"><span title="">С точки зрения Node, Express - это просто еще один пакет, который вам нужно установить с помощью NPM, а затем установить его в своем собственном коде.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы можете вручную использовать NPM для получения каждого необходимого пакета отдельно.</span> <span title="">Обычно мы вместо этого управляем зависимостями, используя простой текстовый файл с именем package.json.</span> <span title="">В этом файле перечислены все зависимости для конкретного «пакета» JavaScript, включая имя пакета, версию, описание, исходный файл для выполнения, производственные зависимости, зависимости разработки, версии Node, с которыми он может работать, и т. Д. Файл package.json должен</span> <span title="">содержать все, что нужно NPM для загрузки и запуска вашего приложения (если вы пишете библиотеку многократного использования, вы можете использовать это определение для загрузки пакета в репозиторий npm и сделать его доступным для других пользователей).</span></span></p> + +<h3 id="Добавление_зависимостей">Добавление зависимостей</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Следующие шаги показывают, как вы можете использовать NPM для загрузки пакета, сохранить его в зависимостях проекта, а затем потребовать его в приложении Node.</span></span></p> + +<div class="note"> +<p><strong>Замечание:</strong> <span class="tlid-translation translation" lang="ru"><span title="">Здесь мы показываем инструкции для получения и установки пакета Express.</span> <span title="">Позже мы покажем, как этот пакет и другие уже указаны для нас с помощью Express Application Generator.</span> <span title="">Этот раздел предоставлен, потому что полезно понять, как работает NPM и что создается генератором приложений.</span></span></p> +</div> + +<ol> + <li><span class="tlid-translation translation" lang="ru"><span title="">Сначала создайте каталог для вашего нового приложения и перейдите в него:</span></span> + + <pre class="brush: bash notranslate">mkdir myapp +cd myapp</pre> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Используйте команду npm init для создания файла package.json для вашего приложения.</span> <span title="">Эта команда запрашивает у вас несколько вещей, включая имя и версию вашего приложения, а также имя исходного файла точки входа (по умолчанию это index.js).</span> <span title="">Сейчас просто примите значения по умолчанию:</span></span> + <pre class="brush: bash notranslate">npm init</pre> + + <p><span class="tlid-translation translation" lang="ru"><span title="">Если вы отобразите файл package.json (cat package.json), вы увидите принятые по умолчанию значения, заканчивающиеся лицензией.</span></span></p> + + <pre class="brush: json notranslate">{ + "name": "myapp", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} +</pre> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Теперь установите Express в каталог myapp и сохраните его в списке зависимостей вашего файла package.json</span></span></li> + <li> + <pre class="brush: bash notranslate">npm install express --save</pre> + + <p><span class="tlid-translation translation" lang="ru"><span title="">Раздел зависимостей вашего package.json теперь появится в конце файла package.json и будет содержать Express.</span></span></p> + + <pre class="brush: json notranslate">{ + "name": "myapp", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", +<strong> "dependencies": { + "express": "^4.16.3" + }</strong> +} +</pre> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Для использования библиотеки вы вызываете функцию require (), как показано ниже в вашем файле index.js.</span></span> + <pre class="notranslate"><code><strong>const express = require('express')</strong> +const app = express(); + +app.get('/', (req, res) => { + res.send('Hello World!') +}); + +app.listen(</code>8000<code>, () => { + console.log('Example app listening on port </code>8000<code>!') +});</code> +</pre> + + <p><span class="tlid-translation translation" lang="ru"><span title="">Этот код показывает минимальное веб-приложение Express «HelloWorld».</span> <span title="">Это импортирует модуль «экспресс» и использует его для создания сервера (приложения), который прослушивает HTTP-запросы на порту 8000 и выводит на консоль сообщение, объясняющее, какой URL-адрес браузера можно использовать для тестирования сервера.</span> <span title="">Функция app.get () отвечает только на запросы HTTP GET с указанным URL-путем ('/'), в этом случае вызывая функцию для отправки нашего Hello World!</span> <span title="">сообщение.</span></span><br> + <br> + <span class="tlid-translation translation" lang="ru"><span title="">Создайте файл с именем index.js в корне каталога приложения «myapp» и передайте ему содержимое, показанное выше.</span></span></p> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Вы можете запустить сервер, вызвав узел с помощью скрипта в командной строке:</span></span> + <pre class="brush: bash notranslate">>node index.js +Example app listening on port 8000 +</pre> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Перейдите к URL </span></span> (<a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>) <span class="tlid-translation translation" lang="ru"><span title="">.</span> <span title="">Если все работает, браузер должен просто отобразить строку «Hello World!».</span></span></li> +</ol> + +<h3 id="Зависимости_разработки">Зависимости разработки</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Если зависимость используется только во время разработки, вы должны вместо этого сохранить ее как «зависимость разработки» (чтобы пользователям вашего пакета не приходилось устанавливать ее в производстве).</span> <span title="">Например, чтобы использовать популярный инструмент JavaScript Linting eslint, вы должны вызвать NPM, как показано ниже:</span></span></p> + +<pre class="brush: bash notranslate"><code>npm install eslint --save-dev</code></pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Следующая запись будет добавлена в package.json вашего приложения:</span></span></p> + +<pre class="brush: js notranslate"> "devDependencies": { + "eslint": "^4.12.1" + } +</pre> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание: «Линтеры» - это инструменты, которые выполняют статический анализ программного обеспечения, чтобы распознавать и сообщать о приверженности / несоблюдении некоторого набора лучших практик кодирования.</span></span></p> +</div> + +<h3 id="Запуск_задач"><span class="tlid-translation translation" lang="ru"><span title="">Запуск задач</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">В дополнение к определению и извлечению зависимостей вы также можете определить именованные скрипты в ваших файлах package.json и вызвать NPM, чтобы выполнить их с помощью команды run-script.</span> <span title="">Этот подход обычно используется для автоматизации выполнения тестов и частей набора инструментов разработки или сборки (например, запуска инструментов для минимизации JavaScript, сжатия изображений, LINT / анализа вашего кода и т. Д.).</span></span></p> + +<div class="note"> +<p><strong>Замечание:</strong> <span class="tlid-translation translation" lang="ru"><span title="">Для запуска тестов и других внешних инструментов могут также использоваться такие исполнители, как Gulp и Grunt.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Например, чтобы определить скрипт для запуска зависимости разработки eslint, которую мы указали в предыдущем разделе, мы могли бы добавить следующий блок скрипта в наш файл package.json (при условии, что наш источник приложения находится в папке / src / js):</span></span></p> + +<pre class="brush: js notranslate">"scripts": { + ... + "lint": "eslint src/js" + ... +} +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Чтобы пояснить немного подробнее, eslint src / js - это команда, которую мы могли бы ввести в нашем терминале / командной строке, чтобы запустить eslint для файлов JavaScript, содержащихся в каталоге src / js внутри каталога нашего приложения.</span> <span title="">Включение вышеупомянутого в файл package.json нашего приложения обеспечивает ярлык для этой команды - lint.</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Затем мы сможем запустить eslint с помощью NPM, вызвав:</span></span></p> + +<pre class="brush: bash notranslate"><code>npm run-script lint +# OR (using the alias) +npm run lint</code> +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Этот пример может выглядеть не короче, чем исходная команда, но вы можете включить в свои сценарии npm гораздо более крупные команды, включая цепочки из нескольких команд.</span> <span title="">Вы можете определить один скрипт npm, который запускает все ваши тесты одновременно.</span></span></p> + +<h2 id="Установка_Express_Application_Generator">Установка Express Application Generator</h2> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Инструмент Express Application Generator создает «скелет» приложения Express.</span> <span title="">Установите генератор, используя NPM, как показано (флаг -g устанавливает инструмент глобально, чтобы вы могли вызывать его из любого места):</span></span></p> + +<pre class="notranslate"><code>npm install express-generator -g</code></pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Чтобы создать приложение Express с именем «helloworld» с настройками по умолчанию, перейдите туда, где вы хотите его создать, и запустите приложение, как показано ниже:</span></span></p> + +<pre class="brush: bash notranslate">express helloworld</pre> + +<div class="note"> +<p><strong>Замечание:</strong> <span class="tlid-translation translation" lang="ru"><span title="">Вы также можете указать библиотеку шаблонов для использования и ряд других настроек.</span> <span title="">Используйте команду help, чтобы увидеть все параметры:</span></span></p> + +<pre class="brush: bash notranslate">express --help +</pre> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">NPM создаст новое приложение Express в подпапке вашего текущего местоположения, отображая процесс сборки на консоли.</span> <span title="">По завершении инструмент отобразит команды, которые необходимо ввести, чтобы установить зависимости Node и запустить приложение.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Новое приложение будет иметь файл package.json в своем корневом каталоге.</span> <span title="">Вы можете открыть это, чтобы увидеть, какие зависимости установлены, включая Express и библиотеку шаблонов Jade:</span></span></p> + +<pre class="brush: js notranslate">{ + "name": "helloworld", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www" + }, + "dependencies": { + "body-parser": "~1.18.2", + "cookie-parser": "~1.4.3", + "debug": "~2.6.9", + "express": "~4.15.5", + "jade": "~1.11.0", + "morgan": "~1.9.0", + "serve-favicon": "~2.4.5" + } +}</pre> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Установите все зависимости для приложения helloworld, используя NPM, как показано ниже:</span></span></p> + +<pre class="brush: bash notranslate">cd helloworld +npm install +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Затем запустите приложение (команды немного отличаются для Windows и Linux / macOS), как показано ниже:</span></span></p> + +<pre class="brush: bash notranslate"># Run the helloworld on Windows with Command Prompt +SET DEBUG=helloworld:* & npm start + +# Run the helloworld on Windows with PowerShell +SET DEBUG=helloworld:* | npm start + +# Run helloworld on Linux/macOS +DEBUG=helloworld:* npm start +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Команда DEBUG создает полезное ведение журнала, что приводит к выводу, подобному показанному ниже.</span></span></p> + +<pre class="brush: bash notranslate">>SET DEBUG=helloworld:* & npm start + +> helloworld@0.0.0 start D:\Github\expresstests\helloworld +> node ./bin/www + + helloworld:server Listening on port 3000 +0ms</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Откройте браузер и перейдите по адресу http://127.0.0.1:3000/, чтобы увидеть страницу приветствия Express по умолчанию.</span></span></p> + +<p><img alt="Express - Generated App Default Screen" src="https://mdn.mozillademos.org/files/14331/express_default_screen.png" style="border-style: solid; border-width: 1px; display: block; height: 301px; margin: 0px auto; width: 675px;"></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Мы поговорим больше о сгенерированном приложении, когда перейдем к статье о создании каркасного приложения.</span></span></p> + +<ul> +</ul> + +<h2 id="Резюме">Резюме</h2> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Теперь на вашем компьютере установлена и запущена среда разработки Node, которую можно использовать для создания веб-приложений Express.</span> <span title="">Вы также увидели, как NPM можно использовать для импорта Express в приложение, а также как вы можете создавать приложения с помощью инструмента Express Application Generator и затем запускать их.</span><br> + <br> + <span title="">В следующей статье мы начнем работу с учебным пособием по созданию полноценного веб-приложения с использованием этой среды и связанных инструментов.</span></span></p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://nodejs.org/en/download/">Downloads</a> page (nodejs.org)</li> + <li><a href="https://nodejs.org/en/download/package-manager/">Installing Node.js via package manager</a> (nodejs.org)</li> + <li><a href="http://expressjs.com/en/starter/installing.html">Installing Express</a> (expressjs.com)</li> + <li><a href="https://expressjs.com/en/starter/generator.html">Express Application Generator</a> (expressjs.com)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Introduction", "Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="В_этом_модуле">В этом модуле</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html new file mode 100644 index 0000000000..2e1edbc625 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html @@ -0,0 +1,85 @@ +--- +title: Список авторов. Тест - список жанров +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page +--- +<p>Страница списка авторов должна показывать список всех авторов, хранимых в БД, причем каждое имя автора должно быть связано со страницей подробностей для этого автора. Дата рождения автора и дата смерти должны выводиться в одной строке после имени автора.</p> + +<h2 class="highlight-spanned" id="Контроллер"><span class="highlight-span">Контроллер</span></h2> + +<p>Функция контроллера списка авторов должна получить список всех элементов в <code>Author</code> , и передать этот список в шаблон для отображения.</p> + +<p>Откройте файл <strong>/controllers/authorController.js</strong>. Найдите экспортируемый метод <code>author_list()</code> в начале файла и замените его следующим ниже кодом:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all Authors.</span> +exports<span class="punctuation token">.</span>author_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Author<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">sort</span><span class="punctuation token">(</span><span class="punctuation token">[</span><span class="punctuation token">[</span><span class="string token">'family_name'</span><span class="punctuation token">,</span> <span class="string token">'ascending'</span><span class="punctuation token">]</span><span class="punctuation token">]</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_authors<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">//Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'author_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Author List'</span><span class="punctuation token">,</span> author_list<span class="punctuation token">:</span> list_authors <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>Метод использует такие функции модели как <code>find()</code>, <code>sort()</code> и <code>exec()</code> для того, чтобы вернуть все объекты <code>Author</code> отсортированными по <code>family_name</code> в алфавитном порядке. В вызове <code>exec()</code> callback-функция имеет первый параметр- объект ошибок (или <code>null</code>) и второй параметр - список всех авторов, если ошибок не было. При ошибках вызывается следующая функция промежуточного слоя с полученным значением объекта ошибок, а если ошибок не было, отображается шаблон <strong>author_list</strong>(.pug), передавая странице <code>title</code> и список авторов (<code>author_list</code>).</p> + +<h2 class="highlight-spanned" id="Представление"><span class="highlight-span">Представление</span></h2> + +<p>Создайте файл <strong>/views/author_list.pug</strong> и поместите в него следующий текст:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each author <span class="keyword token">in</span> author_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>author<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>author<span class="punctuation token">.</span>name<span class="punctuation token">}</span> + <span class="operator token">|</span> <span class="punctuation token">(</span>#<span class="punctuation token">{</span>author<span class="punctuation token">.</span>date_of_birth<span class="punctuation token">}</span> <span class="operator token">-</span> #<span class="punctuation token">{</span>author<span class="punctuation token">.</span>date_of_death<span class="punctuation token">}</span><span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no authors<span class="punctuation token">.</span></code></pre> + +<p>Представление создано по тому же образцу, что и другие шаблоны.</p> + +<h2 class="highlight-spanned" id="Как_это_выглядит"><span class="highlight-span">Как это выглядит?</span></h2> + +<p>Запустите приложение и откройте браузер с адресом <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. Выберите ссылку <em>All authors</em>. Если все было сделано правильно, страница должна выглядеть примерно нак, как на следующем скриншоте.</p> + +<p><img alt="Author List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14468/LocalLibary_Express_Author_List.png" style="display: block; height: 453px; margin: 0px auto; width: 1200px;"></p> + +<div class="note"> +<p><strong>Заметка:</strong> Представление дат продолжительности жизни автора выгядит безобразно! Это можно исправить, если использовать <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data#date_formatting">тот же подход</a> , который применялся для списка <code>BookInstance</code> (добавить в модель <code>Author</code> виртуальное свойство продолжительности жизни). Но в этот раз, однако, некоторые даты могут отсутствовать, и ссылки на несуществующие свойства игнорируются, если не задан строгий режим. Метод <code>moment()</code> возврашает текущее время, и нежелательно, чтобы отсутствующие даты форматировались как "сегодня". Один из способов состоит в том, чтобы форматирующая функция возвращала пустую строку, если дата не существует. Например:</p> + +<p><code>return this.date_of_birth ? moment(this.date_of_birth).format('YYYY-MM-DD') : '';</code></p> +</div> + +<h2 id="Тест_-_страница_списка_жанров!Edit">Тест - страница списка жанров!<a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data$edit#Genre_list_page—challenge!" rel="nofollow, noindex"><span>Edit</span></a></h2> + +<p>В этой части требуется создать собственную страницу списка жанров. Страница должна показывать жанры, имеющиеся в БД, а для каждого жанра должна быть создана ссылка на страницу с детальной информацией. Скриншот ожидаемого результата приводится ниже.</p> + +<p><img alt="Genre List - Express Local Library site" src="https://mdn.mozillademos.org/files/14460/LocalLibary_Express_Genre_List.png" style="border-style: solid; border-width: 1px; display: block; height: 346px; margin: 0px auto; width: 600px;"></p> + +<p>Функция контроллера списка жанров должна получить список всех экземпляров <code>Genre</code>, и передать его в шаблон для отображения.</p> + +<ol> + <li>Следует отредактировать <code>genre_list()</code> в файле <strong>/controllers/genreController.js</strong>. </li> + <li>Реализация почти такая же, как и для контроллера <code>author_list()</code> . + <ul> + <li>Sort the results by name, in ascending order.</li> + </ul> + </li> + <li>Отображающий шаблон должен быть назван <strong>genre_list.pug</strong>.</li> + <li>Шаблону для отображения должны быть переданы переменные <code>title</code> (строка 'Genre List') и <code>genre_list</code> (the list of список жанров, который вернет callback-функция <code>Genre.find()</code>.</li> + <li>Представление должно соответствовать скриншоту, приведенному ранее (оно должно иметь структуру и формат, похожие на таковые в представлении списка авторов, за исключением, конечно, продолжительности жизни, так как для жанров даты не заданы).</li> +</ol> + +<h2 id="Далее">Далее</h2> + +<p>Вернуться к части 5 - <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</p> + +<p>Перейти к следующему подразделу в части 5: подробная информация о жанрах (<a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page">Genre detail page</a>).</p> diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html new file mode 100644 index 0000000000..b5a4400d90 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html @@ -0,0 +1,68 @@ +--- +title: Страница списка книг +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page +--- +<p>Далее мы реализуем нашу страницу списка книг. На этой странице должен отображаться список всех книг и их авторов в базе данных, причем каждое название книги является гиперссылкой на соответствующую страницу сведений о книге.</p> + +<h2 class="highlight-spanned" id="Контроллер"><span class="highlight-span">Контроллер</span></h2> + +<p>Функция контроллера списка книг должна получить список всех объектов <code>Book</code> в базе данных, а затем передать их для отрисовки шаблона.</p> + +<p>Откройте файл <strong>/controllers/bookController.js</strong>. Найдите экспортируемый метод контроллера <code>book_list()</code> и замените его следующим кодом.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all Books.</span> +exports<span class="punctuation token">.</span>book_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Book<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="string token">'title author'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_books<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">//Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'book_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Book List'</span><span class="punctuation token">,</span> book_list<span class="punctuation token">:</span> list_books <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>Метод использует функцию модели<code>find()</code> для возврата всех объектов <code>Book</code>, выбрав для возврата только заголовок и автора, поскольку нам не нужны другие поля (он также вернет <code>_id</code> и виртуальные поля). Здесь мы также вызываем <code>populate()</code> on <code>Book</code>, указывая поле <code>author</code> —это заменит сохраненный идентификатор автора книги полными сведениями об авторе.</p> + +<p>При успешном выполнении, обратный вызов передаст запрос на отрисовку шаблона <strong>book_list</strong>(.pug), передаст <code>title</code> и<code>book_list</code> (список книг с автором) в качестве переменных.</p> + +<h2 class="highlight-spanned" id="Представление"><span class="highlight-span">Представление</span></h2> + +<p>Создайте файл <strong>/views/book_list.pug</strong> и скопируйте в него текст ниже.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each book <span class="keyword token">in</span> book_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>book<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>book<span class="punctuation token">.</span>title<span class="punctuation token">}</span> + <span class="operator token">|</span> <span class="punctuation token">(</span>#<span class="punctuation token">{</span>book<span class="punctuation token">.</span>author<span class="punctuation token">.</span>name<span class="punctuation token">}</span><span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no books<span class="punctuation token">.</span></code></pre> + +<p>View расширит базовый шаблон <strong>layout.pug</strong> и переопределит <code>block</code> с именем '<strong>content</strong>'. Он отображает <code>title</code> который мы передали из контроллера (с помощью метода <code>render()</code> ), а затем перебирает переменную <code>book_list</code> используя синтаксис <code>each</code>-<code>in</code>-<code>else</code> . Для каждой книги создается элемент списка, отображающий название книги в виде ссылки на страницу сведений о книге, за которой следует имя автора. Если в <code>book_list</code> нет книг, то выполняется <code>else</code>, и отображается текст "нет книг".'</p> + +<div class="note"> +<p><strong>Заметка: </strong>Мы используем <code>book.url</code> для предоставления ссылки на подробную запись для каждой книги (мы реализовали этот маршрут, но не страницу). Это виртуальное свойство модели <code>Book</code> , которая использует поле <code>_id</code> для создания уникального URL.</p> +</div> + +<p>Здесь интересно, что каждая книга определена в двух строках, использование конвейера для второй строки (выделено выше) необходимо, чтобы имя автора не стало частью гиперссылки из первой строки.</p> + +<h2 class="highlight-spanned" id="На_что_это_похоже">На что это похоже?</h2> + +<p>Запустите приложение (смотрите <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes#Testing_the_routes">тестирование маршрутов</a> для соответствующей команды) и откройте ваш браузер по адресу: <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. Затем выберите ссылку: <em>All books</em>. Если все сделано корректно, то вы должны увидеть нечто подобное скриншоту ниже.</p> + +<p><img alt="Book List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14464/LocalLibary_Express_Book_List.png" style="border-style: solid; border-width: 1px; display: block; height: 387px; margin: 0px auto; width: 918px;"></p> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page">BookInstance list page</a>.</li> +</ul> diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html new file mode 100644 index 0000000000..512e78d040 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html @@ -0,0 +1,69 @@ +--- +title: Список экземпляров книг +slug: Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page +--- +<p>Далее мы реализуем список всех имеющихся в библиотеке копий книги (<code>BookInstance</code>) . Эта страница должна включать название книги из <code>Book</code>, с которой связаны экземпляры <code>BookInstance</code> (linked to its detail page), а такжде дополнительнцю информацию, имеющуюся в модели <code>BookInstance</code>, включая статус, издание, и уникальный идентификатор каждой копии. Уникальное значение идентификатора копии должно быть связано со страницей детальной информации <code>BookInstance</code>.</p> + +<h2 class="highlight-spanned" id="Контроллер"><span class="highlight-span">Контроллер</span></h2> + +<p>Функция контроллера списка <code>BookInstance</code> требуется для получения списка всех экземпляров некоторой книги, для получения информации, связанной с книгой, и для передачиполученного списка в шаблог для отображения.</p> + +<p>Откройте файл <strong>/controllers/bookinstanceController.js</strong>. Найдите экспортируемый метод <code>bookinstance_list()</code> контроллера и замените его следующим кодом (измененный код выделен жирным).</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all BookInstances.</span> +exports<span class="punctuation token">.</span>bookinstance_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + <strong>BookInstance<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'book'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_bookinstances<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'bookinstance_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Book Instance List'</span><span class="punctuation token">,</span> bookinstance_list<span class="punctuation token">:</span> list_bookinstances <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></strong> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>Чтобы вернуть все объекты <code>BookInstance,</code> метод использует функцию <code>find()</code> модели. Далее в цепочке вызывается метод <code>populate()</code> с аргументом - полем <code>book,</code> что приводит к замене идентификатора id, хранящегося для каждого экземпляра <code>BookInstance</code> полным документом <code>Book</code>.</p> + +<p>При удаче, callback-функция, переданная запросу, заполняет шаблон <strong>bookinstance_list</strong>(.pug), передав переменные <code>title</code> и <code>bookinstance_list</code>.</p> + +<h2 class="highlight-spanned" id="Представление">Представление</h2> + +<p>Создайте файл <strong>/views/bookinstance_list.pug</strong> и скопируйте в него текст, приведенный ниже.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each val <span class="keyword token">in</span> bookinstance_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>val<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>book<span class="punctuation token">.</span>title<span class="punctuation token">}</span> <span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>imprint<span class="punctuation token">}</span> <span class="operator token">-</span> + <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">==</span><span class="string token">'Available'</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>success #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">==</span><span class="string token">'Maintenance'</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>danger #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">else</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>warning #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">!=</span><span class="string token">'Available'</span> + span <span class="function token"> </span><span class="punctuation token">(</span>Due<span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>due_back<span class="punctuation token">}</span> <span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no book copies <span class="keyword token">in</span> <span class="keyword token">this</span> library<span class="punctuation token">.</span></code></pre> + +<p>This view is much the same as all the others. It extends the layout, replacing the <em>content</em> block, displays the <code>title</code> passed in from the controller, and iterates through all the book copies in <code>bookinstance_list</code>. For each copy we display its status (colour coded) and if the book is not available, its expected return date. One new feature is introduced—we can use dot notation after a tag to assign a class. So <code>span.text-success</code> will be compiled to <code><span class="text-success"></code> (and might also be written in Pug as <code>span(class="text-success")</code>.</p> + +<h2 class="highlight-spanned" id="What_does_it_look_like"><span class="highlight-span">What does it look like?</span></h2> + +<p>Run the application, open your browser to <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>, then select the <em>All book-instances </em>link. If everything is set up correctly, your site should look something like the following screenshot.</p> + +<p><img alt="BookInstance List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14474/LocalLibary_Express_BookInstance_List.png" style="border-style: solid; border-width: 1px; display: block; height: 322px; margin: 0px auto; width: 1200px;"></p> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment">Date formatting using moment</a>.</li> +</ul> diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html new file mode 100644 index 0000000000..58f297ce95 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html @@ -0,0 +1,60 @@ +--- +title: Форматирование даты при помощи moment +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment +--- +<p>По умолчанию отображение дат наших моделей некрасиво: <em>Tue Dec 06 2016 15:49:58 GMT+1100 (AUS Eastern Daylight Time)</em>. В этом разделе мы покажем, как можно обновить страницу списка <em>BookInstance List </em>из предыдущего раздела, чтобы представитьполе <code>due_date</code> в более удобном формате: December 6th, 2016. </p> + +<p>Подход, который будет использован, состоит в создании виртуального свойства в модели <code>BookInstance</code>, которое будет возращать отформатированную дату. Форматирование будет производиться с использованием <a class="external external-icon" href="https://www.npmjs.com/package/moment" rel="noopener">moment</a>, легковесной библиотеки JavaScript для разбора, проверки, изменения и форматирования дат.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Можно применять <em>moment</em> для форматирования непосредственно в шаблонах Pug, а можно отформатировать строку в других местах. Использование виртуального свойства позволяет получить дату, отформатированную точно так же, как при помощи <code>due_date</code>. </p> +</div> + +<h2 class="highlight-spanned" id="Установка_moment"><span class="highlight-span">Установка moment</span></h2> + +<p>Ведите следующую команду в корне проекта:</p> + +<pre class="brush: bash line-numbers language-bash"><code class="language-bash">npm install moment</code></pre> + +<h2 class="highlight-spanned" id="Создание_виртуального_свойства"><span class="highlight-span">Создание виртуального свойства</span></h2> + +<ol> + <li>Откройте файл <strong>./models/bookinstance.js</strong>.</li> + <li>В начало файла введите строку для импортирования <em>moment</em>. + <pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">var</span> moment <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'moment'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + </li> +</ol> + +<p>Добавьте виртуальное свойство <code>due_back_formatted</code> сразу после свойства url.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">BookInstanceSchema +<span class="punctuation token">.</span><span class="function token">virtual</span><span class="punctuation token">(</span><span class="string token">'due_back_formatted'</span><span class="punctuation token">)</span> +<span class="punctuation token">.</span><span class="keyword token">get</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">return</span> <span class="function token">moment</span><span class="punctuation token">(</span><span class="keyword token">this</span><span class="punctuation token">.</span>due_back<span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">format</span><span class="punctuation token">(</span><span class="string token">'MMMM Do, YYYY'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<div class="note"> +<p><strong>Заметка:</strong> Метод format method может вывести дату почти по любому образцу. Синтаксис для представления различных составляющих даты можно найти в документации ( <a class="external external-icon" href="http://momentjs.com/docs/#/displaying/" rel="noopener">moment documentation</a>).</p> +</div> + +<h2 class="highlight-spanned" id="Обновляем_представление"><span class="highlight-span">Обновляем представление</span></h2> + +<p>Откройте файл <strong>/views/bookinstance_list.pug</strong> и замените <code>due_back</code> на <code>due_back_formatted</code>.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"> <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">!=</span><span class="string token">'Available'</span> + <span class="comment token">//span (Due: #{val.due_back} )</span> + span <span class="function token"> </span><span class="punctuation token">(</span>Due<span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>due_back_formatted<span class="punctuation token">}</span> <span class="punctuation token">)</span> </code></pre> + +<p>Вот и все. Если вы перейдете к <em>All book-instances</em> в боковом меню, вы должны увидеть все даты в привлекательном формате!</p> + +<p> </p> + +<h2 id="Далее">Далее</h2> + +<ul> + <li>Вернуться к <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Перейти к следующему подразделу части 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">Author list page and Genre list page challenge</a> (страница списка авторов и тест- страница списка жанров).</li> +</ul> + +<p> </p> diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html new file mode 100644 index 0000000000..32100db740 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html @@ -0,0 +1,138 @@ +--- +title: Асинхронное управление потоками при помощи async +slug: Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async +tags: + - Node + - Часть 5 +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async +--- +<p>Код контроллера для некоторых страниц библиотеки будет зависеть от результатов многих асинхронных запросов, которые должны выполняться в определенном порядке или параллельно. Для того, чтобы управлять потоком выполнения, и выводить страницы, когда получена вся необходимая информация, будет использован <a class="external external-icon" href="https://www.npmjs.com/package/async" rel="noopener">async</a> - известный модуль node.</p> + +<div class="note"> +<p><strong>Note:</strong> В JavaScript существует много других способов управления аснхронным поведением и потоком выполнения, включая такой относительно новый элемент языка JacaScript как <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Techniques/Promises">Promises</a> (обещания, промисы).</p> +</div> + +<p>Модуль Async имеет массу полезных методов (см. документациюt <a class="external external-icon" href="http://caolan.github.io/async/docs.html" rel="noopener">the documentation</a>). Вот некоторые наиболее важные функции:</p> + +<ul> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#parallel" rel="noopener">async.parallel()</a></code> для осуществеления любых операций, которые должны выполняться параллельно.</li> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#series" rel="noopener">async.series()</a></code> если нужно иметь уверенность, что асинхронные операции выполняются последовательно.</li> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#waterfall" rel="noopener">async.waterfall()</a></code> для операций, которые должны выполняться последовательно, причем каждая операция зависит от результатов предыдущих операций.</li> +</ul> + +<h2 class="highlight-spanned" id="Почему_это_необходимо"><span class="highlight-span">Почему это необходимо?</span></h2> + +<p>Большинство методов, которые используются в <em>Express</em> - <span class="highlight-span">асинхронные - вы определяете выполняемую операцию, передавая </span> callback-функцию. Метод завершается немедленно, а callback-функция вызывается тогда, когда завершилась запрошенная операция. По соглашению, принятому в <em>Express</em>, callback-функция передает значение ошибки <em>error</em> как первый параметр (или <code>null</code> при успехе) и результат функции (если есть) как второй параметр.</p> + +<p>Если контроллер должен выполнить только одну асинхронную операцию, чтобы получить информацию для представления страницы, то реализация проста - мы просто представляем шаблон в колбэке. Фрагмент кода (ниже) демонстрирует это для функции, которая подсчитывает количество элементов модкли <code>SomeModel</code> (применяя метод Mongoose <code><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.count" rel="noopener">count()</a></code> ):</p> + +<pre class="brush: js"><code>exports.some_model_count = function(req, res, next) { + +</code> SomeModel.count({ a_model_field: 'match_value' }, function (err, count) { + // ... сделать что-то, если ошибка + + // При успехе представить результат, передав count в render-функцию (здесь - как переменную 'data'). + res.render('the_template', { data: count } ); + }); +<code>}</code> +</pre> + +<p>Однако что, если требуется сделать <strong>множественные</strong> асинхронные запросы, и результат нельзя представить, пока не завершились все операции? Наивная реализация могла бы использовать "венок" запросов, запуская последующие запросы в колбэках предыдущих, и представляя ответ в последнем колбэке. Проблема такого подхода состоит в том, что запросы должны вапольняться последовательно, хотя, вероятно, было бы более эффективно выполнять их параллельно. Это также может привести к усложненному вложенному коду, что обычно называют адом обратных вызовов ( <a class="external external-icon" href="http://callbackhell.com/" rel="noopener">callback hell</a> ).</p> + +<p>Намного лучше было бы выполнять все запросы параллельно, и иметь единственную callback-функцию, которая будет вызвана после того как все запросы выполнены. Именно такое выполнение операций модуль <em>Async</em> делает легким и простым!</p> + +<h2 class="highlight-spanned" id="Параллельные_асинхронные_операции"><span class="highlight-span">Параллельные асинхронные операции</span></h2> + +<p>Метод <code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#parallel" rel="noopener">async.parallel()</a></code> используется для параллельного выполнения нескольких асинхронных операций.</p> + +<p>Первый аргумент в <code>async.parallel()</code> - это коллекция асинхронных функций, которые требуется выполнить (массив, объект или другой итерируемый элемент). Каждая функция получает callback-функцию <code>callback(err, result)</code> , которую она должна вызвать при завершении, с ошибкой <code>err</code> (может быть <code>null</code>) и, возможно, со значением результата <code>results</code>.</p> + +<p>Возможный второй аргумент для <code>async.parallel()</code> - это callback -функция, которая должна быть вызвана после завершения всех функций, указанных в первом аргументе. Эта функция вызывается с аргументом ошибки и результатом - коллекцией результатов отдельных асинхронных операций. Тип коллекции - такой же, как и тип первого аргумента async.parallel (т.е. если передается <em>массив</em> асинхронных функций, итоговая callback-функция будет вызвана с <em>массивом</em> результатов). Если любая из параллельных функций сообщила об ошибке, сразу вызывается итоговая callback-функция, которая возвращает ошибку.</p> + +<p>Пример ниже показывает, как это работает в случае, когда первый аргумент является объектом. Как видно, результаты возвращаются в объекте с такими же именами свойств, как у переданных функций.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">parallel</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + one<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + two<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> + something_else<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// 'results' равны: {one: 1, two: 2, ..., something_else: some_value}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>Если вместо объекта передать <em>массив </em>функций как первый аргумент, результатом будет массив (порядок результатов в массиве такой же, как и порядок функций в массиве, а не порядок выполнения функций).</p> + +<h2 class="highlight-spanned" id="Последовательные_асинхронные_операции"><span class="highlight-span">Последовательные асинхронные операции</span></h2> + +<p>Для выполнения нескольких асинхронных операций последовательно используется метод <code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#series" rel="noopener">async.series()</a></code> , при этом <span class="highlight-span">последующие функции не зависят от результатов предыдущих функций</span>. Метод определяется и ведет себя так же, как и <code>async.parallel()</code>.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">series</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + one<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + two<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> + something_else<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback after the last asynchronous function completes.</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// 'results' is now equals to: {one: 1, two: 2, ..., something_else: some_value} </span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<div class="note"> +<p><strong>Заметка:</strong> Спецификация языка ECMAScript (JavaScript) устанавливает, что порядок в перечислении объектов не определен, поэтому возможно, что функции не будут вызываться в том порядке, в котором вы их задали на всех платформах. Если порядок вызова действительно важен, вместо объекта следует передавать массив, как показано ниже.</p> +</div> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">series</span><span class="punctuation token">(</span><span class="punctuation token">[</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// do some stuff ...</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'one'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// do some more stuff ... </span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'two'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">]</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// results is now equal to ['one', 'two'] </span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="Последовательные_зависимые_асинхронные_операции"><span class="highlight-span">Последовательные зависимые асинхронные операции</span></h2> + +<p>Выполнение нескольких асинхронных операций последовательно, когда каждая операция зависит от результатов предыдущих операций, осуществляется методом <code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#waterfall" rel="noopener">async.waterfall()</a></code>.</p> + +<p>Функции-callback, которая вызываются асинхронными функциями , содержит <code>null</code> как первый аргумент, и результаты в следующих аргументах. Каждая функция в последовательности (кроме первой) как аргументы использует результаты предыдущих функция, а callback-функция является последним аргументом. Когда операции завершаются, вызывается финальная callback-функция, аргументы которой - объект err и результат последней операции. Как это работает, станет более ясным после рассмотрения примера - фрагмента кода, приведенного ниже ( пример взят из документации <em>async</em>):</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">waterfall</span><span class="punctuation token">(</span><span class="punctuation token">[</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{//первая функция в цепочке</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'one'</span><span class="punctuation token">,</span> <span class="string token">'two'</span><span class="punctuation token">)</span><span class="punctuation token">;//результаты 'one' и 'two'</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>arg1<span class="punctuation token">,</span> arg2<span class="punctuation token">,</span> callback<span class="punctuation token">)</span> <span class="punctuation token">{//вторая функция в цепочке</span> + <span class="comment token">// arg1 равен 'one' , arg2 равен 'two' </span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'three'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> //результат 'three' + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>arg1<span class="punctuation token">,</span> callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// arg1 равен 'three'</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'done'</span><span class="punctuation token">)</span><span class="punctuation token">; //результат 'done'</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">,</span> <span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> result<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// result равен 'done'</span> +<span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="Установка_async"><span class="highlight-span">Установка async</span></h2> + +<p>Установим модуль async при помощи менеджера пакетов NPM, чтобы использовать его в своем коде. Это делается обычным способом - откроем окно команд в корне проекта <em>LocalLibrary</em> и введем команду:</p> + +<pre class="brush: bash line-numbers language-bash"><code class="language-bash">npm install async</code></pre> + +<h2 id="Дальнейшие_шаги">Дальнейшие шаги</h2> + +<ul> + <li>Вернуться учебнику <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express, Part 5: Вывести данные библиотеки</a>.</li> + <li>Перейти к следующему разделу части 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer">Основы шаблонов</a>.</li> +</ul> diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html new file mode 100644 index 0000000000..be5bd57962 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html @@ -0,0 +1,121 @@ +--- +title: Страница с подробностями жанров +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page +--- +<p>Страница "подробности" (<em>detail)</em> для жанров должна показывать информацию для отдельного жанра по его автоматически генерируему идентификатору <code>_id</code>. Должно быть показано название жанра и список книг этого жанра, со ссылками на страницу с детальной информацией для каждой книги.</p> + +<h2 id="Controller">Controller</h2> + +<p>Откройте файл<em> </em><strong>/controllers/genreController.js</strong> и импортируйте модули <em>async</em> и <em>Book</em> в первых строках файла.</p> + +<pre class="brush: js">var Book = require('../models/book'); +var async = require('async'); +</pre> + +<p>Найдите экспортируемый метод контроллера <code>genre_detail</code><code>()</code> и замените его следующим кодом:</p> + +<pre class="brush: js">// Display detail page for a specific Genre. +exports.genre_detail = function(req, res, next) { + +<strong> async.parallel({ + genre: function(callback) { + Genre.findById(req.params.id) + .exec(callback); + }, + + genre_books: function(callback) { + Book.find({ 'genre': req.params.id }) + .exec(callback); + }, + + }, function(err, results) { + if (err) { return next(err); } + if (results.genre==null) { // No results. + var err = new Error('Genre not found'); + err.status = 404; + return next(err); + } + // Successful, so render + res.render('genre_detail', { title: 'Genre Detail', genre: results.genre, genre_books: results.genre_books } ); + });</strong> + +}; +</pre> + +<p>Метод использует <code>async.parallel()</code> для параллельного запроса названия жанра и связанных с ним книг, причем callback-функция возвращает страницу, когда (если) оба запроса завершились успешно.</p> + +<p>The ID of the required genre record is encoded at the end of the URL and extracted automatically based on the route definition (<strong>/genre/:id</strong>). The ID is accessed within the controller via the request parameters: <code style="font-style: normal; font-weight: normal;">req.params.id</code>. It is used in <code style="font-style: normal; font-weight: normal;">Genre.findById()</code> to get the current genre. It is also used to get all <code>Book</code> objects that have the genre ID in their <code>genre</code> field: <code>Book.find({ 'genre': req.params.id })</code>.</p> + +<div class="note"> +<p><strong>Note:</strong> If the genre does not exist in the database (i.e. it may have been deleted) then <code>findById()</code> will return successfully with no results. In this case we want to display a "not found" page, so we create an <code>Error</code> object and pass it to the <code>next</code> middleware function in the chain. </p> + +<pre class="brush: js"><strong>if (results.genre==null) { // No results. + var err = new Error('Genre not found'); + err.status = 404; + return next(err); +}</strong> +</pre> + +<p>The message will then propagate through to our error handling code (this was set up when we <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website#error_handling">generated the app skeleton</a> - for more information see <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Handling_errors">Handling Errors</a>).</p> +</div> + +<p>The rendered view is <strong>genre_detail</strong> and it is passed variables for the <code>title</code>, <code>genre</code> and the list of books in this genre (<code>genre_books</code>).</p> + +<h2 id="View">View</h2> + +<p>Create <strong>/views/genre_detail.pug</strong> and fill it with the text below:</p> + +<pre class="brush: js">extends layout + +block content + + <strong>h1 Genre: #{genre.name}</strong> + + div(style='margin-left:20px;margin-top:20px') + + h4 Books + + dl + each book in genre_books + dt + a(href=book.url) #{book.title} + dd #{book.summary} + + else + p This genre has no books +</pre> + +<p>The view is very similar to all our other templates. The main difference is that we don't use the <code>title</code> passed in for the first heading (though it is used in the underlying <strong>layout.pug</strong> template to set the page title).</p> + +<h2 id="What_does_it_look_like">What does it look like?</h2> + +<p>Run the application and open your browser to <a href="http://localhost:3000/">http://localhost:3000/</a>. Select the <em>All genres</em> link, then select one of the genres (e.g. "Fantasy"). If everything is set up correctly, your page should look something like the following screenshot.</p> + +<p><img alt="Genre Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14462/LocalLibary_Express_Genre_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 523px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p>You might get an error similar to this:</p> + +<pre class="brush: bash">Cast to ObjectId failed for value " 59347139895ea23f9430ecbb" at path "_id" for model "Genre" +</pre> + +<p>This is a mongoose error coming from the <strong>req.params.id</strong>. To solve this problem, first you need to require mongoose on the <strong>genreController.js</strong> page like this:</p> + +<pre class="brush: js"> var mongoose = require('mongoose'); +</pre> + +<p>Then use <strong>mongoose.Types.ObjectId() </strong>to convert the id to a that can be used. For example:</p> + +<pre class="brush: js">exports.genre_detail = function(req, res, next) { + var id = mongoose.Types.ObjectId(req.params.id); + ... +</pre> +</div> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page">Book detail page</a>.</li> +</ul> diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/home_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/home_page/index.html new file mode 100644 index 0000000000..248187f1a5 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/home_page/index.html @@ -0,0 +1,134 @@ +--- +title: Home page +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Home_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Home_page +--- +<p>Первой создаваемой страницей будет домашняя страница веб-сайта, доступная из корня сайта (<code>'/'</code>) или из каталога (<code>catalog/</code>). На странице будет виден статический текст, описывающий сайт, и динамически вычисляемые "количества" записей разных типов имеющихся в БД.</p> + +<p>Маршрут для домашней страницы уже создан. Для завершения страницы обновить функции контроллера, чтобы он извлекал количество записей из БД, и создавал представление (шаблон), который можно использовать для презентации страницы.</p> + +<h2 id="Маршрут">Маршрут</h2> + +<p>Маршруты индексной страницы созданы ранее в предыдущем разделе (<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes">previous tutorial).</a> Напомним, все функции маршрутов определены в файле <strong>/routes/catalog.js</strong>:</p> + +<pre class="brush: js ">// GET catalog home page. +router.get('/', book_controller.index); //This actually maps to /catalog/ because we import the route with a /catalog prefix</pre> + +<p>Параметр callback-функции определен в <strong>/controllers/bookController.js</strong>:</p> + +<pre class="brush: js">exports.index = function(req, res, next) { + res.send('NOT IMPLEMENTED: Site Home Page'); +}</pre> + +<p>Именно эту функцию контроллера мы расширим, чтобы получать информацию из моделей и затем отображать ее, используя шаблоны (представления).</p> + +<h2 id="Контроллер">Контроллер</h2> + +<p>Функция контроллера индекса должна получать информацию о том, сколько книг (<code>Book)</code>, экземпляров книг (<code>BookInstance)</code>, сколько из них доступно, сколько авторов (<code>Author)</code>, жанров (<code>Genre)</code> имеется в БД, должна поместить эту информацию в шаблон, чтобы создать HTML-страницу, после чего вернуть ее в HTTP-ответе.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Количество экземпляров в каждой модели вычисляется при помощи метода <code><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.countDocuments" rel="noopener">countDocuments()</a></code> . Он вызывается для модели с возможным набором условий, необходимых для проверки соответствия первому аргументу и callback-функции второго аргумента (обсуждалось ранее в "Использование базы данных с Mongoose" <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Using a Database (with Mongoose)</a>), причем можно вернуть также запрос <code>Query,</code> а затем выполнить его позже при помощи callback. Эта callback-функция будет выполняться, когда БД вернет количество записей. Значение ошибки (or <code>null</code>) будет первым параметром, а количество записей (или null, если была ошибка) - вторым параметром.</p> + +<pre class="brush: js ">SomeModel.countDocuments({ a_model_field: 'match_value' }, function (err, count) { + // ... do something if there is an err + // ... do something with the count if there was no error + });</pre> +</div> + +<p>Откройте файл <strong>/controllers/bookController.js</strong>. Почти в самом начале вы должны увидеть экспортируемую функцию <code>index()</code> .</p> + +<pre class="brush: python ">var Book = require('../models/book') + +exports.index = function(req, res, next) { + res.send('NOT IMPLEMENTED: Site Home Page'); +}</pre> + +<p>Замените весь код, показанный выше, на следующий фрагмент кода. Первое, что он делает - импортирует (<code>require()</code>) все модели (выделено жирным). Это требуется, поскольку они нужны для подсчета числа записей. Затем импортируется модуль <em>async</em> .</p> + +<pre class="brush: js "><strong>var Book = require('../models/book'); +var Author = require('../models/author'); +var Genre = require('../models/genre'); +var BookInstance = require('../models/bookinstance');</strong> + +var async = require('async'); + +exports.index = function(req, res) { + + async.parallel({ + book_count: function(callback) { + Book.count<s>Documents</s>({}, callback); // Pass an empty object as match condition to find all documents of this collection +// count<s>Documents</s> не работает, работает только просто count + }, + book_instance_count: function(callback) { + BookInstance.count<s>Documents</s>({}, callback); + }, + book_instance_available_count: function(callback) { + BookInstance.count<s>Documents</s>({status:'Available'}, callback); + }, + author_count: function(callback) { + Author.count<s>Documents</s>({}, callback); + }, + genre_count: function(callback) { + Genre.count<s>Documents</s>({}, callback); + } + }, function(err, results) { + res.render('index', { title: 'Local Library Home', error: err, data: results }); + }); +};</pre> + +<p>Метод <code>async.parallel()</code> передает объект с функциями для получения количества элементов каждой модели. Все эти функции стартуют одновременно. Когда все они завершатся, будет вызвана финальная callback-функция, в итоговом параметре которой содержится нужный нам результат (или ошибка).</p> + +<p>При успешном завершении callback-функции она вызывает <code><a class="external external-icon" href="http://expressjs.com/en/4x/api.html#res.render" rel="noopener">res.render()</a></code>, у которой в качестве параметров - представление (шаблон) '<strong>index</strong>' и объект, содержащий данные, которые следует поместить в шаблон (среди них - количества элементов в моделях). Данные представлены как пары ключ-значение, и могут быть получены в шаблоне по ключу.</p> + +<div class="note"> +<p><strong>Заметка:</strong> В данном случае callback-функция, которую вызывает <code>async.parallel()</code> , несколько необычная - страница отображается всегда, независимо от того, была ошибка или нет (обычно используют отдельный путь выполнения для обработки выводимых ошибок).</p> +</div> + +<h2 id="Представление">Представление</h2> + +<p>Откройте файл <strong>/views/index.pug</strong> и замените его содержимое текстом, приведенным ниже</p> + +<pre class="brush: js ">extends layout + +block content + h1= title + p Welcome to #[em LocalLibrary], a very basic Express website developed as a tutorial example on the Mozilla Developer Network. + + h1 Dynamic content + + if error + p Error getting dynamic content. + else + p The library has the following record counts: + + ul + li #[strong Books:] !{data.book_count} + li #[strong Copies:] !{data.book_instance_count} + li #[strong Copies available:] !{data.book_instance_available_count} + li #[strong Authors:] !{data.author_count} + li #[strong Genres:] !{data.genre_count}</pre> + +<p>Представление несложное. Мы расширили базовый шаблон <strong>layout.pug</strong>, переопределив блок (<code>block)</code> с именем '<strong>content</strong>'. Первый заголовок <code>h1</code> будет экранированным текстом - значением переменной <code>title</code> ,variable that которая передается в функцию <code>render()</code> —заметьте, что применение '<code>h1=</code>' говорит, что следующий текст рассматривается как выражение JavaScript. Затем расположен параграф, знакомящий с LocalLibrary.</p> + +<p>Под заголовком <em>Dynamic content</em> мы проверяем, определена ли переданная из функции <code>render()</code> переменная error. Если да, отмечаем ошибку. Если нет, выводим ( как список) количества копий каждой модели, которые хранятся в переменной <code>data</code>.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Мы не экранируем количества элементов (т.е. используется синтаксис <code>!{}</code> ) потому что эти значения вычисляются. Если бы информация предоставлялась конечным пользователем, следовало бы экранировать переменную перед выводом.</p> +</div> + +<h2 id="Как_это_выглядит">Как это выглядит?</h2> + +<p>Сейчас у нас есть все для того, чтобы показать страницу index. Запустите приложение и откройте браузер с адресом <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. Если все задано правильно, ваш сайт должен иметь примерно такой вид, как на приведенном снимке экрана.</p> + +<p><img alt="Home page - Express Local Library site" src="https://mdn.mozillademos.org/files/14458/LocalLibary_Express_Home.png" style="display: block; height: 440px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p><strong>Заметка:</strong> Элементы бокового меню использовать еще нельзя, так как адреса, представления и шаблоны для этих страниц еще не определены. Если вы попытаетесь их использовать, будет выведено сообщение об ошибке, например, вида "NOT IMPLEMENTED: Book list" (НЕ РЕАЛИЗОВАНО: список книг), в зависимости от выбранного элемента меню. Эти строковые литералы (которые будут замещены действительными данными) были заданы в различных файлах контроллеров в каталоге "controllers".</p> +</div> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page">Book list page</a>.</li> +</ul> diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/index.html new file mode 100644 index 0000000000..bb2e804d2e --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/index.html @@ -0,0 +1,83 @@ +--- +title: 'Учебник Express часть 5: Отображение данных библиотеки' +slug: Learn/Server-side/Express_Nodejs/Displaying_data +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">Теперь мы готовы добавить страницы, на которых будут отображаться книги веб-сайта <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a> и другие данные. Страницы будут включать главную страницу, которая показывает сколько записей определенного типа мы имеем и отдельные страницы для детального просмотра записей. Попутно мы приобретем практический опыт в получении записей из баз данных и использовании шаблонов.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Предварительные знания:</th> + <td>Завершите изучение предыдущих тем учебника (включая <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Учебник Express часть 4: Маршруты и контроллеры</a>).</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понять, как использовать асинхронный модуль и язык шаблона Pug, и как получить данные из URL в наших функциях контроллера.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>В предыдущих статьях учебника мы определили <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Mongoose модели</a>, которые можно использовать для взаимодействия с базой данных и создания некоторых исходных записей библиотеки. Затем мы <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">создали все маршруты</a>, необходимые для веб-сайта LocalLibrary, но с "фиктивными" функциями контроллеров (это скелетные функции, которые просто возвращают сообщение "не реализовано " при доступе к странице).</p> + +<p>Следующим шагом является обеспечение правильных реализаций для страниц, которые отображают информацию из библиотеки (мы рассмотрим реализацию страниц с формами для создания, обновления или удаления информации в последующих статьях). Это включает в себя обновление функций контроллера для извлечения записей с помощью наших моделей и определение шаблонов для отображения этой информации пользователям.</p> + +<p>Мы начнем с обзорных / основных тем, объясняющих, как управлять асинхронными операциями в функциях контроллера и как писать шаблоны с помощью Pug. Затем мы предоставим реализации для каждой из наших основных страниц" только для чтения " с кратким объяснением любых специальных или новых функций, которые они используют.</p> + +<p>В конце этой статьи вы должны иметь хорошее сквозное понимание того, как маршруты, асинхронные функции, представления и модели работают на практике.</p> + +<h2 id="Отображение_данных_библиотеки_—_подразделы">Отображение данных библиотеки — подразделы</h2> + +<p>Следующие подразделы проходят процесс добавления различных функций, необходимых для отображения необходимых страниц веб-сайта. Вы должны прочитать и проработать каждый из них по очереди, прежде чем перейти к следующему.</p> + +<ol> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async">Aсинхронное управление потоками с помощью async</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer">Пример шаблона</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template">Базовые шаблоны LocalLibrary</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page">Домашняя страница</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page">Страница списка книг</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page">Страница списка экземпляров книг</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment">Форматирование даты с момента использования</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">Страница списка авторов и страница списка жанров</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page">Страница сведений о жанре</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page">Страница сведений о книге</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page">Страница информации об авторе</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge">Страница сведений об экземпляре книги и вызове</a></li> +</ol> + +<h2 id="Итог">Итог</h2> + +<p>Теперь мы создали все страницы "только для чтения " для нашего сайта: домашнюю страницу, которая отображает количество экземпляров каждой из наших моделей, а также список и подробные страницы для наших книг, экземпляров книг, авторов и жанров. По пути мы получили много фундаментальных знаний о контроллерах, управлении потоком при использовании асинхронных операций, создании представлений с помощью Pug, запросе базы данных с помощью наших моделей, как передавать информацию в шаблон из вашего представления, а также как создавать и расширять шаблоны. Те, кто выполнил вызов также узнали немного о дате обработки с помощью момента.</p> + +<p>В нашей следующей статье мы будем опираться на наши знания, создавая HTML-формы и код обработки форм, чтобы начать изменять данные, хранящиеся на сайте.</p> + +<h2 id="Смотрите_так_же">Смотрите так же</h2> + +<ul> + <li><a href="http://caolan.github.io/async/docs.html">Aссинхроный модуль</a> (Асинхронные документация)</li> + <li><a href="https://expressjs.com/en/guide/using-template-engines.html">Использование механизмов шаблонов с Express</a> (Express документация)</li> + <li><a href="https://pugjs.org/api/getting-started.html">Pug</a> (Pug документация)</li> + <li><a href="http://momentjs.com/docs/">Moment</a> (Moment документация)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html new file mode 100644 index 0000000000..13c61ea6cb --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html @@ -0,0 +1,69 @@ +--- +title: Базовый шаблон LocalLibrary +slug: Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template +--- +<p>Теперь, чтобы мы понимали как расширить шаблон с помощью Pug, давайте создадим базовый шаблон для проекта. У него будет боковая панель (sidebar)) со ссылками на страницы, которые мы надеемся создать на протяжении учебника (например, для отображения и создания книг, жанров, автор иов т. д.) и основная область контента, которую мы переопределим на каждой из отдельных страниц.</p> + +<p>Откройте файл <strong>/views/layout.pug </strong>и замените его содержимое следующим.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">doctype html +html(lang='en') + head + title= title + meta(charset='utf-8') + meta(name='viewport', content='width=device-width, initial-scale=1') + link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css') + script(src='https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js') + script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js') + link(rel='stylesheet', href='/stylesheets/style.css') + body + div(class='container-fluid') + div(class='row') + div(class='col-sm-2') + block sidebar + ul(class='sidebar-nav') + li + a(href='/catalog') Home + li + a(href='/catalog/books') All books + li + a(href='/catalog/authors') All authors + li + a(href='/catalog/genres') All genres + li + a(href='/catalog/bookinstances') All book-instances + li + hr + li + a(href='/catalog/author/create') Create new author + li + a(href='/catalog/genre/create') Create new genre + li + a(href='/catalog/book/create') Create new book + li + a(href='/catalog/bookinstance/create') Create new book instance (copy) + + div(class='col-sm-10') + block content</code></pre> + +<p>Шаблон использует (и включает) JavaScript и CSS из <a class="external external-icon" href="http://getbootstrap.com/" rel="noopener">Bootstrap</a> , что позволяет улучшить макет и представление HTML-страницы. Применение Bootstrap или другого клиентского фреймворка - быстрый способ создать привлекательную, хорошо масштабируемую страницу. Кроме того, это позволяет получить представление страницы, не вдаваясь в детали - мы можем уделить все внимание коду на стороне сервера!</p> + +<p>Макет представляется достаточно очевидным, если Вы уже прочли статью Основы шаблонов (<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data#Template_primer">Template primer</a>) выше. Обратите внимание на использование <code>block content</code> в качестве места для размещения контента отдельных страниц.</p> + +<p>Базовый шаблон также ссылается на локальный файл стилей (<strong>style.css</strong>), что обеспечивает дополнительное управление внешним видом. Откройте <strong>/public/stylesheets/style.css</strong> и замените его содержимое таким текстом:</p> + +<pre class="brush: css line-numbers language-css"><code class="language-css"><span class="selector token"><span class="class token">.sidebar-nav</span> </span><span class="punctuation token">{</span> + <span class="property token">margin-top</span><span class="punctuation token">:</span> <span class="number token">20</span>px<span class="punctuation token">;</span> + <span class="property token">padding</span><span class="punctuation token">:</span> <span class="number token">0</span><span class="punctuation token">;</span> + <span class="property token">list-style</span><span class="punctuation token">:</span> none<span class="punctuation token">;</span> +<span class="punctuation token">}</span></code></pre> + +<p>При запуске нашего сайта мы должны увидеть боковую панель! В следующих разделах мы будем использовать вышеуказанный макет для определения отдельных страниц.</p> + +<h2 id="Следующие_шаги">Следующие шаги</h2> + +<ul> + <li>Вернуться к <a href="https://developer.mozilla.org/ru/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Учебник Express часть 5: Отображение данных библиотеки</a>.</li> + <li>Перейти к следующему подразделу <a href="https://developer.mozilla.org/ru/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page">Домашняя страница</a>.</li> +</ul> diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/template_primer/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/template_primer/index.html new file mode 100644 index 0000000000..3f537db354 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/template_primer/index.html @@ -0,0 +1,149 @@ +--- +title: Основы шаблонов +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer +--- +<p>Шаблон - это текстовый файл, определяющий <em>структуру</em>, или внешний вид выходного файла, с предусмотренными позициями, в которые будут помещаться данные при отображении по шаблону (в <em>Express</em> шаблоны также называют <em>представлениями</em>).</p> + +<h2 id="Выбор_шаблонов_Express">Выбор шаблонов Express</h2> + +<p>В Express можно использовать много движков отображающих шаблонов ( <a class="external external-icon" href="https://expressjs.com/en/guide/using-template-engines.html" rel="noopener">template rendering engines</a>). В этом руководстве для шаблонов будет использован <a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a> (ранее известный как Jade) . Это наиболее популярный в Node язык шаблонов, который о себе заявляет так: чистый, чувствительный к пробелам синтаксис для написания HTML, на который сильно повлиял <a class="external external-icon" href="http://haml.info/" rel="noopener">Haml</a>.</p> + +<p>Разные языки шаблонов используют различные подходы для определения внешнего вида и разметки позиций для данных—некоторые используют HTML для определения внешнего вида, тогда как другие применяют различные форматы разметки, которые затем должы компилироваться в HTML. Pug - второго типа; он использует <em>представление</em> (<em>representation) </em> HTML, в котором первое слово в каждой строке обычно представляет элемент HTML, а отступы в следующих строках применяются, чтобы представить вложенные элементы. Результатом является определение страницы, которое транслируется непосредственно в HTML, и которое, вероятно, более краткое и легче читается.</p> + +<div class="note"> +<p><strong>Заметка:</strong> недостаток применения <em>Pug</em> - это чувствительность к отступам и пробелам (если добавить лишний пробел в "плохом" месте, можно получить невразумительный код ошибки). Однако, если ваши шаблоны уже действуют, их очень легко читать и поддерживать.</p> +</div> + +<h2 class="highlight-spanned" id="Конфигурация_шаблона"><span class="highlight-span">Конфигурация шаблона</span></h2> + +<p>Когда создавался каркас (<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">the skeleton website</a>) веб-сайта <em>LocalLibrary, </em>он был настроен на использование <a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a> . Можно было заметить, что модуль pug включен в зависимости в файле <strong>package.json</strong>, и установлен (app.set(...)) как движок представлений в файле <strong>app.js</strong>. Эта установка показывает,, что движок представлений - pug, и что <em>Express</em> должен искать шаблоны в подкаталоге <strong>/views</strong>.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// View engine setup.</span> +app<span class="punctuation token">.</span><span class="keyword token">set</span><span class="punctuation token">(</span><span class="string token">'views'</span><span class="punctuation token">,</span> path<span class="punctuation token">.</span><span class="function token">join</span><span class="punctuation token">(</span>__dirname<span class="punctuation token">,</span> <span class="string token">'views'</span><span class="punctuation token">)</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +app<span class="punctuation token">.</span><span class="keyword token">set</span><span class="punctuation token">(</span><span class="string token">'view engine'</span><span class="punctuation token">,</span> <span class="string token">'pug'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>Если посмотреть содержимое каталога <strong>views</strong>, можно увидеть файлы с расширением .pug, в которых шаблоны представлений по умолчанию. Это представление для домашней страницы (<strong>index.pug</strong>) и базовый шаблон (<strong>layout.pug</strong>), который следует заменить нашим содержимым.</p> + +<pre><code>/express-locallibrary-tutorial //the project root + /views + error.pug + <strong>index.pug</strong> + layout.pug</code> +</pre> + +<h2 class="highlight-spanned" id="Синтаксис_шаблонов"><span class="highlight-span">Синтаксис шаблонов</span></h2> + +<p>Пример файла шаблона (ниже) демонстрирует многие наиболее полезные черты Pug.</p> + +<p>Сначала отметим, что файл отражает структуру типового HTML-файла, причем первое слов в (почти) каждой строке является элементом HTML, а отступы используются, чтобы показать вложенные элементы. Так, например, элемент <code>body</code> находится внутри элемента <code>html</code>, а элементы <code>p</code> (параграфы) - внутри элемента <code>body,</code> и так далее. Невложенные элементы (т.е. индивидуальные параграфы) располагаются в отдельных строках.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">doctype html +html(lang="en") + head + title= title + script(type='text/javascript'). + body + h1= title + + p This is a line with #[em some emphasis] and #[strong strong text] markup. + p This line has un-escaped data: !{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'} and escaped data: #{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is not emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'}. + | This line follows on. + p= 'Evaluated and <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span>escaped expression<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>:' + title + + <span class="comment token"><!-- You can add HTML comments directly --></span> + // You can add single line JavaScript comments and they are generated to HTML comments + //- Introducing a single line JavaScript comment with "//-" ensures the comment isn't rendered to HTML + + p A line with a link + a(href='/catalog/authors') Some link text + | and some extra text. + + #container.col + if title + p A variable named "title" exists. + else + p A variable named "title" does not exist. + p. + Pug is a terse and simple template language with a + strong focus on performance and powerful features. + + h2 Generate a list + + ul + each val in [1, 2, 3, 4, 5] + li= val</code></pre> + +<p>Атрибуты элементов определены в скобках после соответствующих элементов. В скобках располагается список пар <em>имя атрибута=значение,</em>причем элементы списка разделяются запятой или пробелом. Например:</p> + +<ul> + <li><code>script(type='text/javascript')</code>, <code>link(rel='stylesheet', href='/stylesheets/style.css')</code></li> + <li><code>meta(name='viewport' content='width=device-width initial-scale=1')</code></li> +</ul> + +<p>Значения всех атрибутов <em>экранируются</em> (т.е. такие символы как "<code>></code>" заменяются эквивалентными кодами HTML как "<code>&gt;"</code>) , чтобы предотвратить JavaScript инъекции и межсайтовые атаки.</p> + +<p>Если после тэга стоит знак = , следующий текст рассматривается как <em>выражение</em> JavaScript. Например, шиже в первой строке, содержимое тэга <code>h1</code> будет <em>переменной </em> <code>title</code> (которая определена в файле или передана в шаблон из Express). Во второй строке содержимое параграфа - это текстовая строка, соединенная с переменной <code>title</code> . В каждом из случаев поведение по умолчанию - экранировать строки.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">h1= title +p= 'Evaluated and <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span>escaped expression<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>:' + title</code></pre> + +<p>Если после тэга знак = отсутствует, тогда содержимое рассматривается как обычный текст. Внутри текста можно вставить экранированные или неэкранированные данные, применяя синтаксис <code>#{}</code> и<code> !{}</code>, как показано ниже. В простой текст можно также вставлять "сырой" HTML.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">p This is a line with #[em some emphasis] and #[strong strong text] markup. +p This line has an un-escaped string: !{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'}, an escaped string: #{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is not emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'}, and escaped variables: #{title}.</code></pre> + +<div class="note"> +<p><strong>Совет:</strong> Почти всегда желательно экранировать данные, полученные от пользователей (при помощи синтаксиса <strong><code>#{}</code></strong> ). Данные, которым можно верить (т.е. подсчитанное количество записей, могут быть выведены без экранирования значений.</p> +</div> + +<p>Можно использовать символ конвейера ('<strong>|</strong>') в начале строки, чтобы отметить простой текст ("<a class="external external-icon" href="https://pugjs.org/language/plain-text.html" rel="noopener">plain text</a>"). Например, дополнительный текст, приведенный ниже, будет показан в той же строке, что и предыдущий, но не будет относиться к ссылке.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">a(href='http://someurl/') Link text +| Plain text</code></pre> + +<p>Pug позволяет выполнять условные операции <code>if</code>, <code>else</code> , <code>else if</code> и <code>unless</code>— пример приведен ниже:</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">if title + p Переменная с именем "title" существует +else + p Переменной с именем "title" не существует</code></pre> + +<p>Можно также выполнять циклы (итерации), применяя ситаксис <code>each-in</code> или <code>while</code> . Фрагмент кода (ниже) содержит цикл по элементам массива, чтобы показать список элементов (отметим применение 'li=' для оценки "val" как переменной). Значение итератора val может быть также передано в шаблон как переменная!</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">ul + each val in [1, 2, 3, 4, 5] + li= val</code></pre> + +<p>Синтаксис разрешает также комментарии (которые попадут в результат или нет, по вашему желанию), смеси для создания повторно используемых блоков кода, операторы выбора case, и много другого. Более подробная информация - в документации <a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">The Pug docs</a>.</p> + +<h2 class="highlight-spanned" id="Расширение_шаблонов"><span class="highlight-span">Расширение шаблонов</span></h2> + +<p>Принято иметь общую структуру для всех страниц сайта, <span class="highlight-span">включая стандартную HTML-разметку для </span>заголовка, футера, навигации и т.д. Вместо того, чтобы засталять разработчиков дублировать эти образцы на каждой странице, <em>Pug</em> позволяет объявить базовай шаблон, а затем модифицировать его, заменяя только те небольшие части, которые различны на каждой конкретной странице.</p> + +<p>Например, базовый шаблон <strong>layout.pug,</strong> созданный в каркасе проекта, имеет такой вид:</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">doctype html +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body + block content</code></pre> + +<p>Тэг <code>block</code> применен для отметки разделов контента, которые могут быть заменены в производных шаблона (если блок не переопределяется, будет использованиа его реализация в базовом классе).</p> + +<p>Умолчание для <strong>index.pug</strong> (созданный для каркаса проекта) показывает, как можно заменить базовый шаблон. Тэг <code>extends</code> идентифицирует базовый шаблон, который следует использовать, а затем мы используем <code>block <em>section_name,</em></code> чтобы отметить новый контент раздела, который мы заменяем.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">extends layout + +block content + h1= title + p Welcome to #{title}</code></pre> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template">The LocalLibrary base template</a>.</li> +</ul> diff --git a/files/ru/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html b/files/ru/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html new file mode 100644 index 0000000000..13f409ea3c --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html @@ -0,0 +1,151 @@ +--- +title: "Форма для создания\_BookInstance" +slug: Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form +--- +<p><a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms$edit#Create_BookInstance_form" rel="nofollow, noindex"><span>Edi</span></a>В этой статье показано, как определить страницу / форму для создания объектов <code>BookInstance</code>. Это очень похоже на форму, которую мы использовали для создания объектов <code>Book</code>.</p> + +<h2 class="highlight-spanned" id="Импорт_методов_проверки_и_очистки"><span class="highlight-span">Импорт методов проверки и очистки</span></h2> + +<p>Откройте <strong>/controllers/bookinstanceController.js</strong> и добавьте следующие строки вверху файла:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">const</span> <span class="punctuation token">{</span> body<span class="punctuation token">,</span>validationResult <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/check'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">const</span> <span class="punctuation token">{</span> sanitizeBody <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/filter'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="Controller—get_route"><span class="highlight-span">Controller—get route</span></h2> + +<p>At the top of the file, require the <em>Book</em> module (needed because each <code>BookInstance</code> is associated with a particular <code>Book</code>).</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">var</span> Book <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/book'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>Find the exported <code>bookinstance_create_get()</code> controller method and replace it with the following code.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display BookInstance create form on GET.</span> +exports<span class="punctuation token">.</span>bookinstance_create_get <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Book<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span><span class="string token">'title'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> books<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful, so render.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'bookinstance_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span>title<span class="punctuation token">:</span> <span class="string token">'Create BookInstance'</span><span class="punctuation token">,</span> book_list<span class="punctuation token">: </span>books<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>The controller gets a list of all books (<code>book_list</code>) and passes it to the view <code><strong>bookinstance_form.pug</strong></code> (along with the <code>title</code>)</p> + +<h2 class="highlight-spanned" id="Controller—post_route"><span class="highlight-span">Controller—post route</span></h2> + +<p>Find the exported <code>bookinstance_create_post()</code> controller method and replace it with the following code.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Handle BookInstance create on POST.</span> +exports<span class="punctuation token">.</span>bookinstance_create_post <span class="operator token">=</span> <span class="punctuation token">[</span> + + <span class="comment token">// Validate fields.</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'book'</span><span class="punctuation token">,</span> <span class="string token">'Book must be specified'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'imprint'</span><span class="punctuation token">,</span> <span class="string token">'Imprint must be specified'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'due_back'</span><span class="punctuation token">,</span> <span class="string token">'Invalid date'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">optional</span><span class="punctuation token">(</span><span class="punctuation token">{</span> checkFalsy<span class="punctuation token">:</span> <span class="keyword token">true</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isISO8601</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Sanitize fields.</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'book'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'imprint'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'status'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'due_back'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">toDate</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Process request after validation and sanitization.</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + + <span class="comment token">// Extract the validation errors from a request.</span> + <span class="keyword token">const</span> errors <span class="operator token">=</span> <span class="function token">validationResult</span><span class="punctuation token">(</span>req<span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="comment token">// Create a BookInstance object with escaped and trimmed data.</span> + <span class="keyword token">var</span> bookinstance <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">BookInstance</span><span class="punctuation token">(</span> + <span class="punctuation token">{</span> book<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>book<span class="punctuation token">,</span> + imprint<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>imprint<span class="punctuation token">,</span> + status<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>status<span class="punctuation token">,</span> + due_back<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>due_back + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span>errors<span class="punctuation token">.</span><span class="function token">isEmpty</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// There are errors. Render form again with sanitized values and error messages.</span> + Book<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span><span class="string token">'title'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> books<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful, so render.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'bookinstance_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create BookInstance'</span><span class="punctuation token">,</span> book_list<span class="punctuation token">:</span> books<span class="punctuation token">,</span> selected_book<span class="punctuation token">:</span> bookinstance<span class="punctuation token">.</span>book<span class="punctuation token">.</span>_id <span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> bookinstance<span class="punctuation token">: </span>bookinstance <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">return</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + <span class="comment token">// Data from form is valid.</span> + bookinstance<span class="punctuation token">.</span><span class="function token">save</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful - redirect to new record.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>bookinstance<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">;</span></code></pre> + +<p>The structure and behaviour of this code is the same as for creating our other objects. First we validate and sanitize the data. If the data is invalid, we then re-display the form along with the data that was originally entered by the user and a list of error messages. If the data is valid, we save the new <code>BookInstance</code> record and redirect the user to the detail page.</p> + +<h2 class="highlight-spanned" id="View"><span class="highlight-span">View</span></h2> + +<p>Create <strong>/views/bookinstance_form.pug</strong> and copy in the text below.</p> + +<pre class="line-numbers language-html"><code class="language-html">extends layout + +block content + h1=title + + form(method='POST' action='') + div.form-group + label(for='book') Book: + select#book.form-control(type='select' placeholder='Select book' name='book' required='true') + - book_list.sort(function(a, b) {let textA = a.title.toUpperCase(); let textB = b.title.toUpperCase(); return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;}); + for book in book_list + if bookinstance + option(value=book._id selected=(bookinstance.book.toString()==book._id.toString() ? 'selected' : false)) #{book.title} + else + option(value=book._id) #{book.title} + + div.form-group + label(for='imprint') Imprint: + input#imprint.form-control(type='text' placeholder='Publisher and date information' name='imprint' required='true' value=(undefined===bookinstance ? '' : bookinstance.imprint)) + div.form-group + label(for='due_back') Date when book available: + input#due_back.form-control(type='date' name='due_back' value=(undefined===bookinstance ? '' : bookinstance.due_back)) + + div.form-group + label(for='status') Status: + select#status.form-control(type='select' placeholder='Select status' name='status' required='true') + option(value='Maintenance') Maintenance + option(value='Available') Available + option(value='Loaned') Loaned + option(value='Reserved') Reserved + + button.btn.btn-primary(type='submit') Submit + + if errors + ul + for error in errors + li!= error.msg</code></pre> + +<p>The view structure and behaviour is almost the same as for the <strong>book_form.pug</strong> template, so we won't go over it again.</p> + +<div class="note"> +<p><strong>Note:</strong> The above template hard-codes the <em>Status</em> values (Maintenance, Available, etc.) and does not "remember" the user's entered values. Should you so wish, consider reimplementing the list, passing in option data from the controller and setting the selected value when the form is re-displayed.</p> +</div> + +<h2 class="highlight-spanned" id="Как_это_выглядит"><span class="highlight-span">Как это выглядит?</span></h2> + +<p>Запустите приложение и откройте в браузере <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. Затем выберите ссылку <em>Create new book instance (copy)</em>. Если все настроено правильно, ваш сайт должен выглядеть примерно так, как показано на скриншоте. После того, как вы отправите валидный <code>BookInstance</code>, он должен быть сохранен, и вы попадете на страницу сведений.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14490/LocalLibary_Express_BookInstance_Create_Empty.png" style="display: block; height: 554px; margin: 0px auto; width: 1000px;"></p> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a>.</li> + <li>Proceed to the next subarticle of part 6: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form">Delete Author form</a>.</li> +</ul> diff --git a/files/ru/learn/server-side/express_nodejs/forms/create_genre_form/index.html b/files/ru/learn/server-side/express_nodejs/forms/create_genre_form/index.html new file mode 100644 index 0000000000..c9407372c4 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/create_genre_form/index.html @@ -0,0 +1,199 @@ +--- +title: Create genre form +slug: Learn/Server-side/Express_Nodejs/forms/Create_genre_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_genre_form +--- +<p>В этом подразделе рассказано, как сделать страницу для создания жанра (<code>Genre</code>). Хорошая точка для старта, у жанра всего одно поле - <code>name</code>. Как и для любой другой страницы, здесь потребуется внести изменения в соответствующие маршрут, контроллер и шаблон (route, controller, view).</p> + +<h2 id="Import_validation_and_sanitisation_methods">Import validation and sanitisation methods</h2> + +<p>To use the <em>express-validator </em>in our controllers we have to <em>require</em> the functions we want to use from the <strong>'express-validator/check</strong>' and <strong>'express-validator/filter</strong>' modules.</p> + +<p>Open <strong>/controllers/genreController.js</strong>, and add the following line at the top of the file:</p> + +<pre class="brush: js">const validator = require('express-validator'); +</pre> + +<h2 id="Controller—get_route">Controller—get route</h2> + +<p>Find the exported <code>genre_create_get()</code> controller method and replace it with the following code. This simply renders the <strong>genre_form.pug</strong> view, passing a title variable.</p> + +<pre class="brush: js">// Display Genre create form on GET. +exports.genre_create_get = function(req, res, next) { + res.render('genre_form', { title: 'Create Genre' }); +};</pre> + +<h2 id="Controller—post_route">Controller—post route</h2> + +<p>Find the exported <code>genre_create_post()</code> controller method and replace it with the following code.</p> + +<pre class="brush: js">// Handle Genre create on POST. +exports.genre_create_post = [ + + // Validate that the name field is not empty. + validator.body('name', 'Genre name required').trim().isLength({ min: 1 }), + + // Sanitize (escape) the name field. + validator.sanitizeBody('name').escape(), + + // Process request after validation and sanitization. + (req, res, next) => { + + // Extract the validation errors from a request. + const errors = validator.validationResult(req); + + // Create a genre object with escaped and trimmed data. + var genre = new Genre( + { name: req.body.name } + ); + + + if (!errors.isEmpty()) { + // There are errors. Render the form again with sanitized values/error messages. + res.render('genre_form', { title: 'Create Genre', genre: genre, errors: errors.array()}); + return; + } + else { + // Data from form is valid. + // Check if Genre with same name already exists. + Genre.findOne({ 'name': req.body.name }) + .exec( function(err, found_genre) { + if (err) { return next(err); } + + if (found_genre) { + // Genre exists, redirect to its detail page. + res.redirect(found_genre.url); + } + else { + + genre.save(function (err) { + if (err) { return next(err); } + // Genre saved. Redirect to genre detail page. + res.redirect(genre.url); + }); + + } + + }); + } + } +];</pre> + +<p>The first thing to note is that instead of being a single middleware function (with arguments <code>(req, res, next)</code>) the controller specifies an <em>array</em> of middleware functions. The array is passed to the router function and each method is called in order.</p> + +<div class="note"> +<p><strong>Note:</strong> This approach is needed, because the sanitisers/validators are middleware functions.</p> +</div> + +<p>The first method in the array defines a validator (<code>validator.body()</code>) from the <code>validator</code> module to check that the <em>name</em> field is not empty (calling <code>trim()</code> to remove any trailing/leading whitespace before performing the validation). The second method in the array (<code>validator.sanitizeBody()</code>) creates a sanitizer to <code>escape()</code> any dangerous HTML characters in the <em>name</em> field.</p> + +<pre class="brush: js">// Validate that the name field is not empty. +validator<code>.</code>body('name', 'Genre name required').isLength({ min: 1 }).trim(), + +// Sanitize (escape) the name field. +validator.sanitizeBody('name').escape()</pre> + +<p>After specifying the validators and sanitizers we create a middleware function to extract any validation errors. We use <code>isEmpty()</code> to check whether there are any errors in the validation result. If there are then we render the form again, passing in our sanitised genre object and the array of error messages (<code>errors.array()</code>).</p> + +<pre class="brush: js">// Process request after validation and sanitization. +(req, res, next) => { + + // Extract the validation errors from a request. + const errors = validator.validationResult(req); + + // Create a genre object with escaped and trimmed data. + var genre = new Genre( + { name: req.body.name } + ); + + if (!errors.isEmpty()) { + // There are errors. Render the form again with sanitized values/error messages. + res.render('genre_form', { title: 'Create Genre', genre: genre, errors: errors.array()}); + return; + } + else { + // Data from form is valid. + ... <save the result/> ... + } +};</pre> + +<p>If the genre name data is valid then we check if a <code>Genre</code> with the same name already exists (as we don't want to create duplicates). If it does, we redirect to the existing genre's detail page. If not, we save the new <code>Genre</code> and redirect to its detail page.</p> + +<pre class="brush: js">// Check if Genre with same name already exists. +Genre.findOne({ 'name': req.body.name }) + .exec( function(err, found_genre) { + if (err) { return next(err); } + if (found_genre) { + // Genre exists, redirect to its detail page. + res.redirect(found_genre.url); + } + else { + genre.save(function (err) { + if (err) { return next(err); } + // Genre saved. Redirect to genre detail page. + res.redirect(genre.url); + }); + } +});</pre> + +<p>This same pattern is used in all our post controllers: we run validators, then sanitisers, then check for errors and either re-render the form with error information or save the data. </p> + +<h2 id="View">View</h2> + +<p>The same view is rendered in both the <code>GET</code> and <code>POST</code> controllers/routes when we create a new <code>Genre</code> (and later on it is also used when we <em>update</em> a <code>Genre</code>). In the <code>GET</code> case the form is empty, and we just pass a title variable. In the <code>POST</code> case the user has previously entered invalid data—in the <code>genre</code> variable we pass back a sanitized version of the entered data and in the <code>errors</code> variable we pass back an array of error messages.</p> + +<pre class="brush: js">res.render('genre_form', { title: 'Create Genre'}); +res.render('genre_form', { title: 'Create Genre', genre: genre, errors: errors.array()});</pre> + +<p>Create <strong>/views/genre_form.pug</strong> and copy in the text below.</p> + +<pre class="brush: html">extends layout + +block content + h1 #{title} + + form(method='POST' action='') + div.form-group + label(for='name') Genre: + input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name)) + button.btn.btn-primary(type='submit') Submit + + if errors + ul + for error in errors + li!= error.msg</pre> + +<p>Much of this template will be familiar from our previous tutorials. First, we extend the <strong>layout.pug</strong> base template and override the <code>block</code> named '<strong>content</strong>'. We then have a heading with the <code>title</code> we passed in from the controller (via the <code>render()</code> method).</p> + +<p>Next, we have the pug code for our HTML form that uses the <code>POST</code> <code>method</code> to send the data to the server, and because the <code>action</code> is an empty string, will send the data to the same URL as the page.</p> + +<p>The form defines a single required field of type "text" called "name". The default <em>value</em> of the field depends on whether the <code>genre</code> variable is defined. If called from the <code>GET</code> route it will be empty as this is a new form. If called from a <code>POST</code> route it will contain the (invalid) value originally entered by the user.</p> + +<p>The last part of the page is the error code. This simply prints a list of errors, if the error variable has been defined (in other words, this section will not appear when the template is rendered on the <code>GET</code> route).</p> + +<div class="note"> +<p><strong>Note:</strong> This is just one way to render the errors. You can also get the names of the affected fields from the error variable, and use these to control where the error messages are rendered, whether to apply custom CSS, etc.</p> +</div> + +<h2 id="What_does_it_look_like">What does it look like?</h2> + +<p>Run the application, open your browser to <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>, then select the <em>Create new genre </em>link. If everything is set up correctly, your site should look something like the following screenshot. After you enter a value, it should be saved and you'll be taken to the genre detail page.</p> + +<p><img alt="Genre Create Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14476/LocalLibary_Express_Genre_Create_Empty.png" style="border-style: solid; border-width: 1px; display: block; height: 301px; margin: 0px auto; width: 800px;"></p> + +<p>The only error we validate against server-side is that the genre field must not be empty. The screenshot below shows what the error list would look like if you didn't supply a genre (highlighted in red).</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14480/LocalLibary_Express_Genre_Create_Error.png" style="border-style: solid; border-width: 1px; display: block; height: 249px; margin: 0px auto; width: 400px;"></p> + +<div class="note"> +<p><strong>Note:</strong> Our validation uses <code>trim()</code> to ensure that whitespace is not accepted as a genre name. We can also validate that the field is not empty on the client side by adding the value <code>required='true'</code> to the field definition in the form:</p> + +<pre class="brush: js">input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name), <strong>required='true'</strong> )</pre> +</div> + +<h2 id="Next_steps">Next steps</h2> + +<ol> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms.</a></li> + <li>Proceed to the next sub article of part 6: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_author_form">Create Author form</a>.</li> +</ol> diff --git a/files/ru/learn/server-side/express_nodejs/forms/delete_author_form/index.html b/files/ru/learn/server-side/express_nodejs/forms/delete_author_form/index.html new file mode 100644 index 0000000000..0e0fa6cdf3 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/delete_author_form/index.html @@ -0,0 +1,165 @@ +--- +title: Delete Author form +slug: Learn/Server-side/Express_Nodejs/forms/Delete_author_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Delete_author_form +--- +<p>В этой статье показано, как определить страницу для удаления объектов <code>Author</code>.</p> + +<p>Как описано в разделе <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms#form_design">form design</a>, наша стратегия будет заключаться в том, чтобы разрешить удаление только объектов, на которые не ссылаются другие объекты(в этом случае это означает, что мы не позволим <code>Author</code> быть удаленным, если на него ссылается <code>Book</code>). С точки зрения реализации это означает, что форма должна подтвердить, что нет никаких связанных книг, прежде чем автор будет удален. Если есть связанные книги, то они должны отображаться и быть удалены до того, как будеет удален объект <code>Author</code>.</p> + +<h2 class="highlight-spanned" id="Controller—get_route">Controller—get route</h2> + +<p>Откройте <strong>/controllers/authorController.js</strong>. Найдите экспротируемый метод контроллера <code>author_delete_get()</code> и замените его на слдеующий код.</p> + +<pre><code class="language-js">// Отображать форму для удаления автора GET +exports.author_delete_get = function(req, res, next) { + + async.parallel({ + author: function(callback) { + Author.findById(req.params.id).exec(callback) + }, + authors_books: function(callback) { + Book.find({ 'author': req.params.id }).exec(callback) + }, + }, function(err, results) { + if (err) { return next(err); } + if (results.author==null) { // No results. + res.redirect('/catalog/authors'); + } + // </code>Удачно, значит рендерим.<code class="language-js"> + res.render('author_delete', { title: 'Delete Author', author: results.author, author_books: results.authors_books } ); + }); + +};</code></pre> + +<p>TКонтроллер получает id экземпляра <code>Author</code> для удаления из параметра URL (<code>req.params.id</code>). Он использует метод <code>async.parallel()</code> , чтобы получить запись автра и паралельнно вс связанные книги. WКогда оба пораметра авершины, он рендерит страницу <code><strong>author_delete</strong></code><strong>.pug</strong>, передает значения для <code>title</code>, <code>author</code>, и <code>author_books</code>.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Если <code>findById()</code><strong> </strong>не возвращает результатов, то автор отсутствует в базе данных. В этом случае удалять нечего, поэтому сразу выводим список всех авторов.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">}, function(err, results) { + if (err) { return next(err); } + if (results.author==null) { // No results. + res.redirect('/catalog/authors') + }</code></pre> +</div> + +<h2 class="highlight-spanned" id="Controller—post_route">Controller—post route</h2> + +<p>Найдите экспортируемый метод контроллера <code>author_delete_post()</code> и замените его на следующий код.</p> + +<pre><code class="language-js">// Обработчик удаления автора POST. +exports.author_delete_post = function(req, res, next) { + + async.parallel({ + author: function(callback) { + Author.findById(req.body.authorid).exec(callback) + }, + authors_books: function(callback) { + Book.find({ 'author': req.body.authorid }).exec(callback) + }, + }, function(err, results) { + if (err) { return next(err); } + // Success + if (results.authors_books.length > 0) { + // </code>Автор книги. Визуализация выполняется так же, как и для GET route.<code class="language-js"> + res.render('author_delete', { title: 'Delete Author', author: results.author, author_books: results.authors_books } ); + return; + } + else { + </code>//У автора нет никаких книг. Удалить объект и перенаправить в список авторов.<code class="language-js"> + Author.findByIdAndRemove(req.body.authorid, function deleteAuthor(err) { + if (err) { return next(err); } + // </code>Успех-перейти к списку авторов<code class="language-js"> + res.redirect('/catalog/authors') + }) + } + }); +};</code></pre> + +<p>Сначала мы проверяем, что был предоставлен id (он отправляется через параметры тела формы, а не через версию в URL). Затем мы получаем автора и связанные с ним книги так же, как и для маршрута <code>GET</code>. Если книг нет, то удаляем объект автора и перенаправляем в список всех авторов. Если есть еще книги, то мы просто перерисовываем форму, передавая автора и список книг, которые нужно удалить.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Мы можем проверить, возвращает ли вызов <code>findbyid ()</code> какой-либо результат, и если нет, немедленно отобразить список всех авторов.Для краткости мы оставили код как есть выше (он все равно вернет список авторов, если id не будет найден, но это произойдет после <code>findByIdAndRemove()</code>).</p> +</div> + +<h2 class="highlight-spanned" id="View">View</h2> + +<p>Создайте <strong>/views/author_delete.pug</strong> и скопируйет текст ниже.</p> + +<pre class="line-numbers language-html"><code class="language-html">extends layout + +block content + h1 #{title}: #{author.name} + p= author.lifespan + + if author_books.length + + p #[strong Delete the following books before attempting to delete this author.] + + div(style='margin-left:20px;margin-top:20px') + + h4 Books + + dl + each book in author_books + dt + a(href=book.url) #{book.title} + dd #{book.summary} + + else + p Do you really want to delete this Author? + + form(method='POST' action='') + div.form-group + input#authorid.form-control(type='hidden',name='authorid', required='true', value=author._id ) + + button.btn.btn-primary(type='submit') Delete</code></pre> + +<p>Представление расширяет шаблон макета, переопределяя блок с именем <code>content</code>. Вверху отображаются сведения об авторе. Затем он включает условный оператор, основанный на количестве <code><strong>author_books</strong></code> (пункты <code>if</code> и <code>else</code> ).</p> + +<ul> + <li>Если есть книги, связанные с автором, то на странице перечислены книги и говорится, что они должны быть удалены, прежде чем этот <code>Author</code> может быть удален.</li> + <li>Если книг нет, на странице отображается запрос на подтверждение. Если нажать кнопку <strong>Delete</strong>, то id автора будет отправлен на сервер в <code>POST</code>-запросе, и запись этого автора будет удалена.</li> +</ul> + +<h2 class="highlight-spanned" id="Добавление_элемента_управления_delete">Добавление элемента управления delete</h2> + +<p>Затем мы добавим элемент управления <code>Delete</code> в представление сведений об авторе (страница сведений-хорошее место для удаления записи).</p> + +<div class="note"> +<p><strong>Note:</strong> В полном объеме контроль будет доступен только авторизованным пользователям. Однако на данный момент у нас нет системы авторизации!</p> +</div> + +<p>Откройте <strong>author_detail.pug</strong> и добавьте следующие строки внизу.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">hr +p + a(href=author.url+'/delete') Delete author</code></pre> + +<p>Теперь элемент управления должен отображаться в виде ссылки, как показано ниже на странице сведений об авторе.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14492/LocalLibary_Express_Author_Detail_Delete.png" style="border-style: solid; border-width: 1px; display: block; height: 202px; margin: 0px auto; width: 500px;"></p> + +<h2 class="highlight-spanned" id="Как_это_выглядит">Как это выглядит?</h2> + +<p>Запустите приложение и откройте в вашем браузере <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. Затем раздел <em>All authors </em>, а затем укажите конктретного пользователя. Наконец, выберите ссылку <em>Delete author</em>.</p> + +<p>Если у автора нет книг, вам будет представлена такая страница. После нажатия клавиши delete сервер удалит автора и перенаправит в список авторов</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14494/LocalLibary_Express_Author_Delete_NoBooks.png" style="border-style: solid; border-width: 1px; display: block; height: 342px; margin: 0px auto; width: 600px;"></p> + +<p>Если у автора есть книги, то вам будет представлен следующий вид. Затем вы можете удалить книги из их подробных страниц (как только этот код будет реализован!).</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14496/LocalLibary_Express_Author_Delete_WithBooks.png" style="border-style: solid; border-width: 1px; display: block; height: 327px; margin: 0px auto; width: 500px;"></p> + +<div class="note"> +<p><strong>Note:</strong> Другие страницы для удаления объектов могут быть реализованы примерно таким же образом. Мы оставили это как задачи.</p> +</div> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a>.</li> + <li>Proceed to the final subarticle of part 6: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form">Update Book form</a>.</li> +</ul> diff --git a/files/ru/learn/server-side/express_nodejs/forms/index.html b/files/ru/learn/server-side/express_nodejs/forms/index.html new file mode 100644 index 0000000000..f877a6015c --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/index.html @@ -0,0 +1,269 @@ +--- +title: 'Учебник Express часть 6: Работа с формами' +slug: Learn/Server-side/Express_Nodejs/forms +tags: + - Начинающим + - Сервер + - Формы +translation_of: Learn/Server-side/Express_Nodejs/forms +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">В этой главе мы покажем Вам как работать с HTML формами в Express, используя Pug, и в частности как написать формы для создания, обновления и удаления документов из базы данных.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Предварительные знания:</th> + <td>Завершите изучение предыдущих тем учебника, включая <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Учебник Express Часть 5: Отображение данных библиотеки</a></td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понять, как писать формы для получения данных от пользователей и обновлять базу данных с этими данными.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p><a href="/en-US/docs/Web/Guide/HTML/Forms">HTML форма</a> - это группа из одного или нескольких полей / виджетов на веб-странице, которая может использоваться для сбора информации от пользователей для отправки на сервер. Формы представляют собой гибкий механизм для сбора данных, вводимых пользователем, поскольку существуют подходящие входные данные форм, доступные для ввода различных типов данных-текстовые поля, флажки, переключатели, средства выбора даты и т. д. Формы также являются относительно безопасным способом обмена данными с сервером, поскольку они позволяют отправлять данные в запросах <code>POST</code> с защитой от подделки межсайтовых запросов.</p> + +<p>Работа с формами может быть сложной! Разработчику нужно написать HTML код для форм, валидацию и правильно анализировать введенные данные на сервере (и, возможно, также в браузере), отобразить форму с сообщениями об ошибках, чтобы сообщить пользователям о любых недопустимых полях, обработать данные, когда они были успешно отправлены, и, наконец, каким-то образом ответить пользователю о том, что результат успешен.</p> + +<p>В этом уроке мы покажем вам, как вышеуказанные операции могут быть выполнены в <em>Express</em>. По пути мы расширим веб-сайт <em>LocalLibrary</em>, чтобы пользователи могли создавать, редактировать и удалять элементы из библиотеки.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Мы не рассматривали, как ограничить определенные маршруты аутентифицированными или авторизованными пользователями, поэтому на данный момент любой пользователь сможет вносить изменения в базу данных.</p> +</div> + +<h3 id="HTML_Forms">HTML Forms</h3> + +<p>Первый краткий обзор <a href="/en-US/docs/Web/Guide/HTML/Forms">HTML Forms</a>. Рассмотрим простую HTML-форму с одним текстовым полем для ввода имени некоторой "команды" и связанной с ней меткой:</p> + +<p><img alt="Simple name field example in HTML form" src="https://mdn.mozillademos.org/files/14117/form_example_name_field.png" style="border-style: solid; border-width: 1px; display: block; height: 44px; margin: 0px auto; width: 399px;"></p> + +<p>Определенные в HTML формы собираются внутри тэга <code><form>...</form></code>, содержащего хтя ы один элемент <code>input</code> с <code>type="submit"</code>.</p> + +<pre class="brush: html notranslate"><form action="/team_name_url/" method="post"> + <label for="team_name">Enter name: </label> + <input id="team_name" type="text" name="name_field" value="Default name for team."> + <input type="submit" value="OK"> +</form></pre> + +<p>Хотя здесь мы включили только одно (текстовое) поле для ввода имени команды, форма может содержать любое количество других элементов ввода и связанных с ними меток. Атрибут <code>type</code> определяет какой из <a href="/ru/docs/Learn/HTML/Forms/Стандартные_виджеты_форм">виджетов</a> будет выбран для отображения поля. Атрибуты <code>name</code> и <code>id</code> идентифицируют поле в JavaScript/CSS/HTML, а <code>value</code> определяет его первоначальное значение. Связанная с полем метка, задается с помощью тега <code style="font-style: normal; font-weight: normal;">label</code> (располгается строкой выше и содержит в себе подпись "Enter name"). Связь метки и поля ввода устанавливается при помощи атрибута <code>for</code>, в котором указывается значение идентификатора поля (<code>input</code> <code>id</code>).</p> + +<p>Input <code>submit </code>будет отображаться в виде кнопки (по умолчанию) - он может быть нажат пользователем, чтобы загрузить данные, содержащиеся в других входных элементов на сервер (в данном случае, только team_name). Атрибуты формы определяют метод HTTP, используемый для отправки данных, и назначение данных на сервере (action):</p> + +<ul> + <li><code>action</code>: ресурс/URL-адрес, по которому данные должны отправляться на обработку при отправке формы. Если это не установлено (или установлено в пустую строку), то форма будет отправлена назад к URL текущей страницы.</li> + <li><code>method</code>: Метод HTTP, используемый для отправки данных: <code>POST</code> or <code>GET</code>. + <ul> + <li>Метод <code>POST</code> должен всегда использоваться, если данные собираются привести к изменению базы данных сервера, потому что это может быть сделано более устойчивым к атакам запроса подделки межсайтового.</li> + <li>Метод <code>GET</code> следует использовать только для форм, которые не изменяют пользовательские данные (например, форма поиска). Рекомендуется, когда вы хотите, чтобы иметь возможность делать закладки или поделиться URL.</li> + </ul> + </li> +</ul> + +<h3 id="Процесс_обработки_формы">Процесс обработки формы</h3> + +<p>Обработка форм использует все те же методы, которые мы изучили для отображения информации о наших моделях: маршрут отправляет запрос в функцию контроллера, которая выполняет все необходимые действия с базой данных, включая чтение данных из моделей, а затем генерирует и возвращает HTML-страницу. Что усложняет ситуацию, так это то, что сервер также должен иметь возможность обрабатывать данные, предоставленные пользователем, и повторно отображать форму с информацией об ошибках, если есть какие-либо проблемы.</p> + +<p>Блок-схема процесса обработки запросов формы показана ниже, начиная с запроса страницы, содержащей форму (показана зеленым цветом):</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14478/Web%20server%20form%20handling.png" style="height: 649px; width: 800px;"></p> + +<p>Как показано на диаграмме выше, основные действия, которые необходимо выполнить коду обработки форм:</p> + +<ol> + <li>Отображение формы по умолчанию при первом запросе пользователем. + <ul> + <li>Форма может содержать пустые поля (например, если вы создаете новую запись), или она может быть предварительно заполнена начальными значениями (например, если вы изменяете запись или имеете полезные начальные значения по умолчанию).</li> + </ul> + </li> + <li>Получение данных, отправленных пользователем, обычно в запросе HTTP <code>POST</code>.</li> + <li>Валидация и очистка данных.</li> + <li>Если какие-либо данные недопустимы, повторно отобразите форму—на этот раз с заполненными пользователем значениями и сообщениями об ошибках для проблемных полей</li> + <li>Если все данные верны, выполнить требуемые действия (например, сохранить данные в базе данных, отправьте уведомление по электронной почте, возвращающие результат поиска, загрузить файл и т. д.)</li> + <li>После завершения всех действий перенаправьте пользователя на другую страницу.</li> +</ol> + +<p>Часто код обработки формы реализуется с помощью <code>GET </code>route для начального отображения формы и <code>POST </code>route к тому же пути для обработки проверки и обработки данных формы. Это подход, который будет использоваться в этом уроке!</p> + +<p>Сам Express не предоставляет какой-либо конкретной поддержки для операций обработки форм, но он может использовать промежуточное программное обеспечение для обработки <code>POST</code> и <code>GET</code> параметров из формы, а также для проверки/очистки их значений.</p> + +<h3 id="Валидация_и_обработка">Валидация и обработка</h3> + +<p>Перед сохранением данных формы их необходимо проверить и очистить:</p> + +<ul> + <li>Проверка проверяет, что введенные значения являются подходящими для каждого поля (расположены в правильном диапазоне, формат и т. д.) и что значения были предоставлены для всех обязательных полей.</li> + <li>Очистка удаляет / заменяет символы в данных, которые потенциально могут использоваться для отправки вредоносного содержимого на сервер.</li> +</ul> + +<p>В этом уроке мы будем использовать популярный модуль <a href="https://www.npmjs.com/package/express-validator">express-validator</a> для проверки и очистки данных формы.</p> + +<h4 id="Установка">Установка</h4> + +<p>Установите модуль, выполнив следующую команду в корне проекта</p> + +<pre class="brush: bash notranslate">npm install express-validator +</pre> + +<h4 id="Использование_express-validator">Использование express-validator</h4> + +<div class="note"> +<p><strong>Note:</strong> <a href="https://github.com/ctavan/express-validator#express-validator">express-validator</a> руководство на Github предоставляет хороший обзор API. Мы рекомендуем вам прочитать это, чтобы получить представление о всех его возможностях (включая создание пользовательских валидаторов). Ниже мы рассмотрим только подмножество, которое полезно для <em>LocalLibrary</em>.</p> +</div> + +<p>Для того, чтобы использовать валидатор в наших контроллерах, мы должны требовать функции, которые мы хотим использовать из модулей <strong>'express-validator/check</strong>' и <strong>'express-validator/filter</strong>', как показано ниже:</p> + +<pre class="brush: js notranslate">const { body,validationResult } = require('express-validator/check'); +const { sanitizeBody } = require('express-validator/filter'); +</pre> + +<p>Есть много доступных функций, позволяющих проверять и очищать данные из параметров запроса, тела, заголовков, файлов cookie и т. д., или все сразу. Для этого урока мы будем использовать <code>body</code>, <code>sanitizeBody</code>, and <code>validationResult</code> (как "требуется" выше).</p> + +<p>Функции определяются следующим образом:</p> + +<ul> + <li><code><a href="https://github.com/ctavan/express-validator#bodyfields-message">body(fields[, message])</a></code>: Задает набор полей в теле запроса (параметр <code>POST</code>) для проверки, а также необязательное сообщение об ошибке, которое может отображаться в случае сбоя тестов. Критерии проверки последовательно связаны с методом <code>body()</code>. Например, первая проверка ниже проверяет, что поле" имя "не пустое и задает сообщение об ошибке" пустое имя", если оно не пустое. Второй тест проверяет, что поле age является допустимой датой, и с помощью optional() указывает, что пустые и пустые строки не пройдут проверку. + + <pre class="brush: js notranslate">body('name', 'Empty name').isLength({ min: 1 }), +body('age', 'Invalid age').optional({ checkFalsy: true }).isISO8601(), +</pre> + Можно также последовательно подключить различные валидаторы и добавить сообщения, отображаемые при выполнении предыдущих валидаторов.</li> + <li> + <pre class="brush: js notranslate">body('name').isLength({ min: 1 }).trim().withMessage('Name empty.') + .isAlpha().withMessage('Name must be alphabet letters.'), +</pre> + + <div class="note"> + <p><strong>Note:</strong> Вы также можете добавить встроенные средства очистки, такие как <code>trim()</code>, как показано выше. Однако средства очитски, применяемые здесь, применяются только к шагу проверки. Если требуется очистить конечный результат, необходимо использовать отдельный метод очистки, как показано ниже.</p> + </div> + </li> + <li><code><a href="https://github.com/ctavan/express-validator#sanitizebodyfields">sanitizeBody(fields)</a></code>: Задает поле тела для очистки. затем операции очистки последовательно соединяются с этим методом. Например, операция очистки <code>escape()</code>, описанная ниже, удаляет символы HTML из переменной name, которые могут использоваться в атаках сценариев между сайтами JavaScript. + <pre class="brush: js notranslate">sanitizeBody('name').trim().escape(), +sanitizeBody('date').toDate(),</pre> + </li> + <li><code><a href="https://github.com/ctavan/express-validator#validationresultreq">validationResult(req)</a></code>: Запускает проверку, делая ошибки доступными в виде объекта результата проверки. Это вызывается в отдельном обратном вызове, как показано ниже: + <pre class="brush: js notranslate">(req, res, next) => { + // Extract the validation errors from a request. + const errors = validationResult(req); + + if (!errors.isEmpty()) { + // There are errors. Render form again with sanitized values/errors messages. + // Error messages can be returned in an array using `errors.array()`. + } + else { + // Data from form is valid. + } +}</pre> + Мы используем метод <code>isEmpty()</code> результата проверки, чтобы проверить, были ли ошибки, и его метод array (), чтобы получить набор сообщений об ошибках. Дополнительные сведения см. в разделе API результатов проверки.</li> +</ul> + +<p>Цепочки проверки и очистки являются промежуточными запросами, которые должны быть переданы обработчику Express -маршрута (мы делаем это косвенно, через контроллер). При запуске промежуточного по каждый валидатор / средства очистки выполняется в указанном порядке..</p> + +<p>Мы рассмотрим некоторые реальные примеры, когда мы реализуем <em>LocalLibrary </em>формы ниже.</p> + +<h3 id="Дизайн_формы">Дизайн формы</h3> + +<p>Многие модели в библиотеке связаны / зависимы—например, книга требует автора, а также может иметь один или несколько жанров. Это поднимает вопрос о том, как мы должны обрабатывать случай, когда пользователь хочет:</p> + +<ul> + <li>Создайте объект, если связанные с ним объекты еще не существуют (например, книга, в которой не определен объект автора).</li> + <li>Удаление объекта, который все еще используется другим объектом (например, удаление жанра, который все еще используется книгой).</li> +</ul> + +<p>Для этого проекта мы упростили реализацию, объявив, что форма может быть только:</p> + +<ul> + <li>Создайте объект, используя объекты, которые уже существуют (таким образом, пользователи должны будут создать все необходимые экземпляры автора и жанра, прежде чем пытаться создать любые объекты книги).</li> + <li>Удалите объект, если на него не ссылаются другие объекты (например, Вы не сможете удалить книгу, пока не будут удалены все связанные объекты BookInstance).</li> +</ul> + +<div class="note"> +<p><strong>Note:</strong> Более" надежная " реализация может позволить создавать зависимые объекты при создании нового объекта и удалять любой объект в любое время (например, путем удаления зависимых объектов или путем удаления ссылок на удаленный объект из базы данных).</p> +</div> + +<h3 id="Маршруты">Маршруты</h3> + +<p>Чтобы реализовать наш код обработки форм, нам понадобятся два маршрута с одинаковым шаблоном URL. Первый (<code>GET</code>) маршрут используется для отображения новой пустой формы создания объекта. Второй маршрут (<code>POST</code>) используется для проверки введенных пользователем данных, а затем сохранения информации и перенаправления на страницу сведений (если данные верны) или повторного отображения формы с ошибками (если данные неверны).</p> + +<p>Мы уже создали маршруты для всех страниц создания нашей модели в <strong>/routes/catalog.js</strong> (in a <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes">previous tutorial</a>). Например, жанровые маршруты показаны ниже:</p> + +<pre class="brush: js notranslate">// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id). +router.get('/genre/create', genre_controller.genre_create_get); + +// POST request for creating Genre. +router.post('/genre/create', genre_controller.genre_create_post); +</pre> + +<h2 id="Express_формы_—_подразделы">Express формы — подразделы</h2> + +<p>В следующих подразделах мы добавим необходимые формы для нашего веб-сайта. Вы должны прочитать и проработать каждый из них по очереди, прежде чем перейти к следующему.</p> + +<ol> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_genre_form">Форма для создания Genre</a> — Определение нашей страницы для создания объектов <code>Genre</code>.</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_author_form">Форма для cоздания Author</a> — Определение страницы для создания объектов <code>Author</code>.</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_book_form">Форма для создания Book</a> — Определение страницы/формы для создания объектов <code>Book</code>.</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form">Форма для создания BookInstance</a> — Определение страницы/формы для создания объектов <code>BookInstance</code>.</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form">Форма для удаления Author</a> — Определение страницы для удаления объектов <code>Author</code>.</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form">Форма для обновления Book</a> — Определение страницы для обновления объектов <code>Book</code>.</li> +</ol> + +<h2 id="Challenge_yourself">Challenge yourself</h2> + +<p>Implement the delete pages for the <code>Book</code>, <code>BookInstance</code>, and <code>Genre</code> models, linking them from the associated detail pages in the same way as our <em>Author delete </em>page. The pages should follow the same design approach:</p> + +<ul> + <li>If there are references to the object from other objects, then these other objects should be displayed along with a note that this record can't be deleted until the listed objects have been deleted.</li> + <li>If there are no other references to the object then the view should prompt to delete it. If the user presses the <strong>Delete</strong> button, the record should then be deleted.</li> +</ul> + +<p>A few tips:</p> + +<ul> + <li>Deleting a <code>Genre</code> is just like deleting an <code>Author</code> as both objects are dependencies of <code>Book</code> (so in both cases you can delete the object only when the associated books are deleted).</li> + <li>Deleting a <code>Book</code> is also similar, but you need to check that there are no associated <code>BookInstances</code>.</li> + <li>Deleting a <code>BookInstance</code> is the easiest of all, because there are no dependent objects. In this case you can just find the associated record and delete it.</li> +</ul> + +<p>Implement the update pages for the <code>BookInstance</code>, <code>Author</code>, and <code>Genre</code> models, linking them from the associated detail pages in the same way as our <em>Book update </em>page.</p> + +<p>A few tips:</p> + +<ul> + <li>The <em>Book update page</em> we just implemented is the hardest! The same patterns can be used for the update pages for the other objects.</li> + <li>The <code>Author</code> date of death and date of birth fields, and the <code>BookInstance</code> due_date field are the wrong format to input into the date input field on the form (it requires data in form "YYYY-MM-DD"). The easiest way to get around this is to define a new virtual property for the dates that formats the dates appropriately, and then use this field in the associated view templates.</li> + <li>If you get stuck, there are examples of the update pages in <a href="https://github.com/mdn/express-locallibrary-tutorial">the example here</a>.</li> +</ul> + +<h2 id="Summary">Summary</h2> + +<p><em>Express</em>, node, and third party packages on NPM provide everything you need to add forms to your website. In this article you've learned how to create forms using <em>Pug</em>, validate and sanitize input using <em>express-validator</em>, and add, delete, and modify records in the database.</p> + +<p>You should now understand how to add basic forms and form-handling code to your own node websites!</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://www.npmjs.com/package/express-validator">express-validator</a> (npm docs).</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> diff --git a/files/ru/learn/server-side/express_nodejs/forms/update_book_form/index.html b/files/ru/learn/server-side/express_nodejs/forms/update_book_form/index.html new file mode 100644 index 0000000000..16172605d1 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/update_book_form/index.html @@ -0,0 +1,189 @@ +--- +title: Update Book form +slug: Learn/Server-side/Express_Nodejs/forms/Update_Book_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Update_Book_form +--- +<p><a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms$edit#Update_Book_form" rel="nofollow, noindex"><span>Edit</span></a>Наконец, в разделе показано, как определить страницу для обновления объектов <code>Book</code>. Обработка форм при обновлении книги аналогична обработке форм при создании книги, за исключением того, что необходимо заполнить форму в маршруте <code>GET</code> значениями из базы данных.</p> + +<h2 class="highlight-spanned" id="Controller—get_route"><span class="highlight-span">Controller—get route</span></h2> + +<p>Откройте <strong>/controllers/bookController.js</strong>. Найдите экспортируемый метод контроллера <code>book_update_get()</code> и замените его на следующий код.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display book update form on GET.</span> +exports<span class="punctuation token">.</span>book_update_get <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + <span class="comment token">// Get book, authors and genres for form.</span> + <span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">parallel</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + book<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Book<span class="punctuation token">.</span><span class="function token">findById</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>params<span class="punctuation token">.</span>id<span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'genre'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + authors<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Author<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + genres<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Genre<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>results<span class="punctuation token">.</span>book<span class="operator token">==</span><span class="keyword token">null</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="comment token">// No results.</span> + <span class="keyword token">var</span> err <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Error</span><span class="punctuation token">(</span><span class="string token">'Book not found'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + err<span class="punctuation token">.</span>status <span class="operator token">=</span> <span class="number token">404</span><span class="punctuation token">;</span> + <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="comment token">// Success.</span> + <span class="comment token">// Mark our selected genres as checked.</span> + <span class="keyword token">for</span> <span class="punctuation token">(</span><span class="keyword token">var</span> all_g_iter <span class="operator token">=</span> <span class="number token">0</span><span class="punctuation token">;</span> all_g_iter <span class="operator token"><</span> results<span class="punctuation token">.</span>genres<span class="punctuation token">.</span>length<span class="punctuation token">;</span> all_g_iter<span class="operator token">++</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">for</span> <span class="punctuation token">(</span><span class="keyword token">var</span> book_g_iter <span class="operator token">=</span> <span class="number token">0</span><span class="punctuation token">;</span> book_g_iter <span class="operator token"><</span> results<span class="punctuation token">.</span>book<span class="punctuation token">.</span>genre<span class="punctuation token">.</span>length<span class="punctuation token">;</span> book_g_iter<span class="operator token">++</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>all_g_iter<span class="punctuation token">]</span><span class="punctuation token">.</span>_id<span class="punctuation token">.</span><span class="function token">toString</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="operator token">==</span>results<span class="punctuation token">.</span>book<span class="punctuation token">.</span>genre<span class="punctuation token">[</span>book_g_iter<span class="punctuation token">]</span><span class="punctuation token">.</span>_id<span class="punctuation token">.</span><span class="function token">toString</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>all_g_iter<span class="punctuation token">]</span><span class="punctuation token">.</span>checked<span class="operator token">=</span><span class="string token">'true'</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'book_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Update Book'</span><span class="punctuation token">,</span> authors<span class="punctuation token">:</span>results<span class="punctuation token">.</span>authors<span class="punctuation token">,</span> genres<span class="punctuation token">:</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">,</span> book<span class="punctuation token">:</span> results<span class="punctuation token">.</span>book <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>Контроллер получит id <code>Book</code> книги для обновления из параметра URL (<code>req.params.id</code>). Он использует метод <code>async.parallel()</code>чтобы получить указанную запись <code>Book</code> (pаполнение полей жанра и автора) и список всех объектов <code>Author</code> и <code>Genre</code>. Когда все операции завершены, он помечает выбранные жанры как отмеченные, а затем отображает их в <strong>book_form.pug</strong>, передает переменные <code>itle</code>, book, всех <code>authors</code>, и все<code>genres</code>.</p> + +<h2 class="highlight-spanned" id="Controller—post_route"><span class="highlight-span">Controller—post route</span></h2> + +<p>Найдите экспортируемый метод контроллера <code>book_update_post()</code> и замените его следующим кодом.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Handle book update on POST.</span> +exports<span class="punctuation token">.</span>book_update_post <span class="operator token">=</span> <span class="punctuation token">[</span> + + <span class="comment token">// Convert the genre to an array</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + <span class="keyword token">if</span><span class="punctuation token">(</span><span class="operator token">!</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre <span class="keyword token">instanceof</span> <span class="class-name token">Array</span><span class="punctuation token">)</span><span class="punctuation token">)</span><span class="punctuation token">{</span> + <span class="keyword token">if</span><span class="punctuation token">(</span><span class="keyword token">typeof</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">===</span><span class="string token">'undefined'</span><span class="punctuation token">)</span> + req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">=</span><span class="punctuation token">[</span><span class="punctuation token">]</span><span class="punctuation token">;</span> + <span class="keyword token">else</span> + req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">=</span><span class="keyword token">new</span> <span class="class-name token">Array</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="function token">next</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + + <span class="comment token">// Validate fields.</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'title'</span><span class="punctuation token">,</span> <span class="string token">'Title must not be empty.'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">,</span> <span class="string token">'Author must not be empty.'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'summary'</span><span class="punctuation token">,</span> <span class="string token">'Summary must not be empty.'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'isbn'</span><span class="punctuation token">,</span> <span class="string token">'ISBN must not be empty'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Sanitize fields.</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'title'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'summary'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'isbn'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'genre.*'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Process request after validation and sanitization.</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + + <span class="comment token">// Extract the validation errors from a request.</span> + <span class="keyword token">const</span> errors <span class="operator token">=</span> <span class="function token">validationResult</span><span class="punctuation token">(</span>req<span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="comment token">// Create a Book object with escaped/trimmed data and old id.</span> + <span class="keyword token">var</span> book <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Book</span><span class="punctuation token">(</span> + <span class="punctuation token">{</span> title<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>title<span class="punctuation token">,</span> + author<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>author<span class="punctuation token">,</span> + summary<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>summary<span class="punctuation token">,</span> + isbn<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>isbn<span class="punctuation token">,</span> + genre<span class="punctuation token">:</span> <span class="punctuation token">(</span><span class="keyword token">typeof</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">===</span><span class="string token">'undefined'</span><span class="punctuation token">)</span> <span class="operator token">?</span> <span class="punctuation token">[</span><span class="punctuation token">]</span> <span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="punctuation token">,</span> + _id<span class="punctuation token">:</span>req<span class="punctuation token">.</span>params<span class="punctuation token">.</span>id <span class="comment token">//This is required, or a new ID will be assigned!</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span>errors<span class="punctuation token">.</span><span class="function token">isEmpty</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// There are errors. Render form again with sanitized values/error messages.</span> + + <span class="comment token">// Get all authors and genres for form.</span> + <span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">parallel</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + authors<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Author<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + genres<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Genre<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + + <span class="comment token">// Mark our selected genres as checked.</span> + <span class="keyword token">for</span> <span class="punctuation token">(</span><span class="keyword token">let</span> i <span class="operator token">=</span> <span class="number token">0</span><span class="punctuation token">;</span> i <span class="operator token"><</span> results<span class="punctuation token">.</span>genres<span class="punctuation token">.</span>length<span class="punctuation token">;</span> i<span class="operator token">++</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>book<span class="punctuation token">.</span>genre<span class="punctuation token">.</span><span class="function token">indexOf</span><span class="punctuation token">(</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>i<span class="punctuation token">]</span><span class="punctuation token">.</span>_id<span class="punctuation token">)</span> <span class="operator token">></span> <span class="operator token">-</span><span class="number token">1</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>i<span class="punctuation token">]</span><span class="punctuation token">.</span>checked<span class="operator token">=</span><span class="string token">'true'</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'book_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Update Book'</span><span class="punctuation token">,</span>authors<span class="punctuation token">:</span>results<span class="punctuation token">.</span>authors<span class="punctuation token">,</span> genres<span class="punctuation token">:</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">,</span> book<span class="punctuation token">:</span> book<span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">return</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + <span class="comment token">// Data from form is valid. Update the record.</span> + Book<span class="punctuation token">.</span><span class="function token">findByIdAndUpdate</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>params<span class="punctuation token">.</span>id<span class="punctuation token">,</span> book<span class="punctuation token">,</span> <span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span>thebook<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful - redirect to book detail page.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>thebook<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">;</span></code></pre> + +<p>Это очень похоже на маршрут записи, используемый при создании Book. Сперва мы проверяем и очищаем данные книги и используем их для создание нового объекта <code>Book</code>(устанавливая его значение <code>_id</code> в идентификатор объекта для обновления). Если есть ошибки, когда мы проверяем данные, то мы повторно представляем форму, дополнительно отображая данные, введенные пользователем, ошибки, а также списки жанров и авторов. Если ошибок нет, то мы вызываем <code>Book.findByIdAndUpdate()</code> для обновления документа <code>Book</code>, а затем перенаправить на страницу сведений.</p> + +<h2 class="highlight-spanned" id="View"><span class="highlight-span">View</span></h2> + +<p>Откройте <strong>/views/book_form.pug</strong> и обновите раздел, в котором элемент управления "форма автора" имеет условный код, показанный ниже.</p> + +<pre class="line-numbers language-html"><code class="language-html"> div.form-group + label(for='author') Author: + select#author.form-control(type='select' placeholder='Select author' name='author' required='true' ) + for author in authors + if book + //- Handle GET form, where book.author is an object, and POST form, where it is a string. + option( + value=author._id + selected=( + author._id.toString()==book.author._id + || author._id.toString()==book.author + ) ? 'selected' : false + ) #{author.name} + else + option(value=author._id) #{author.name}</code></pre> + +<div class="note"> +<p><strong>Note</strong>: Это изменение кода необходимо для того, чтобы форму book_form можно было использовать как для создания, так и для обновления объектов book (без этого при создании формы на маршруте <code>GET</code> возникает ошибка).</p> +</div> + +<h2 class="highlight-spanned" id="Добавить_кнопку_обновления">Добавить кнопку обновления</h2> + +<p>Откройте <strong>book_detail.pug</strong> и убедитесь, что есть ссылки для удаления и обновления книг в нижней части страницы, как показано ниже.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html"> hr + p + a(href=book.url+'/delete') Delete Book + p + a(href=book.url+'/update') Update Book</code></pre> + +<p>Теперь вы можете обновлять книги со страницы сведений о книге.</p> + +<h2 class="highlight-spanned" id="Как_это_выглядит"><span class="highlight-span">Как это выглядит?</span></h2> + +<p>Запустите приложение, откройте ваш браузер на <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>, выберите ссылку <em>All books</em>, затем выберите конкретную книгу. Наконец, выберите ссылку <em>Update Book</em>.</p> + +<p>Форма должна выглядеть так же, как страница <em>Create book</em>, только с заголовком 'Update book' и предварительно заполнены значениями записей.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14498/LocalLibary_Express_Book_Update_NoErrors.png" style="border-style: solid; border-width: 1px; display: block; height: 443px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p><strong>Note:</strong> Другие страницы для обновления объектов могут быть реализованы примерно таким же образом. Мы оставили это как задание.</p> +</div> + +<p> </p> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a>.</li> +</ul> + +<p> </p> diff --git a/files/ru/learn/server-side/express_nodejs/index.html b/files/ru/learn/server-side/express_nodejs/index.html new file mode 100644 index 0000000000..9c1f44a50f --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/index.html @@ -0,0 +1,77 @@ +--- +title: Веб-фреймворк Express (Node.js/JavaScript) +slug: Learn/Server-side/Express_Nodejs +tags: + - Express + - Express.js + - Введение + - Для начинающих + - Программирование на стороне сервера + - Учебник +translation_of: Learn/Server-side/Express_Nodejs +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">Express представляет собой популярный веб-фреймворк, написанный на JavaScript и работающий внутри среды исполнения node.js. Этот модуль освещает некоторые ключевые преимущества этого фреймворка, установку среды разработки и выполнение основных задач веб-разработки и развертывания.</p> + +<h2 id="Предварительные_требования">Предварительные требования</h2> + +<p>Перед началом этого модуля вам необходимо представлять, что из себя представляет серверное программирование и веб-фреймворки, желательно из прочтения статей другого модуля <a href="/en-US/docs/Learn/Server-side/First_steps">Server-side website programming first steps</a>. Знакомство с основными концепциями программирования и языком программирования <a href="/en-US/docs/Web/JavaScript">JavaScript</a> будет очень полезным, но оно не является обязательным для понимания базовых понятий этого модуля.</p> + +<div class="note"> +<p><strong>Заметка</strong>: Этот веб-сайт содержит множество источников для изучения JavaScript<em> в контексте разработки на стороне клиента</em>: <a href="/en-US/docs/Web/JavaScript">JavaScript</a>, <a href="/en-US/docs/Web/JavaScript/Guide">JavaScript Guide</a>, <a href="/en-US/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript Basics</a>, <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> (изучение). Ключевые особенности и коцепции языка JavaScript остаются сходными и для серверной разработки на Node.js и используемый материал достаточно релевантен. Node.js предоставляет <a href="https://nodejs.org/dist/latest-v6.x/docs/api/">additional APIs</a> для обеспечения функционала, который полезен для "безбраузерной" разработки, т.е. для создания HTTP-сервера и доступа к файловой системе, но не поддерживает JavaScript APIs для работы с браузером и DOM.</p> + +<p>Это руководство обеспечит вас некоторой информацией о работе с Node.js и Express, но также существуют и другие многочисленные отличные ресурсы в Интернете и книгах — некоторые из них доступны из тем <a href="http://stackoverflow.com/a/5511507/894359">How do I get started with Node.js</a> (StackOverflow) и <a href="https://www.quora.com/What-are-the-best-resources-for-learning-Node-js?">What are the best resources for learning Node.js?</a> (Quora).</p> +</div> + +<h2 id="Руководства">Руководства</h2> + +<dl> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Введение в Express/Node</a></dt> + <dd>В первой статье об Express мы ответим на вопросы "Что такое Node?" и "Что такое Express?" и дадим вам представление о том, что делает веб-фреймворк Express особенным. <span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_3">Мы расскажем об основных функциях и покажем вам некоторые из основных строительных блоков приложений Express (хотя на данный момент у вас еще нет среды разработки, в которой можно ее протестировать)</span>.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Настройка среды разработки Node (Express)</a></dt> + <dd> + <div style="padding-bottom: 30px;"><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">Теперь, когда вы знаете, что такое Express, мы покажем вам, как настроить и протестировать среду разработки Node/Express в Windows, Linux (Ubuntu) и Mac OS X. Независимо от того, какую популярную операционную систему вы используете, эта статья </span><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_1">даст вам то, что вам нужно, чтобы начать разработку приложений Express.</span></div> + </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Учебник Express: сайт LocalLibrary</a></dt> + <dd> + <div style="padding-bottom: 30px;"><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">Первая статья в нашей серии практических уроков объясняет, что вы будете изучать, и предоставит обзор веб-сайта «локальной библиотеки», над которым мы будем работать и развивать в последующих статьях.</span></div> + </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Учебник Express часть 2: Создание скелета веб-сайта</a></dt> + <dd> + <div style="padding-bottom: 30px;"><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">В этой статье показано, как вы можете создать «скелет»</span><span class="s3gt_translate_tooltip_variant"> веб-сайта, который затем можно будет заполнить с помощью маршрутов сайта, шаблонов/представлений и баз данных.</span></div> + </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Учебник Express часть 3: Использование базы данных (с помощью Mongoose)</a></dt> + <dd>В этой статье кратко представлены базы данных для Node/Express. Затем показывается, как мы можем использовать <a href="http://mongoosejs.com/">Mongoose</a> для обеспечения доступа к баз данных для сайта <em>LocalLibrary</em>. <span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_2">В уроке объясняется, как объявляются объектная схема и модели, основные типы полей и базовая валидация.</span> <span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_3">Также кратко показаны некоторые из основных способов доступа к данным модели.</span></dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Учебник Express часть 4: Маршруты и контроллеры</a></dt> + <dd>В этом уроке мы создадим маршруты (код обработки URL) с "фиктивным" обработчиком функций для всех конечных точек ресурсов, которые нам в конечном итоге понадобятся для сайта<em> LocalLibrary</em>. По завершении мы будем иметь модульную структуру нашего кода обработки маршрута, который мы можем расширить с помощью функций реального обработчика в следующих статьях. Мы также будем очень хорошо понимать, как создавать модульные маршруты, используя Express.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Учебник Express часть 5: Отображение данных библиотеки</a></dt> + <dd>Теперь мы готовы добавить страницы, на которых будут отображаться книги веб-сайта <em>LocalLibrary</em> и другие данные. Страницы будут включать главную страницу, которая показывает сколько записей определенного типа мы имеем и отдельную страницу для детального просмотра записи. По пути мы получим практический опыт в получении записей из баз данных и использовании шаблонов.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Учебник Express часть 6: Работы с формами</a></dt> + <dd>В этой части мы покажем вам, как работать с <a href="/en-US/docs/Web/Guide/HTML/Forms">HTML формами</a> в Express, используя Pug, и в частности, как создавать, обновлять и удалять документы из базы данных.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Учебник Express часть 7: Выкладка в production</a></dt> + <dd>Теперь когда вы создали восхитительный сайт <em>LocalLibrary</em>, вы захотите установить его на общедоступном сервере, чтобы он мог дать доступ персоналу библиотеки и пользователям в Интернет. В этой статье представлен обзор того, как вы можете найти хост для развертывания вашего сайта и что вам нужно сделать, чтобы подготовить ваш сайт к публикации.</dd> +</dl> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<dl> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry">Установка LocalLibrary на PWS/Cloud Foundry</a></dt> + <dd>В этой статье представлена практическая демонстрация того, как установить <em>LocalLibrary</em> на <a href="http://run.pivotal.io">облаке Pivotal Web Services PaaS</a> — это полнофункциональная альтернатива с открытым исходным кодом для Heroku, облачного сервиса PaaS используемого в части 7 этого учебника, представленного выше. PWS/Cloud Foundry опредленно стоит попробовать, если вы ищете альтернативу Heroku (или другому PaaS облачному сервису), или просто хотите попробовать что-то другое.</dd> +</dl> + +<h2 id="Изучите_другие_учебники">Изучите другие учебники</h2> + +<div> +<p>Это все статьи учебника (на данный момент). Если вы хотите продолжить обучение, есть другие интересные темы:</p> + +<ul> + <li>Использование сессий</li> + <li>Авторизация пользователей</li> + <li>Авторизация пользователей и уровни доступа</li> + <li>Тестирование веб приложений Express</li> + <li>Веб безопасность для веб приложений Express.</li> +</ul> + +<p>И, конечно, было бы неплохо создать какой-либо проверочный тест знаний!</p> +</div> diff --git a/files/ru/learn/server-side/express_nodejs/introduction/index.html b/files/ru/learn/server-side/express_nodejs/introduction/index.html new file mode 100644 index 0000000000..bbe40c95f7 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/introduction/index.html @@ -0,0 +1,512 @@ +--- +title: Express/Node introduction +slug: Learn/Server-side/Express_Nodejs/Introduction +translation_of: Learn/Server-side/Express_Nodejs/Introduction +--- +<div>{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">В этой первой статье по Express мы ответим на вопросы "Что такое Node?" и "Что такое Express?", и сделаем обзор того, что делает веб-фреймворк Express таким особенным. <span id="result_box" lang="ru"><span>Мы расскажем об основных функциях и покажем вам некоторые из основных строительных блоков приложения Express (хотя на данный момент у вас еще нет среды разработки, в которой можно ее протестировать).</span></span></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Предварительные знания:</th> + <td><span id="result_box" lang="ru"><span>Базовая компьютерная грамотность.</span> <span>Общее понимание </span></span><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps">серверного программирования веб-сайтов </a><span lang="ru"><span>и, в частности, механики </span></span><a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">клиент-серверного взаимодействия на веб-сайтах</a>.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Ознакомить Вас с фреймворком <span id="result_box" lang="ru"><span>Express и как он вписывается в среду Node, какие функции он предоставляет, и основные строительные блоки приложения Express.</span></span></td> + </tr> + </tbody> +</table> + +<h2 id="Что_такое_Express_и_Node">Что такое Express и Node?</h2> + +<p><a href="https://nodejs.org/">Node</a> (или более формально<em> Node.js</em>) <span id="result_box" lang="ru"><span>- кросплатформенная среда исполнения с открытым исходным кодом, которая позволяет разработчикам создавать всевозможные серверные инструменты и приложения используя язык </span></span><a href="/en-US/docs/Glossary/JavaScript">JavaScript</a><span lang="ru"><span>. Среда исполнения предназначена для использования вне контекста браузера </span></span>(т.е. <span id="result_box" lang="ru"><span>выполняется непосредственно на компьютере или на серверной ОС)</span></span>. <span id="result_box" lang="ru"><span>Таким образом, среда исключает API-интерфейсы JavaScript для браузера и добавляет поддержку более традиционных OS API-интерфейсов</span></span><span lang="ru"><span>, включая библиотеки HTTP и файловых систем.</span></span></p> + +<p>С точки зрения веб-серверной разработки Node имеет ряд преимуществ:</p> + +<ul> + <li>Отличная производительность! Node <span id="result_box" lang="ru"><span>был разработан для оптимизации пропускной способности и масштабируемости в веб-приложениях и очень хорошо справляется со многими распространенными проблемами веб-разработки (например, веб-приложения реального времени).</span></span></li> + <li><span id="result_box" lang="ru"><span>Код написан на «обычном старом JavaScript», а это означает, что затрачивается меньше времени при написании кода для браузера и веб-сервера связанное с «переключением технологий» между языками.</span></span></li> + <li><span id="result_box" lang="ru"><span>JavaScript является относительно новым языком программирования и имеет преимущества от улучшения дизайна языка по сравнению с другими традиционными языками для веб-серверов</span></span> (например, Python, PHP, и т.д.). Многие другие новые и популярные языки компилируются/конвертируются в JavaScript, поэтому вы можете также использовать CoffeeScript, ClosureScript, Scala, LiveScript, etc.</li> + <li>Менеджер пакетов Node (NPM) <span id="result_box" lang="ru"><span>обеспечивает доступ к сотням тысяч многоразовых пакетов.</span> <span>Он также имеет лучшее в своем классе разрешение зависимостей и может также использоваться для автоматизации большинства инструментов построения.</span></span></li> + <li>Он портативен, имеет версии для Microsoft Windows, OS X, Linux, Solaris, FreeBSD, OpenBSD, WebOS, и NonStop OS. Кроме того, он имеет хорошую поддержку среди многих хостинг-провайдеров, <span id="result_box" lang="ru"><span>которые часто предоставляют конкретную инфраструктуру и документацию для размещения сайтов, работающих на Node.</span></span></li> + <li><span id="result_box" lang="ru"><span>Он имеет очень активную стороннюю экосистему и сообщество разработчиков, которые всегда готовы помочь.</span></span></li> +</ul> + +<p><span lang="ru"><span>Вы можете изпользовать Node.js для создания простого веб сервера используя пакет Node HTTP. </span></span></p> + +<h3 id="Hello_Node.js">Hello Node.js</h3> + +<p>Следующий пример создаёт веб сервер который прослушивает любой HTTP запрос на <span id="result_box" lang="ru"><span>URL <code>http://127.0.0.1:8000/</code> </span></span>— когда запрос будет получен, скрипт ответит строкой "Hello World". Если Вы уже установили node, можете, следуя шагам инструкции попробовать пример:</p> + +<ol> + <li>Откройте терминал (в Windows окно командной строки)</li> + <li>Создайте папку, куда вы хотите сохранить программу, к примеру <code>test-node</code> и перейдите в нее с помощью следующей команды:</li> +</ol> + +<pre class="notranslate"><code>cd test-node</code></pre> + +<ol start="3"> + <li>Используя любимый текстовый редактор, создайте файл <code>hello.js</code> и вставьте в него код:</li> +</ol> + +<pre class="brush: js notranslate">// Загружаем HTTP модуль +const http = require("http"); + +const hostname = "127.0.0.1"; +const port = 8000; + +// Создаем HTTP-сервер +const server = http.createServer((req, res) => { + + // Устанавливаем HTTP-заголовок ответа с HTTP статусом и Content type + res.writeHead(200, {'Content-Type': 'text/plain'}); + + // Отсылаем тело ответа "Hello World" + res.end('Hello World\n'); +}); + +// Выводим лог как только сервер будет запущен +server.listen(port, hostname, () => { + console.log(`Server running at http://${hostname}:${port}/`); +}) +</pre> + +<ol start="4"> + <li>Сохраните файл в папку, созданную выше.</li> + <li>Вернитесь в терминал и выполните следующую команду:</li> +</ol> + +<pre class="notranslate"><code>node hello.js</code></pre> + +<p>В итоге, перейдите по ссылке <code>http://localhost:8000</code> в вашем браузере; вы должны увидеть текст "<strong>Hello World</strong>" в верху слева на чистой странице.</p> + +<h2 id="Веб_фреймворк">Веб фреймворк</h2> + +<p>Другие общие для веб-программирования задачи не поддерживаются на прямую Node. Если вы хотите добавить специфичную поддержку различных HTTP методов (например <code>GET</code>, <code>POST</code>, <code>DELETE</code>, и т.д.) по разному для разных URL путей ("routes"), отдачу статических файлов, или использовать шаблоны для создания динамических ответов, вам нужно написать код самим, или можете отказаться от изобретения колеса и использовать фреймворк!</p> + +<h2 id="Введение_в_Express">Введение в Express</h2> + +<p><a href="https://expressjs.com/">Express</a> - самый популярный веб-фреймворк для <em>Node</em>. Он является базовой библиотекой для ряда других популярных <a href="https://expressjs.com/en/resources/frameworks.html">веб-фреймворков Node</a>. Он предоставляет следующие механизмы:</p> + +<ul> + <li>Написание обработчиков для запросов с различными HTTP-методами в разных URL-адресах (маршрутах).</li> + <li>Интеграцию с механизмами рендеринга «view», для генерации ответов, вставляя данные в шаблоны.</li> + <li>Установка общих параметров веб-приложения, такие как порт для подключения, и расположение шаблонов, которые используются для отображения ответа.</li> + <li>«промежуточное ПО» для дополнительной обработки запроса в любой момент в конвейере обработки запросов.</li> +</ul> + +<p>В то время как сам express довольно минималистичный, разработчики создали совместимые пакеты промежуточного программного обеспечения для решения практически любой проблемы с веб-разработкой. Существуют библиотеки для работы с куки-файлами, сеансами, входами пользователей, параметрами URL, данными POST, заголовками безопасности и многими другими. Вы можете найти список пакетов промежуточного программного обеспечения, поддерживаемых командой Express в <a href="http://expressjs.com/en/resources/middleware.html">Express Middleware</a> (наряду со списком некоторых популярных пакетов сторонних производителей) .</p> + +<div class="note"> +<p><strong>Примечание:</strong> Гибкость это палка о двух концах. <span id="result_box" lang="ru"><span>Существуют пакеты промежуточного программного обеспечения (middleware) для решения практически любых проблем или для удовлетворения любых ваших требований, но правильный выбор подходящих пакетов иногда может быть проблемой.</span> <span>Также нет «правильного пути» для структурирования приложения, и многие примеры, которые вы можете найти в Интернете, не являются оптимальными или лишь показывают небольшую часть того, что вам нужно сделать для разработки веб-приложения.</span></span></p> +</div> + +<h2 id="Откуда_это_все_взялось">Откуда это все взялось?</h2> + +<p>Node первоначально был выпущен только под Linux в 2009. Менеджер пакетов NPM был выпущен в 2010, а поддержка Windows была добавлена в 2012. Текущая LTS-версия Node v12.16.1 , в то время как последний выпуск Node версии 13.11.0. Это короткий экскурс в историю; обратитесь к <a href="https://en.wikipedia.org/wiki/Node.js#History">Википедии</a>, если вы хотите узнать больше).</p> + +<p>Express первоначально был выпущен в ноябре 2010 и текущая версия API 4.17.1 Вы можете отследить <a href="https://expressjs.com/en/changelog/4x.html">изменения</a> и текущий релиз, и <a href="https://github.com/expressjs/express/blob/master/History.md">GitHub</a> для более детальной информации о релизах.</p> + +<h2 id="Насколько_популярен_NodeExpress">Насколько популярен Node/Express?</h2> + +<p>Популярность веб-фрэймворка важна, поскольку она является индикатором того, будет ли она продолжаться, и какие ресурсы, вероятно, будут доступны с точки зрения документации, дополнительных библиотек и технической поддержки.</p> + +<p>Не существует какого-либо доступного и точного измерения популярности серверных фреймворков (хотя сайты, такие как Hot Frameworks, пытаются оценить популярность, используя такие механизмы, как подсчет количества проектов на GitHub и вопросов на StackOverflow для каждой платформы). Лучший вопрос заключается в том, достаточно ли популярны Node и Express, чтобы избежать проблем с непопулярными платформами. Они продолжают развиваться? Можете ли вы получить помощь, если вам это нужно? Есть ли у вас возможность получить оплачиваемую работу, если вы изучаете Express?</p> + +<p>Как только мы посмотрим на список <a href="https://expressjs.com/en/resources/companies-using-express.html">широкоизвестных компаний</a> пользующихся Express, количество разработчиков участвующих в разработке Express, и громадному числу людей, которые занимаются поддержкой Express, то мы с уверенностью скажем - <em>Express</em> поистине популярный фреймворк!</p> + +<h2 id="Является_ли_Express_ограничивающим">Является ли Express ограничивающим?</h2> + +<p>Web-фрэймворки часто принято делить на "ограничивающие" и "неограничивающие".</p> + +<p>Ограничивающими фрэймворки считаются фрэймворки, которые следуют "должным" ограничениям при выполнении отдельных задач. Довольно часто они ориентированы на ускоренную разработку <em>в конкретной области</em> (решение задач определенного типа), поскольку должный подход к произвольно выбранной задаче бывает не прост для понимания и плохо документирован. При этом они лишаются гибкости при решении задач выходящих за сферу их обычного применения, а так же проявляют тенденцию к ограничению выбора компонентов и подходов своего применения. </p> + +<p>Напротив, неограничивающие фреймворки имеют гораздо меньше ограничений для связи компонентов, что бы достичь цели или ограничений в выборе используемых компонентов. Они облегчают разработчикам использование наиболее подходящих инструментов для выполнения конкретной задачи, но платой за это будет то, что вы самостоятельно должны найти такие компоненты.</p> + +<p>Express не ограничивающий. Вы можете вставить в цепочку обработки (middleware) запросов практически любое совместимые промежуточные компоненты, которые вам нравятся. Вы можете структурировать приложение в одном файле или в нескольких, использую любую структуру каталогов. Иногда вы можете чувствовать, что у вас слишком много вариантов!</p> + +<h2 id="Как_выглядит_код_Express">Как выглядит код Express?</h2> + +<p>В традиционных динамических веб-сайтах, веб-приложение ожидает HTTP-запроса от веб-браузера (или другого клиента). Когда запрос получен, приложение определяет, какое действие необходимо выполнить на основе URL шаблна и, возможно, связанной информации, содержащейся в данных <code>POST</code> или <code>GET</code>. В зависимости от того, что требуется, Express может затем читать или записывать данные из/в базы данных или выполнять другие задачи, в соответствии с полученным запросом. Затем приложение возвращает ответ в веб-браузер, зачастую динамически создавая HTML страницу для отображения браузером, вставляя извлеченные данные в заполнители HTML шаблона.</p> + +<p>Express предоставляет методы позволяющие указать, какая функция вызывается для конкретного HTTP запроса (<code>GET</code>, <code>POST</code>, <code>SET</code>, etc.), и URL шаблон ("Route"), а также методы позволяющие указать, какой механизм шаблона ("view") используется, где находятся шаблоныы файлов и какой шаблон использовать для вывода ответа. Вы можете использовать Express middleware для добавления поддержки файлов cookies, сеансов, и пользователей, получения <code>POST</code>/<code>GET</code> параметров, и т.д. Вы можете использовать любой механизм базы данных, поддерживаемый Node (Express не определяет поведение, связанное с базой данных).</p> + +<p>В следующих разделах объясняются некоторые общие моменты, которые вы увидите при работе с кодом <em>Express</em> and <em>Node</em>.</p> + +<h3 id="Helloworld_Express">Helloworld Express</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Сначала давайте рассмотрим стандартный пример Express Hello World (мы обсудим каждую часть этого ниже и в следующих разделах).</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Совет: Если у вас уже установлены Node и Express (или если вы устанавливаете их, как показано в следующей статье), вы можете сохранить этот код в файле с именем app.js и запустить его в командной строке, вызвав узел app.js.</span> <span title="">отражения</span><span title="">)</span><span title="">.</span></span></p> +</div> + +<pre class="brush: js notranslate">var express = require('express'); +var app = express(); + +<strong>app.get('/', function(req, res) { + res.send('Hello World!'); +});</strong> + +app.listen(3000, function() { + console.log('Example app listening on port 3000!'); +}); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Первые две строки требуют () (импорт) модуля Express и создания приложения Express.</span> <span title="">Этот объект, который традиционно называется app, имеет методы для маршрутизации HTTP-запросов, настройки промежуточного программного обеспечения, рендеринга представлений HTML, регистрации механизма шаблонов и изменения параметров приложения, которые управляют поведением приложения (например, режим среды, чувствительны ли определения маршрута к регистру).</span> <span title="">, и т.д.)</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Средняя часть кода (три строки, начинающиеся с app.get) показывает определение маршрута.</span> <span title="">Метод app.get () указывает функцию обратного вызова, которая будет вызываться всякий раз, когда есть HTTP-запрос GET с путем ('/') относительно корня сайта.</span> <span title="">Функция обратного вызова принимает запрос и объект ответа в качестве аргументов и просто вызывает send () для ответа, чтобы вернуть строку «Hello World!»</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Последний блок запускает сервер через порт «3000» и печатает комментарий журнала в консоль.</span> <span title="">Когда сервер работает, вы можете перейти к localhost: 3000 в вашем браузере, чтобы увидеть возвращенный пример ответа.</span></span></p> + +<h3 id="Импорт_и_создание_модулей"><span class="tlid-translation translation" lang="ru"><span title="">Импорт и создание модулей</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Модуль - это библиотека / файл JavaScript, который вы можете импортировать в другой код с помощью функции require () Node.</span> <span title="">Express сам по себе является модулем, как и промежуточное программное обеспечение и библиотеки баз данных, которые мы используем в наших приложениях Express.</span><br> + <br> + <span title="">Приведенный ниже код показывает, как мы импортируем модуль по имени, используя в качестве примера платформу Express.</span> <span title="">Сначала мы вызываем функцию require (), определяя имя модуля в виде строки («express») и вызывая возвращенный объект для создания приложения Express.</span> <span title="">Затем мы можем получить доступ к свойствам и функциям объекта приложения.</span></span></p> + +<pre class="brush: js notranslate">var express = require('express'); +var app = express(); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы также можете создавать свои собственные модули, которые можно импортировать таким же образом.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Совет: вы захотите создать свои собственные модули, потому что это позволяет вам организовать ваш код в управляемые части - монолитное однофайловое приложение трудно понять и поддерживать.</span> <span title="">Использование модулей также помогает вам управлять пространством имен, поскольку при использовании модуля импортируются только те переменные, которые вы явно экспортировали.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Чтобы сделать объекты доступными вне модуля, вам просто нужно назначить их объекту экспорта.</span> <span title="">Например, модуль square.js ниже представляет собой файл, который экспортирует методы area () и perimeter ():</span></span></p> + +<pre class="brush: js notranslate">exports.area = function(width) { return width * width; }; +exports.perimeter = function(width) { return 4 * width; }; +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Мы можем импортировать этот модуль, используя require (), а затем вызвать экспортированные методы, как показано:</span></span></p> + +<pre class="brush: js notranslate">var square = require('./square'); // Here we require() the name of the file without the (optional) .js file extension +console.log('The area of a square with a width of 4 is ' + square.area(4));</pre> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. Вы также можете указать абсолютный путь к модулю (или имя, как мы делали изначально).</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Если вы хотите экспортировать полный объект в одном назначении, а не создавать его по одному свойству за раз, назначьте его для module.exports, как показано ниже (вы также можете сделать это, чтобы сделать корень объекта экспорта конструктором или другой функцией)</span> <span title="">:</span></span></p> + +<pre class="brush: js notranslate">module.exports = { + area: function(width) { + return width * width; + }, + + perimeter: function(width) { + return 4 * width; + } +}; +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Для получения дополнительной информации о модулях см.</span></span> <a href="https://nodejs.org/api/modules.html#modules_modules">Modules</a> (Node API docs).</p> + +<h3 id="Использование_асинхронных_API"><span class="tlid-translation translation" lang="ru"><span title="">Использование асинхронных API</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Код JavaScript часто использует асинхронные, а не синхронные API для операций, выполнение которых может занять некоторое время.</span> <span title="">Синхронный API - это тот, в котором каждая операция должна завершиться до начала следующей операции.</span> <span title="">Например, следующие функции журнала являются синхронными и выводят текст на консоль по порядку (первый, второй).</span></span></p> + +<pre class="brush: js notranslate">console.log('First'); +console.log('Second'); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">В отличие от этого, асинхронный API - это тот, в котором API начнет операцию и сразу же вернется (до завершения операции).</span> <span title="">После завершения операции API будет использовать некоторый механизм для выполнения дополнительных операций.</span> <span title="">Например, приведенный ниже код выведет «Second, First», потому что хотя метод setTimeout () вызывается первым и возвращается немедленно, операция не завершается в течение нескольких секунд.</span></span></p> + +<pre class="brush: js notranslate">setTimeout(function() { + console.log('First'); + }, 3000); +console.log('Second'); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Использование неблокирующих асинхронных API-интерфейсов еще более важно в Node, чем в браузере, поскольку Node - это однопоточная среда выполнения, управляемая событиями.</span> <span title="">«Однопоточный» означает, что все запросы к серверу выполняются в одном потоке (а не порождаются в отдельных процессах).</span> <span title="">Эта модель чрезвычайно эффективна с точки зрения скорости и ресурсов сервера, но это означает, что если любая из ваших функций вызывает синхронные методы, выполнение которых занимает много времени, они будут блокировать не только текущий запрос, но и любой другой запрос, обрабатываемый</span> <span title="">ваше веб-приложение.</span><br> + <br> + <span title="">Есть несколько способов, которыми асинхронный API уведомляет ваше приложение о том, что оно завершено.</span> <span title="">Наиболее распространенный способ - зарегистрировать функцию обратного вызова при вызове асинхронного API, который будет вызываться после завершения операции.</span> <span title="">Это подход, использованный выше.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Совет: Использование обратных вызовов может быть довольно «грязным», если у вас есть последовательность зависимых асинхронных операций, которые должны выполняться по порядку, потому что это приводит к нескольким уровням вложенных обратных вызовов.</span> <span title="">Эта проблема широко известна как «ад обратного вызова».</span> <span title="">Эту проблему можно решить с помощью хороших методов кодирования (см. Http://callbackhell.com/), использования такого модуля, как async, или даже перехода к функциям ES6, таким как Promises.</span></span></p> +</div> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. Общим соглашением для Node и Express является использование обратных вызовов с ошибками.</span> <span title="">В этом соглашении первое значение в ваших функциях обратного вызова является значением ошибки, в то время как последующие аргументы содержат данные об успехе.</span> <span title="">В этом блоге есть хорошее объяснение того, почему этот подход полезен: путь Node.js - понимание обратных вызовов с ошибками (fredkschott.com).</span></span></p> +</div> + +<h3 id="Создание_обработчиков_маршрута"><span class="tlid-translation translation" lang="ru"><span title="">Создание обработчиков маршрута</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">В нашем примере Hello World Express (см. Выше) мы определили функцию обработчика маршрута (обратного вызова) для HTTP-запросов GET к корню сайта ('/').</span></span></p> + +<pre class="brush: js notranslate">app.<strong>get</strong>('/', function(req, res) { + res.send('Hello World!'); +}); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Функция обратного вызова принимает запрос и объект ответа в качестве аргументов.</span> <span title="">В этом случае метод просто вызывает send () в ответе, чтобы вернуть строку «Hello World!»</span> <span title="">Существует ряд других методов ответа для завершения цикла запрос / ответ, например, вы можете вызвать res.json () для отправки ответа JSON или res.sendFile () для отправки файла.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Совет по JavaScript: вы можете использовать любые имена аргументов, которые вам нравятся, в функциях обратного вызова;</span> <span title="">при вызове обратного вызова первый аргумент всегда будет запросом, а второй всегда будет ответом.</span> <span title="">Имеет смысл назвать их так, чтобы вы могли идентифицировать объект, с которым работаете, в теле обратного вызова.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Объект приложения Express также предоставляет методы для определения обработчиков маршрутов для всех других HTTP-глаголов, которые в основном используются одинаково: post (), put (), delete (), options (), trace (), copy (</span> <span title="">), lock (), mkcol (), move (), purge (), propfind (), proppatch (), unlock (), report (), mkactivity (), checkout (), merge (</span> <span title="">), m-search (), notify (), subscribe (), unsubscribe (), patch (), search () и connect ().</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Существует специальный метод маршрутизации app.all (), который будет вызываться в ответ на любой метод HTTP.</span> <span title="">Это используется для загрузки функций промежуточного программного обеспечения по определенному пути для всех методов запроса.</span> <span title="">В следующем примере (из документации Express) показан обработчик, который будет выполняться для запросов к / secret независимо от используемого глагола HTTP (при условии, что он поддерживается модулем http).</span></span></p> + +<pre class="brush: js notranslate">app.all('/secret', function(req, res, next) { + console.log('Accessing the secret section ...'); + next(); // pass control to the next handler +});</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Маршруты позволяют сопоставлять определенные шаблоны символов в URL-адресе, извлекать некоторые значения из URL-адреса и передавать их в качестве параметров обработчику маршрута (в качестве атрибутов объекта запроса, передаваемого в качестве параметра).</span><br> + <br> + <span title="">Часто полезно группировать обработчики маршрутов для определенной части сайта и получать к ним доступ с помощью общего префикса маршрута (например, сайт с вики может иметь все связанные с вики маршруты в одном файле и иметь к ним доступ с префиксом маршрута</span> <span title="">из / вики /).</span> <span title="">В Express это достигается с помощью объекта express.Router.</span> <span title="">Например, мы можем создать наш вики-маршрут в модуле с именем wiki.js, а затем экспортировать объект Router, как показано ниже:</span></span></p> + +<pre class="brush: js notranslate">// wiki.js - Wiki route module + +var express = require('express'); +var router = express.Router(); + +// Home page route +router.get('/', function(req, res) { + res.send('Wiki home page'); +}); + +// About page route +router.get('/about', function(req, res) { + res.send('About this wiki'); +}); + +module.exports = router; +</pre> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. Добавление маршрутов к объекту Router аналогично добавлению маршрутов к объекту приложения (как показано ранее).</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Чтобы использовать маршрутизатор в нашем главном файле приложения, нам потребуется () модуль route (wiki.js), а затем вызовите use () в приложении Express, чтобы добавить маршрутизатор в путь обработки промежуточного программного обеспечения.</span> <span title="">Эти два маршрута будут доступны из / wiki / и / wiki / about /.</span></span></p> + +<pre class="brush: js notranslate">var wiki = require('./wiki.js'); +// ... +app.use('/wiki', wiki);</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Мы покажем вам намного больше о работе с маршрутами, и в частности об использовании маршрутизатора, позже в связанном разделе</span></span> <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes"> Routes and controllers .</a></p> + +<h3 id="Использование_промежуточного_программного_обеспечения"><span class="tlid-translation translation" lang="ru"><span title="">Использование промежуточного программного обеспечения</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Промежуточное программное обеспечение широко используется в приложениях Express для задач от обслуживания статических файлов до обработки ошибок и сжатия HTTP-ответов.</span> <span title="">Принимая во внимание, что функции маршрута заканчивают цикл запроса-ответа HTTP, возвращая некоторый ответ клиенту HTTP, функции промежуточного программного обеспечения обычно выполняют некоторую операцию над запросом или ответом и затем вызывают следующую функцию в «стеке», которая может быть большим количеством промежуточного программного обеспечения или маршрута</span> <span title="">обработчик</span><span title="">.</span> <span title="">Порядок вызова промежуточного программного обеспечения зависит от разработчика приложения.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. Промежуточное программное обеспечение может выполнять любую операцию, выполнять любой код, вносить изменения в объект запроса и ответа, а также может завершать цикл запрос-ответ.</span> <span title="">Если он не завершает цикл, он должен вызвать next (), чтобы передать управление следующей функции промежуточного программного обеспечения (или запрос останется зависшим).</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Большинство приложений используют стороннее промежуточное программное обеспечение для упрощения общих задач веб-разработки, таких как работа с файлами cookie, сессиями, аутентификацией пользователя, доступом к данным запросов POST и JSON, ведение журнала и т. д. Список пакетов промежуточного программного обеспечения, поддерживаемых командой Express, можно найти.</span> <span title="">(который также включает в себя другие популярные сторонние пакеты).</span> <span title="">Другие экспресс-пакеты доступны в диспетчере пакетов NPM.</span><br> + <br> + <span title="">Для использования стороннего промежуточного программного обеспечения сначала необходимо установить его в свое приложение с помощью NPM.</span> <span title="">Например, чтобы установить промежуточное программное обеспечение средства регистрации HTTP-запросов morgan, вы должны сделать следующее:</span></span></p> + +<pre class="brush: bash notranslate"><code>$ npm install morgan +</code></pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Затем вы можете вызвать use () для объекта приложения Express, чтобы добавить промежуточное программное обеспечение в стек:</span></span></p> + +<pre class="brush: js notranslate">var express = require('express'); +<strong>var logger = require('morgan');</strong> +var app = express(); +<strong>app.use(logger('dev'));</strong> +...</pre> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. Промежуточное программное обеспечение и функции маршрутизации вызываются в том порядке, в котором они были объявлены.</span> <span title="">Для некоторого промежуточного программного обеспечения важен порядок (например, если промежуточное программное обеспечение сеанса зависит от промежуточного программного обеспечения cookie, то сначала должен быть добавлен обработчик cookie).</span> <span title="">Почти всегда случается так, что промежуточное ПО вызывается перед настройкой маршрутов, иначе ваши обработчики маршрутов не будут иметь доступа к функциям, добавленным вашим промежуточным ПО.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы можете написать свои собственные функции промежуточного программного обеспечения, и вам, вероятно, придется это сделать (хотя бы для создания кода обработки ошибок).</span> <span title="">Единственное различие между функцией промежуточного программного обеспечения и обратным вызовом обработчика маршрута состоит в том, что функции промежуточного программного обеспечения имеют третий аргумент, следующий: какие функции промежуточного программного обеспечения должны вызываться, если они не завершают цикл запроса (когда вызывается функция промежуточного программного обеспечения, она содержит следующую функцию).</span> <span title="">это надо называть).</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы можете добавить функцию промежуточного программного обеспечения в цепочку обработки с помощью app.use () или app.add (), в зависимости от того, хотите ли вы применить промежуточное программное обеспечение ко всем ответам или к ответам с определенным глаголом HTTP (GET, POST и т. д.).</span> <span title="">)</span><span title="">.</span> <span title="">Маршруты задаются одинаково в обоих случаях, хотя маршрут необязателен при вызове app.use ().</span><br> + <br> + <span title="">В приведенном ниже примере показано, как можно добавить функцию промежуточного программного обеспечения, используя оба метода, а также с / без маршрута.</span></span></p> + +<pre class="brush: js notranslate">var express = require('express'); +var app = express(); + +// An example middleware function +var a_middleware_function = function(req, res, <em>next</em>) { + // ... perform some operations + next(); // Call next() so Express will call the next middleware function in the chain. +} + +// Function added with use() for all routes and verbs +app.use(a_middleware_function); + +// Function added with use() for a specific route +app.use('/someroute', a_middleware_function); + +// A middleware function added for a specific HTTP verb and route +app.get('/', a_middleware_function); + +app.listen(3000);</pre> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Совет по JavaScript: выше мы объявляем функцию промежуточного программного обеспечения отдельно, а затем устанавливаем ее в качестве обратного вызова.</span> <span title="">В нашей предыдущей функции обработчика маршрута мы объявили функцию обратного вызова, когда она использовалась.</span> <span title="">В JavaScript любой подход является допустимым.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Документация по Express содержит намного больше отличной информации по использованию и написанию промежуточного программного обеспечения Express.</span></span></p> + +<h3 id="Обслуживание_статических_файлов"><span class="tlid-translation translation" lang="ru"><span title="">Обслуживание статических файлов</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы можете использовать промежуточное программное обеспечение express.static для обслуживания статических файлов, включая ваши изображения, CSS и JavaScript (static () - единственная функция промежуточного программного обеспечения, которая фактически является частью Express).</span> <span title="">Например, вы должны использовать строку ниже для обслуживания изображений, файлов CSS и файлов JavaScript из каталога с именем public на том же уровне, где вы вызываете узел:</span></span></p> + +<pre class="brush: js notranslate">app.use(express.static('public')); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Любые файлы в публичном каталоге обслуживаются путем добавления их имени файла (относительно базового «публичного» каталога) к базовому URL.</span> <span title="">Так, например:</span></span></p> + +<pre class="notranslate"><code>http://localhost:3000/images/dog.jpg +http://localhost:3000/css/style.css +http://localhost:3000/js/app.js +http://localhost:3000/about.html +</code></pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы можете вызывать static () несколько раз для обслуживания нескольких каталогов.</span> <span title="">Если файл не может быть найден одной функцией промежуточного программного обеспечения, он будет просто передан последующему промежуточному программному обеспечению (порядок вызова промежуточного программного обеспечения основан на вашем порядке объявления).</span></span></p> + +<pre class="brush: js notranslate">app.use(express.static('public')); +app.use(express.static('media')); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы также можете создать виртуальный префикс для ваших статических URL-адресов, вместо добавления файлов к базовому URL-адресу.</span> <span title="">Например, здесь мы указываем путь монтирования, чтобы файлы загружались с префиксом "/ media":</span></span></p> + +<pre class="brush: js notranslate">app.use('/media', express.static('public')); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Теперь вы можете загружать файлы, находящиеся в публичном каталоге, из префикса / media path.</span></span></p> + +<pre class="notranslate"><code>http://localhost:3000/media/images/dog.jpg +http://localhost:3000/media/video/cat.mp4 +http://localhost:3000/media/cry.mp3</code> +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Для получения дополнительной информации см.</span></span> <a href="Serving static files in Express">Serving static files in Express</a>.</p> + +<h3 id="Обработка_ошибок"><span class="tlid-translation translation" lang="ru"><span title="">Обработка ошибок</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Ошибки обрабатываются одной или несколькими специальными функциями промежуточного программного обеспечения, которые имеют четыре аргумента вместо обычных трех: (err, req, res, next).</span> <span title="">Например:</span></span></p> + +<pre class="brush: js notranslate">app.use(function(err, req, res, next) { + console.error(err.stack); + res.status(500).send('Something broke!'); +}); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Они могут возвращать любой требуемый контент, но должны вызываться после всех других app.use () и маршрутизировать вызовы, чтобы они были последним промежуточным ПО в процессе обработки запросов!</span><br> + <br> + <span title="">Express поставляется со встроенным обработчиком ошибок, который заботится обо всех оставшихся ошибках, которые могут возникнуть в приложении.</span> <span title="">Эта промежуточная функция обработки ошибок по умолчанию добавляется в конец стека функций промежуточного программного обеспечения.</span> <span title="">Если вы передаете ошибку в next () и не обрабатываете ее в обработчике ошибок, она будет обработана встроенным обработчиком ошибок;</span> <span title="">ошибка будет записана клиенту с трассировкой стека.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. Трассировка стека не включена в производственную среду.</span> <span title="">Чтобы запустить его в производственном режиме, необходимо установить переменную среды NODE_ENV в «производство».</span></span></p> +</div> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. HTTP404 и другие коды состояния «ошибка» не считаются ошибками.</span> <span title="">Если вы хотите справиться с этим, вы можете добавить функцию промежуточного программного обеспечения для этого.</span> <span title="">Для получения дополнительной информации см. FAQ.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Для получения дополнительной информации см.</span></span> <a href="http://expressjs.com/en/guide/error-handling.html">Error handling</a> (Express docs).</p> + +<h3 id="Использование_баз_данных"><span class="tlid-translation translation" lang="ru"><span title="">Использование баз данных</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Приложения Express могут использовать любой механизм базы данных, поддерживаемый Node (сам по себе Express не определяет каких-либо дополнительных действий / требований для управления базой данных).</span> <span title="">Есть много вариантов, включая PostgreSQL, MySQL, Redis, SQLite, MongoDB и т. Д.</span><br> + <br> + <span title="">Чтобы использовать их, вы должны сначала установить драйвер базы данных, используя NPM.</span> <span title="">Например, чтобы установить драйвер для популярной NoSQL MongoDB, вы должны использовать команду:</span></span></p> + +<pre class="brush: bash notranslate"><code>$ npm install mongodb +</code></pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Сама база данных может быть установлена локально или на облачном сервере.</span> <span title="">В вашем экспресс-коде вам требуется драйвер, подключиться к базе данных, а затем выполнить операции создания, чтения, обновления и удаления (CRUD).</span> <span title="">Пример ниже (из документации Express) показывает, как вы можете найти записи «млекопитающих», используя MongoDB.</span></span></p> + +<pre class="brush: js notranslate">var MongoClient = require('mongodb').MongoClient; + +MongoClient.connect('mongodb://localhost:27017/animals', function(err, db) { + if (err) throw err; + + db.collection('mammals').find().toArray(function (err, result) { + if (err) throw err; + + console.log(result); + }); +});</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Другим популярным подходом является косвенный доступ к вашей базе данных с помощью Object Relational Mapper («ORM»).</span> <span title="">При таком подходе вы определяете свои данные как «объекты» или «модели», и ORM отображает их в базовый формат базы данных.</span> <span title="">Этот подход имеет то преимущество, что как разработчик вы можете продолжать думать с точки зрения объектов JavaScript, а не семантики базы данных, и что есть очевидное место для выполнения проверки и проверки входящих данных.</span> <span title="">Подробнее о базах данных мы поговорим в следующей статье.</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Для получения дополнительной информации см.</span></span> <a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (Express docs).</p> + +<h3 id="Рендеринг_данных_просмотров"><span class="tlid-translation translation" lang="ru"><span title="">Рендеринг данных (просмотров)</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Механизмы шаблонов (в Express называемые «механизмами просмотра») позволяют указывать структуру выходного документа в шаблоне, используя заполнители для данных, которые будут заполняться при создании страницы.</span> <span title="">Шаблоны часто используются для создания HTML, но могут также создавать другие типы документов.</span> <span title="">В Express есть поддержка ряда шаблонных движков, и здесь есть полезное сравнение более популярных движков: Сравнение шаблонизаторов JavaScript: Jade, Mustache, Dust и More.</span><br> + <br> + <span title="">В своем коде настроек приложения вы задаете механизм шаблонов для использования и место, где Express должен искать шаблоны, используя настройки «views» и «engine», как показано ниже (вам также нужно будет установить пакет, содержащий вашу библиотеку шаблонов).</span> <span title="">!</span><span title="">)</span></span></p> + +<pre class="brush: js notranslate">var express = require('express'); +var app = express(); + +// Set directory to contain the templates ('views') +app.set('views', path.join(__dirname, 'views')); + +// Set view engine to use, in this case 'some_template_engine_name' +app.set('view engine', 'some_template_engine_name'); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Внешний вид шаблона будет зависеть от того, какой движок вы используете.</span> <span title="">Предполагая, что у вас есть файл шаблона с именем «index. <Template_extension>», который содержит заполнители для переменных данных с именами «title» и «message», вы должны вызвать Response.render () в функции обработчика маршрута для создания и отправки ответа HTML.</span> <span title="">:</span></span></p> + +<pre class="brush: js notranslate">app.get('/', function(req, res) { + res.render('index', { title: 'About dogs', message: 'Dogs rock!' }); +});</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Для получения дополнительной информации см.</span></span> <a href="http://expressjs.com/en/guide/using-template-engines.html">Using template engines with Express</a> (Express docs).</p> + +<h3 id="Файловая_структура"><span class="tlid-translation translation" lang="ru"><span title="">Файловая структура</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Express не делает никаких предположений относительно структуры или компонентов, которые вы используете.</span> <span title="">Маршруты, представления, статические файлы и другая логика конкретного приложения могут находиться в любом количестве файлов с любой структурой каталогов.</span> <span title="">Хотя вполне возможно иметь все приложения Express в одном файле, обычно имеет смысл разделить ваше приложение на файлы на основе функций (например, управление учетными записями, блоги, доски обсуждений) и проблемной области архитектуры (например, модель, представление или контроллер, если</span> <span title="">вы случайно используете</span></span> <a href="/en-US/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">MVC architecture</a>).</p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">В более поздней теме мы будем использовать Express Application Generator, который создает модульный каркас приложения, который мы можем легко расширить для создания веб-приложений.</span></span></p> + +<ul> +</ul> + +<h2 id="Резюме">Резюме</h2> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Поздравляем, вы завершили первый шаг в своем путешествии Express / Node!</span> <span title="">Теперь вы должны понимать основные преимущества Express и Node, а также примерно то, как могут выглядеть основные части приложения Express (маршруты, промежуточное ПО, обработка ошибок и код шаблона).</span> <span title="">Вы также должны понимать, что с Express, который является непонятным фреймворком, то, как вы собираете эти части вместе, и библиотеки, которые вы используете, в значительной степени зависит от вас!</span><br> + <br> + <span title="">Конечно, Express - это очень легкая платформа для веб-приложений, поэтому большая часть ее преимуществ и возможностей обеспечивается сторонними библиотеками и функциями.</span> <span title="">Мы рассмотрим это более подробно в следующих статьях.</span> <span title="">В нашей следующей статье мы рассмотрим настройку среды разработки Node, чтобы вы могли увидеть некоторый код Express в действии.</span></span></p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://nodejs.org/api/modules.html#modules_modules">Modules</a> (Node API docs)</li> + <li><a href="https://expressjs.com/">Express</a> (home page)</li> + <li><a href="http://expressjs.com/en/starter/basic-routing.html">Basic routing</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/routing.html">Routing guide</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/using-template-engines.html">Using template engines with Express</a> (Express docs)</li> + <li><a href="https://expressjs.com/en/guide/using-middleware.html">Using middleware</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/writing-middleware.html">Writing middleware for use in Express apps</a> (Express docs)</li> + <li><a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (Express docs)</li> + <li><a href="Serving static files in Express">Serving static files in Express</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/error-handling.html">Error handling</a> (Express docs)</li> +</ul> + +<div>{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}</div> + +<div id="gtx-anchor" style="position: absolute; left: 20px; top: 6224px; width: 616.578px; height: 17px;"></div> + +<div class="jfk-bubble gtx-bubble"> +<div class="jfk-bubble-content-id" id="bubble-22"> +<div id="gtx-host" style="max-width: 400px;"></div> +</div> + +<div class="jfk-bubble-closebtn-id jfk-bubble-closebtn"></div> + +<div class="jfk-bubble-arrow-id jfk-bubble-arrow jfk-bubble-arrowup" style="left: 288.5px;"> +<div class="jfk-bubble-arrowimplbefore"></div> + +<div class="jfk-bubble-arrowimplafter"></div> +</div> +</div> diff --git a/files/ru/learn/server-side/express_nodejs/mongoose/index.html b/files/ru/learn/server-side/express_nodejs/mongoose/index.html new file mode 100644 index 0000000000..18cdb9922a --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/mongoose/index.html @@ -0,0 +1,800 @@ +--- +title: 'Учебник Express часть 3: Использование базы данных (с помощью Mongoose)' +slug: Learn/Server-side/Express_Nodejs/mongoose +translation_of: Learn/Server-side/Express_Nodejs/mongoose +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">В этой статье дается краткое введение в базы данных, и методика их использования в приложнениях Node/Express. Затем мы покажем, как можно использовать <a href="http://mongoosejs.com/">Mongoose</a> для доступа к базе данных веб-сайта <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a>. Мы объясним, как объявляются схемы и модели объектов, укажем основные типы полей, и методику базовой валидации. В статье также кратко показаны основные методы доступа к данным модели.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Предварительные сведения:</th> + <td><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Уметь спроектировать и создать свои модели, используя Mongoose.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>Сотрудники библиотеки будут использовать сайт Local Library для хранения информации о книгах и абонентах, а абоненты библиотеки будут использовать его для просмотра и поиска книг, для получения информации о доступных копиях, для резервирования или одалживния книг. Чтобы эффективно хранить и извлекать информацию, мы будем хранить ее в базе данных.</p> + +<p>Express-приложения могут использовать различные базы данных, и есть несколько подходов, которые можно использовать для выполнения операций <strong>C</strong>reate, <strong>R</strong>ead, <strong>U</strong>pdate and <strong>D</strong>elete (CRUD) (создать, прочесть, обновить, удалить). В руководстве дан краткий обзор некоторых доступных опций, и детально рассмотрены некоторые механизмы работы.</p> + +<h3 id="Какие_базы_данных_можно_использовать">Какие базы данных можно использовать?</h3> + +<p><em>Express-</em>приложение может использовать любые базы данных, поддерживаемые <em>Node</em> (сам по себе Express не определяет каких-либо конкретных дополнительных свойств и требований для управления базами данных). Есть <a href="https://expressjs.com/en/guide/database-integration.html">много популярных </a>вариантов -- PostgreSQL, MySQL, Redis, SQLite, и MongoDB.</p> + +<p>При выборе базы данных следует учитывать такие факторы как время разработки, время обучения, простота репликации и копирования, расходы, поддержка сообщества и т. д. Хотя нет единственной "лучшей" базы данных, почти любое из популярных решений будет приемлемым для сайта малого и среднего размера, такого как наша Local Library.</p> + +<p>Более подробно о вариантах смотрите в: <a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (Express docs).</p> + +<h3 id="Каков_наилучший_способ_взаимодействия_с_базой_данных">Каков наилучший способ взаимодействия с базой данных?</h3> + +<p>Существует два подхода при работе с базой данных:</p> + +<ul> + <li>Использование родного языка запросов баз данных (т.е. SQL)</li> + <li>Использование объектной модели данных (ODM) или объектно-реляционной модели (ORM). ODM / ORM представлют данные веб-сайта как объекты JavaScript, которые затем отображаются на поддерживающую базу данных. Некоторые ORM привязаны к определенной базе данных, тогда как другие не зависят от конкретной базы данных.</li> +</ul> + +<p>Наилучшую производительность можно получить с помощью SQL или другого языка запросов, поддерживаемого базой данных. Объектные модели (ODM) часто медленнее, потому что требуют перевода объектов в формат базы данных, при этом не обязательно будут использованы наиболее эффективные запросы к базе данных (особенно, если ODM предназначена для различных баз данных и должна идти на большие компромиссы в смысле поддержки тех или иных функций базы данных).</p> + +<p>Преимущество применения ORM состоит в том, что программисты могут сосредоточиться на объектах JavaScript, а не на семантике базы данных — особенно, если требуется работать с разными базами данных (на одном или разных веб-сайтах). Они также дают очевидное место для валидации и проверки данных.</p> + +<div class="note"> +<p>Совет: Применение ODM / ORMs часто приводит к снижению затрат на разработку и обслуживание! Если Вы не очень хорошо знакомы с родным языком запросов или если производительность имеет первостепенное значение, следует серьезно рассмотреть возможность применения ODM.</p> +</div> + +<h3 id="Какую_модель_ORMODM_следует_использовать">Какую модель ORM/ODM следует использовать?</h3> + +<p>Есть много ODM/ORM доступных решений на сайте менеджера пакетов NPM (проверьте теги по подгруппе <a href="https://www.npmjs.com/browse/keyword/odm">odm</a> и <a href="https://www.npmjs.com/browse/keyword/orm">orm</a>).</p> + +<p>Популярные решения на момент написания статьи:</p> + +<ul> + <li><a href="https://www.npmjs.com/package/mongoose">Mongoose</a>: -- это средство моделирование обьектов базы данных <a href="https://www.mongodb.org/">MongoDB</a>, предназначенное для асинхронной работы.</li> + <li><a href="https://www.npmjs.com/package/waterline">Waterline</a>: ORM фреймворка <a href="http://sailsjs.com/">Sails</a> (основан на Express) . Она предоставляет единый API для доступа к множеству баз данных, в том числе Redis, mySQL, LDAP, MongoDB, и Postgres.</li> + <li><a href="https://www.npmjs.com/package/bookshelf">Bookshelf</a>: поддерживает как promise- так и традиционные callback- интерфейсы, поддержка транзакций, eager/nested-eager relation loading, полиморфные ассоциации, и поддержка, один к одному, один ко многим, и многие ко многим. Работает с PostgreSQL, MySQL, и SQLite3.</li> + <li><a href="https://www.npmjs.com/package/objection">Objection</a>: Делает настолько легким, насколько возможно, использование всей мощи SQL и движка базы данных ( поддерживает SQLite3, Postgres, и MySQL).</li> + <li><a href="https://www.npmjs.com/package/sequelize">Sequelize</a>: Основанная на промисах ORM для Node.js и <a href="https://ru.wikipedia.org/wiki/Io.js">io.js</a>. Поддерживает диалекты PostgreSQL, MySQL, MariaDB, SQLite и MSSQL, обладает надежной поддержкой транзакций, отношений, чтения копий и т.д.</li> + <li> + <p><a href="https://node-orm.readthedocs.io/en/latest/"><font color="#3d7e9a"><font face="Arial, x-locale-body, sans-serif"><font size="3">Node ORM2</font></font></font></a><font color="#333333"><font face="Arial, x-locale-body, sans-serif"><font size="3"> -- это OR менеджер для NodeJS. Поддерживает MySQL, SQLite и Progress, помогает работать с БД, используя объектный подход.</font></font></font></p> + </li> + <li> + <p><a href="http://1602.github.io/jugglingdb/"><font color="#3d7e9a"><font face="Arial, x-locale-body, sans-serif"><font size="3">JugglingDB</font></font></font></a><font color="#333333"><font face="Arial, x-locale-body, sans-serif"><font size="3"> -- это кросс-ДБ ORM для NodeJS, обеспечивающая общий интерфейс для доступа к наиболее популярным форматам БД. Поддерживает MySQL, SQLite3, Postgres, MongoDB, Redis и хранение данных в памяти js (собственный движок, только для тестирования).</font></font></font></p> + </li> +</ul> + +<p>Как правило, при выборе решения следует учитывать как предоставляемые функции, так и "деятельность сообщества" ( загрузки, вклад, отчеты об ошибках, качество документации, и т.д. ) . На момент написания статьи Mongoose являлась очень популярной ORM, и разумно, если вы выбрали MongoDB.</p> + +<h3 id="Применение_Mongoose_и_MongoDb_для_LocalLibrary">Применение Mongoose и MongoDb для LocalLibrary</h3> + +<p><span id="result_box" lang="ru"><span class="alt-edited">В примере LocalLibrary (и до конца раздела) мы будем использовать Mongoose ODM для доступа к данным </span></span><span lang="ru"><span class="alt-edited">нашей библиотеки.</span> <span class="alt-edited">Mongoose является интерфейсом для MongoDB, NoSQL-базы данных с открытым исходным кодом, в которой использована документо-ориентированная модель данных.</span> В <span class="alt-edited">MongoDB </span><span class="alt-edited">«коллекции» и «документы» -- это аналоги «таблиц» и «строк» в реляционных БД</span></span>.</p> + +<p><span id="result_box" lang="ru"><span>Это сочетание ODM и БД весьма популярно в сообществе Node, частично потому, что система хранения документов и запросов очень похожа на JSON и поэтому знакома разработчикам JavaScript</span></span>.</p> + +<div class="note"> +<p><span id="result_box" lang="ru"><span><strong>Совет</strong>: Не обязательно знать MongoDB, чтобы использовать Mongoose, хотя <a href="http://mongoosejs.com/docs/guide.html"> документацию Mongoose</a> легче использовать и понимать, если вы уже знакомы с MongoDB.</span></span></p> +</div> + +<p><span id="result_box" lang="ru"><span>Далее показано, как определить и получить доступ к схеме и моделям Mongoose для примера веб-сайта LocalLibrary.</span></span></p> + +<h2 id="Проектирование_моделей_LocalLibrary">Проектирование моделей LocalLibrary</h2> + +<p> </p> + +<p>Прежде чем начинать писать код моделей, стоит обдумать, какие данные нам нужно хранить, и каковы отношения между разными объектами.</p> + +<p>Мы знаем, что нужно хранить информацию о книгах (название, резюме (краткое описание), автор, жанр, ISBN (Международный стандартный книжный номер) ) и что может быть несколько доступных экземпляров (с уникальными идентификаторами, статусом наличия и т. д.). Может потребоваться хранить больше информации об авторе (не только имя, т.к. могут быть авторы с одинаковыми или похожими именами). Мы хотим иметь возможность сортировать данные по названиям книг, по авторам, по жанрам и категориям.</p> + +<p>При проектировании моделей имеет смысл иметь отдельные модели для каждого «объекта» (группы связанных данных). В этом случае очевидными объектами являются книги, экземпляры книг и авторы.</p> + +<p>Можно также использовать модели для представления параметров списка выбора (например, как выпадающий список вариантов), вместо жесткого кодирования выбора на самом веб-сайте - рекомендуется, когда не все параметры известны или могут быть изменены. Явный кандидат для модели такого типа -- это жанр книги (например, «Научная фантастика», «Французская поэзия» и т.д.),</p> + +<p>Как только мы определились с моделями и полями, следует подумать об отношениях между ними.</p> + +<p>С учетом сказанного, UML-диаграмма связей (см. ниже) показывает модели, которые представлены как прямоугольники. Мы решили, что создадим модели для книги (общие сведения о книге), для экземпляра книги (состояние отдельных физических копий книги, доступных в системе) и для автора. Кроме того, у нас будет модель для жанра, чтобы эти значения можно было создавать динамически. Решено не создавать модель для <code>BookInstance:status</code> — мы пропишем в коде необходимые значения, потому что не ожидаем их изменения. На элементах диаграммы показаны имя модели, имена и типы полей, имена методов и типы их результатов .</p> + +<p>Также показаны отношения между моделями, включая множественные отношения. Числа на линиях связи показывают максимум и минимум моделей, участвующих отношении. Например, линия между <code>Book</code> и <code>Genre</code> показывает, что <code>Book</code> и <code>Genre</code> связаны. Числа на этой линии рядом с моделью <code>Book</code> показывают, что жанр может быть связан с любым количеством книг, а числа на другом конце линии рядом с <code>Genre</code> отмечают, что книга может быть связана с любым количеством жанров.</p> + +<div class="note"> +<p><strong>Заметка</strong>: Как показано в примере<a href="#related_documents">Mongoose primer</a> ниже, часто лучше иметь поле, определяющее отношение между документами (моделями), только в одной модели (обратное отношение можно найти по присвоенному идентификатору <code>_id</code> в другой модели). Ниже мы предпочли задать отношения между Book/Genre и между Book/Author в схеме Book, а отношение между Book/BookInstance -- в схеме BookInstance. Этот выбор в некотором смысле был произвольным -- таким же хорошим мог бы быть выбор другого поля в другой схеме.</p> +</div> + +<p><img alt="Mongoose Library Model with correct cardinality" src="https://mdn.mozillademos.org/files/15645/Library%20Website%20-%20Mongoose_Express.png" style="height: 620px; width: 737px;"></p> + +<div class="note"> +<p><strong>Заметка</strong>: В следующем разделе дан базовый пример, в котором объясняется, как задавать и как использовать модели. При чтении обратите внимание, как будут создаваться модели, приведенные на диагарамме.</p> +</div> + +<h2 id="Mongoose_Справочник">Mongoose Справочник</h2> + +<p>В этом разделе кратко описано как подключиться к базе MongoDB с помощью Mongooseе, как определить схемы и модели, как сформировать основные запросы.</p> + +<div class="note"> +<p><strong> Примечание: </strong>На этот пример значительно повлияли документы <a href="https://www.npmjs.com/package/mongoose">Mongoose <font color="#3d7e9a"><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><font size="3">quick start</font></font></font></a> на npm и <a href="http://mongoosejs.com/docs/guide.html">официальная документация.</a></p> +</div> + +<h3 id="Установка_Mongoose_и_MongoDB">Установка Mongoose и MongoDB</h3> + +<p>Mongoose устанавливается в ваш проект (<strong>package.json</strong>) как и другие зависимости - при помощи NPM. Команда установки (выполняется из каталога проекта): </p> + +<pre class="brush: bash"><code>npm install mongoose</code> +</pre> + +<p>Установка <em>Mongoose </em>добавит все зависимости, включая драйвер MongoDB, но не установит саму базу данных. При желании установить сервер MongoDB локально, можно <a href="https://www.mongodb.com/download-center">скачать установочный файл здесь</a> для своей операционной системы и установить его. Также можно использовать облако MongoDB.</p> + +<div class="note"> +<p><strong>Примечание:</strong> В примере для хранения базы данных мы используем облачный сервис <a href="https://mlab.com/plans/pricing/">sandbox tier</a> ("песочницу"). This is suitable for development, and makes sense for the tutorial because it makes "installation" operating system independent (database-as-a-service is also one approach you might well use for your production database).</p> +</div> + +<h3 id="Подключенние_к_MongoDB">Подключенние к MongoDB</h3> + +<p><em>Mongoose </em>требует подключение к MongoDB. Вы можете использовать require() и подключится к локальной БД при помощи <code>mongoose.connect(),</code> как показано ниже.</p> + +<pre class="brush: js">// Импортировать модуль mongoose +var mongoose = require('mongoose'); + +// Установим подключение по умолчанию +var mongoDB = 'mongodb://127.0.0.1/my_database'; +mongoose.connect(mongoDB); +// Позволим Mongoose использовать глобальную библиотеку промисов +mongoose.Promise = global.Promise; +// Получение подключения по умолчанию +var db = mongoose.connection; + +// Привязать подключение к событию ошибки (получать сообщения об ошибках подключения) +db.on('error', console.error.bind(console, 'MongoDB connection error:')); +</pre> + +<p>При помощи <code>mongoose.connection</code> можно получить стандартный объект <code>Connection</code>. После подключения в экземпляре <code>Connection</code> возникает событие open (открыт).</p> + +<div class="note"> +<p><strong>Tip:</strong> Если необходимо создать дополнительные подключения, можно использовать <code>mongoose.createConnection()</code>. При этом будут применены те же БД URI (хост, БД, порт, опции и т.д.), что и в <code>connect()</code> и будет возвращен объект <code>Connection</code>.</p> +</div> + +<h3 id="Определение_и_создание_моделей">Определение и создание моделей</h3> + +<p>Модели можно создать при помощи интерфейса <code>Schema</code> . Schema позволяет указать поля, которые будут в каждом документе, значения полей по умолчанию и требования по валидации. Кроме того, можно задать статические методы и методы-хелперы (от help), облегчающие работу с вашими типами данных, а также задать виртуальные свойства, которые можно использовать как и обычные поля, но без влияния на данные в самой базе данных.</p> + +<p>Схемы "компилируются " в окончательную модель методом <code>mongoose.model()</code>. После создания модели ее можно использовать для поиска, создания, обновления и удаления объектов данного типа.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Каждой модели соответствует <em>коллекция</em> <em>документов</em> в ДБ MongoDB. Документы будут содержать поля тех типов, которые заданы в модели <code>Schema</code>.</p> +</div> + +<h4 id="Определение_схем_данных">Определение схем данных</h4> + +<p>Код ниже показывает, как можно задать простую схему. Сначала при помощи <code>require()</code> создается объект mongoose, затем конструктор Schema создает новый экземпляр схемы, при этом различные поля задаются как параметры конструктора.</p> + +<pre class="brush: js">//Требуется Mongoose +var mongoose = require('mongoose'); + +//Определяем схему +var Schema = mongoose.Schema; + +var SomeModelSchema = new Schema({ + a_string: String, + a_date: Date +}); +</pre> + +<p>В примере созданы два поля, типа String и типа Date. В следующем разделе будут примеры полей других типов, их валидации и примеры других методов.</p> + +<h4 id="Создание_модели">Создание модели</h4> + +<p>Модели создаются из схем методом <code>mongoose.model()</code>:</p> + +<pre class="brush: js">// Определяем схему +var Schema = mongoose.Schema; + +var SomeModelSchema = new Schema({ + a_string: String, + a_date: Date +}); + +<strong>// Компилируем модель из схемы +var SomeModel = mongoose.model('SomeModel', SomeModelSchema );</strong></pre> + +<p>Первый аргумент - уникальное имя создаваемой для модели коллекции(Mongoose создаст коллекцию для модели <em>SomeModel</em>), второй аргумент - схема, которая используется для создания модели.</p> + +<div class="note"> +<p><strong>Заметка:</strong> После создания классов модели они могут применяться для создания, обновления или удаления записей в базе, для выполнения запросов по всем записям или по их подмножествам. Как это делать, будет показано в разделе <a href="#Using_models">Использование моделей</a>, и когда будут создаваться представления.</p> +</div> + +<h4 id="Типы_схемы_(поля)">Типы схемы (поля)</h4> + +<p>Схема может содержать любое количество полей, причем каждое поле будет полем документа, хранимого в БД <em>MongoDB</em>. Схема-пример содержит определения многих широко используемых типов полей.</p> + +<pre class="brush: js">var schema = new Schema( +{ + name: <strong>String</strong>, + binary: <strong>Buffer</strong>, + living: <strong>Boolean</strong>, + updated: { type: <strong>Date</strong>, default: Date.now }, + age: { type: <strong>Number</strong>, min: 18, max: 65, required: true }, + mixed: <strong>Schema.Types.Mixed</strong>, + _someId: <strong>Schema.Types.ObjectId</strong>, + array: <strong>[]</strong>, + ofString: [<strong>String</strong>], // You can also have an array of each of the other types too. + nested: { stuff: { type: <strong>String</strong>, lowercase: true, trim: true } } +})</pre> + +<p>Большинство типов в <a href="http://mongoosejs.com/docs/schematypes.html">SchemaTypes</a> (указаны после “type:” или после имен полей) достаточно очевидны. Исключения:</p> + +<ul> + <li><code>ObjectId</code>: Представляет отдельный экземпляр модели в БД. Например, book может ссылаться на объект- автора. Поле будет содержать уникальный идентификатор (<code>_id</code>) отдельного объекта. При необходимости использования этой информации применяют метод <code>populate()</code>.</li> + <li><a href="http://mongoosejs.com/docs/schematypes.html#mixed">Mixed</a>: Произвольный тип в схеме.</li> + <li><font face="Consolas, Liberation Mono, Courier, monospace">[]</font>: Массив элементов. В таких моделях можно выполнять JavaScript-операции для массивов (push, pop, unshift, etc.). Выше показан пример массивы объектов неопределенного типа и массив строк, но можно использовать массив объектов любого типа.</li> +</ul> + +<p>Код содержит также два способа объявления полей:</p> + +<ul> + <li><em>Имя</em> и <em>тип</em>поля как пара "ключ-значение" (поля <code>name</code>, <code>binary и</code> <code>living</code>).</li> + <li><em>Имя</em> поля, после которого указывается объект, определяющий <em>тип</em> и другие возможности поля, такие как: + <ul> + <li>значения по умолчанию.</li> + <li>встроенные валидаторы (например, значения max и min) и функции-валидаторы пользователя.</li> + <li>Является ли поле обязательным</li> + <li>Должны ли строковые поля автоматически преобразовываться в нижний или верхний регистр, удалять ли ведущие и хвостовые пробелы (<code>пример:</code> <code>{ type: <strong>String</strong>, lowercase: true, trim: true }</code>)</li> + </ul> + </li> +</ul> + +<p>Дополнительная информация - в <a href="http://mongoosejs.com/docs/schematypes.html">SchemaTypes</a> (документация Mongoose).</p> + +<h4 id="Валидация_(проверка_допустимости)">Валидация (проверка допустимости)</h4> + +<p>Mongoose предусматривает встроенные валидаторы, валидаторы пользователя, синхронные и асинхронные валидаторы. Во всех случаях можно задать допустимые диапазоны или значения, а также сообщения об ошибках при нарушении условий валидации.</p> + +<p>Встроенные валидаторы включают:</p> + +<ul> + <li>Все <a href="http://mongoosejs.com/docs/schematypes.html">SchemaTypes</a> имеют встроенный валидатор <a href="http://mongoosejs.com/docs/api.html#schematype_SchemaType-required">required</a>, который определяет, должно ли поле быть заданным перед сохранением документа.</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema-number-js">Числа</a> имеют валидаторы <a href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-min">min</a> и <a href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-max">max</a>.</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema-string-js">Строки</a> имеют: + <ul> + <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-enum">enum</a> (перечисления): задают множество допустимых для поля значений.</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-match">match</a> (соответствия)): задают регулярное выражение, которому должна соответствовать строка.</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-maxlength">maxlength</a>, <a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-minlength">minlength</a> -максимальная и минимальная длина строки.</li> + </ul> + </li> +</ul> + +<p>Пример ниже (с небольшими изменениями из документации Mongoose) показывает, как задать некоторые валидаторы и сообщения об ошибках:</p> + +<pre class="brush: js"><code> + var breakfastSchema = new Schema({ + eggs: { + type: Number, + min: [6, 'Too few eggs'], + max: 12 + required: [true, 'Why no eggs?'] + }, + drink: { + type: String, + enum: ['Coffee', 'Tea', 'Water',] + } + }); +</code></pre> + +<p>Подробная информация по валидации полей - в разделе <a href="http://mongoosejs.com/docs/validation.html">Validation</a> (документация Mongoose).</p> + +<h4 id="Виртуальные_свойства">Виртуальные свойства</h4> + +<p>Виртуальные свойства - это свойства документа, которые можно читать (get) и задавать (set), но которые не хранятся в MongoDB. Методы "геттеры" полезны для форматирования и соединения полей, а "сеттеры" применяют для декомпозиции отдельных значений на несколько частей перед сохранением в БД. Пример из документации собирает (и разбирает) виртуальное свойство "полное имя" из полей "имя" и "фамилия", что удобнее, чем конструировать полное имя каждый раз, когда оно используется в шаблоне.</p> + +<div class="note"> +<p><strong>Замечание:</strong> В библиотеке виртуальное свойство будет применено для определения уникального URL каждой записи в модели по пути и по значению <code>_id</code> записи.</p> +</div> + +<p>Подробная информация - в разделе <a href="http://mongoosejs.com/docs/guide.html#virtuals">Virtuals</a> (документация Mongoose).</p> + +<h4 id="Методы_и_помощники_запросов">Методы и помощники запросов</h4> + +<p>В схеме можно также задать методы экземпляра (<a href="http://mongoosejs.com/docs/guide.html#methods">instance methods</a>), статические (<a href="http://mongoosejs.com/docs/guide.html#statics">static</a>) методы и <a href="http://mongoosejs.com/docs/guide.html#query-helpers">помощники запросов</a>. Статические методы и методы экземпляра аналогичны, но различаются тем, что методы экземпляра связаны с конкретной записью и имеют доступ к текущему объекту. Помощники запросов позволяют расширить <a href="http://mongoosejs.com/docs/queries.html"> API построителя цепочек запросов</a> (например, можно добавить запрос "byName" ("по имени") в дополнение к методам <code>find()</code>, <code>findOne()</code> и <code>findById()</code>).</p> + +<h3 id="Применение_моделей">Применение моделей</h3> + +<p>Подготовленную схему можно использовать для создания моделей. Модель представляет коллекцию документов в базе данных, в которой можно выполнять поиск, тогда как экземпляры модели представляют отдельные документы, которые можно сохранять и извлекать.</p> + +<p>Ниже предлагается краткий обзор. Более подробно смотрите в <a href="http://mongoosejs.com/docs/models.html">Models</a> (документация Mongoose).</p> + +<h4 id="Создание_и_изменение_документов">Создание и изменение документов</h4> + +<p>Чтобы создать запись, следует определить экземпляр модели и вызвать метод <code>save()</code>. В примере ниже SomeModel -- это модель с единственным полем "name", которую мы создадим из нашей схемы.</p> + +<pre class="brush: js"><code>// Создать экземпляр модели SomeModel +var awesome_instance = new </code>SomeModel<code>({ name: 'awesome' }); + +// Сохранить новый экземпляр, передав callback +awesome_instance.save(function (err) { + if (err) return handleError(err); + // сохранили! +}); +</code></pre> + +<p>Создание записей (а также обновления, удаления и запросы) - это асинхронные операции, поэтому следует предусмотреть callback-функцию, которая будет вызвана при завершении операции. В API используется соглашение о первом аргументе, согласно которому первый аргумент callback-функции должен быть значением ошибки (или null). Если API возвращает некоторый результат, он должен быть вторым аргументом.</p> + +<p>Можно использовать метод <code>create()</code> для создании экземпляра модели при его сохранении. Тогда callback-функция вернет ошибку (или null) как первый аргумент и только что созданный экземпляр как второй аргумент.</p> + +<pre class="brush: js">SomeModel<code>.create({ name: 'also_awesome' }, function (err, awesome_instance) { + if (err) return handleError(err); + // сохранили! +});</code></pre> + +<p>Каждая модель ассоциирована с соединением (с соединением по умолчанию, если используется <code>mongoose.model()</code>). Следует создать новое соединение и вызвать для него <code>.model()</code>, чтобы создать документ в другой базе данных.</p> + +<p>Поля в новой записи могут быть получены и изменены с применением dot (точка)-синтаксиса. Для сохранения изменений служат методы <code>save()</code> и <code>update()</code>.</p> + +<pre class="brush: js">// Доступ к полям модели в dot-нотации +console.log(<code>awesome_instance.name</code>); //вывод в консоль '<code>also_awesome</code>' + +// Изменить запись, модифицируя поля, потом вызвать save(). +<code>awesome_instance</code>.name="New cool name"; +<code>awesome_instance.save(function (err) { + if (err) return handleError(err); // сохранили! + });</code> +</pre> + +<h4 id="Поиск_записей">Поиск записей</h4> + +<p>При поиске записей методами запросов, условия поиска следует задавать как документ JSON. Приведенный фрагмент кода (ниже) показывает, как в БД найти имена (<em>name</em>) и возраст (<em>age</em>) всех спортсменов-теннисистов. Соответствие будет определяться по одному полю (sport), но можно добавить критерии поиска, задав, например, регулярное выражение, или удалить все критерии, чтобы получить список всех спортсменов.</p> + +<pre class="brush: js"><code>var Athlete = mongoose.model('Athlete', yourSchema); + +// найти всех теннисистов, выбирать поля 'name' и 'age' +Athlete.find({ 'sport': 'Tennis' }, 'name age', function (err, athletes) { + if (err) return handleError(err); + // 'athletes' содержит список спортсменов, соответствующих критерию. +})</code></pre> + +<p>Если задать callback-функцию так, как показано выше, запрос будет выполнен немедленно. Однако callback-функция будет вызвана только после завершения поиска.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Все callbacks-функции в Mongoose используют образец <code>callback(error, result)</code>. Если при выполнении запроса возникает ошибка, параметр <code>error</code> будет содержать объект error, а <code>result</code> будет null. При успешном запросе параметр <code>error</code> будет null, а <code>result</code> будет содержать результат запроса.</p> +</div> + +<p>Если не задать callback-функцию, API вернет переменную типа <a href="http://mongoosejs.com/docs/api.html#query-js">Query</a>. Можно использовать объект запроса, чтобы создать и выполнить свой запрос (с callback-функцией) позже, при помощи метода <code>exec()</code>.</p> + +<pre class="brush: js"><code>// найти всех теннисистов +var query = Athlete.find({ 'sport': 'Tennis' }); + +// выбрать поля 'name' и 'age' +query.select('name age'); + +// ограничить результат 5 элементами +query.limit(5); + +// сортировать по возрасту +query.sort({ age: -1 }); + +// выполнить запрос позже +query.exec(function (err, athletes) { + if (err) return handleError(err); + // athletes содержит упорядоченный список из 5 теннисистов +})</code></pre> + +<p>Выше условия поиска были заданы в методе <code>find()</code>. Можно также использовать функцию <code>where()</code>, кроме того, можно соединить все части в одном запросе применением оператора dot (.) вместо того, чтобы выполнять их раздельно. Фрагмент кода (см. ниже) выполняет тот же запрос, что и предыдущий фрагмент, но с дополнительным условием для возраста. </p> + +<pre><code>Athlete. + find(). + where('sport').equals('Tennis'). + where('age').gt(17).lt(50). //Дополнительное условие + limit(5). + sort({ age: -1 }). + select('name age'). + exec(callback); // callback - имя нашей callback-функции.</code></pre> + +<p>Метод <a href="http://mongoosejs.com/docs/api.html#query_Query-find">find()</a> находит все записи, удовлетворяющие условию, но часто требуется найти только одну из таких записей. Вот методы для поиска одной записи:</p> + +<ul> + <li><code><a href="http://mongoosejs.com/docs/api.html#model_Model.findById">findById()</a></code>: Находит документ по заданному идентификатору <code>id</code> (каждый документ имеет уникальный идентификатор <code>id</code>).</li> + <li><code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOne">findOne()</a></code>: Находит один документ, удовлетворяющий заданному критерию.</li> + <li><code><a href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndRemove">findByIdAndRemove()</a></code>, <code><a href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate">findByIdAndUpdate()</a></code>, <code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndRemove">findOneAndRemove()</a></code>, <code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate">findOneAndUpdate()</a></code>: Находит один документ по <code>id</code> или по критерию, а затем или обновляет, или удаляет его. Эти методы весьма полезны для обновления или удаления записей.</li> +</ul> + +<div class="note"> +<p><strong>Заметка:</strong> Есть также метод <code><a href="http://mongoosejs.com/docs/api.html#model_Model.count">count()</a></code>, который определяет количество записей, соответствующих условию. Он полезен при выполнении подсчетов без фактического извлечения записей.</p> +</div> + +<p>Запросы полезны и во многих других случаях. Дополнительная информация - в <a href="http://mongoosejs.com/docs/queries.html">Queries</a> (документация Mongoose).</p> + +<h4 id="Работа_со_связанными_документами_—_загрузка">Работа со связанными документами — загрузка</h4> + +<p>Один документ (экземпляр модели) может ссылаться на другой документ при помощи поля <code>ObjectId</code> схемы, или на много других документов, используя массив идентификаторов <code>ObjectIds</code>. Идентификатор соответствующей модели хранится в поле. При необходимости получить действительное содержимое связанного документа, следует использовать в запросе метод <code><a href="http://mongoosejs.com/docs/api.html#query_Query-populate">populate()</a></code>, который заменит идентификатор в запросе действительными данными.</p> + +<p>Например, в следующей схеме определены авторы и рассказы. У каждого автора может быть несколько рассказов, которые представим массивом ссылок of <code>ObjectId</code>. У каждого рассказа может быть только один автор. Ссылка "ref" (выделена жирным) указывает в схеме, какая модель должна быть связана с этим полем.</p> + +<pre class="brush: js"><code>var mongoose = require('mongoose') + , Schema = mongoose.Schema + +var authorSchema = Schema({ + name : String, + stories : [{ type: Schema.Types.ObjectId, <strong>ref</strong>: 'Story' }] +}); + +var storySchema = Schema({ + author : { type: Schema.Types.ObjectId, <strong>ref</strong>: 'Author' }, + title : String +}); + +var Story = mongoose.model('Story', storySchema); +var Author = mongoose.model('Author', authorSchema);</code></pre> + +<p>Можно сохранить ссылки в связанном документе, используя значение идентификатора <code>_id</code>. Ниже создается автор, затем рассказ, и значение идентификатора id автора сохраняется в поле "author" рассказа.</p> + +<pre class="brush: js"><code>var bob = new Author({ name: 'Bob Smith' }); + +bob.save(function (err) { + if (err) return handleError(err); + + //автор Bob создан, создаем рассказ + var story = new Story({ + title: "Bob goes sledding", + author: bob._id // присваиваем полю значение идентификатора Боба. Идентификатор создается по умолчанию! + }); + + story.save(function (err) { + if (err) return handleError(err); + // У Боба теперь есть рассказ! + }); +});</code></pre> + +<p>Теперь документ "story" ссылается на автора по идентификатору документа "author". Для получения информации об авторе применяется метод <code>populate()</code> (показано ниже).</p> + +<pre class="brush: js"><code>Story +.findOne({ title: 'Bob goes sledding' }) +.populate('author') //подменяет идентификатор автора информацией об авторе! +.exec(function (err, story) { + if (err) return handleError(err); + console.log('The author is %s', story.author.name); + // выводит "The author is Bob Smith" +});</code></pre> + +<div class="note"> +<p><strong>Заметка:</strong> Внимательные читатели заметили, что автор добавлен к рассказу, но ничего не сделано, чтобы добавить рассказ к массиву рассказов <code>stories</code> автора. Как же тогда получить список всех рассказов конкретного автора? Один из возможных вариантов - добавить автора в массив рассказов, но при этом пришлось бы хранить данные об авторах и рассказах в двух местах и поддерживать их актуальность.</p> + +<p>Лучше получить <code>_id</code> нашего автора <em>author</em>, и применить <code>find()</code> для поиска этого идентификатора в поле "author" всех рассказов.</p> + +<pre class="brush: js"><code>Story +.find({ author : bob._id }) +.exec(function (err, stories) { + if (err) return handleError(err); + // возвращает все рассказы, у которых идентификатор Боба. +});</code> +</pre> +</div> + +<p>Это почти все, что следует знать для работы со связанными данными <em>в нашем руководстве</em>. Более полную информацию можно найти в <a href="http://mongoosejs.com/docs/populate.html">Population</a> (документация Mongoose).</p> + +<h3 id="Одна_схема_(модель)_-_один_файл">Одна схема (модель) - один файл</h3> + +<p>Можно использовать любую структуру файлов при создании схем и моделей, однако мы настоятельно рекомендуем определять каждую схему модели в отдельном модуле (файле), экспортируя метод для создания модели. Пример приведен ниже:</p> + +<pre class="brush: js"><code>// Файл: ./models/somemodel.js + +//Требуется Mongoose +var mongoose = require('mongoose'); + +//Определяем схему +var Schema = mongoose.Schema; + +var SomeModelSchema = new Schema({ + a_string : String, + a_date : Date, +}); + +<strong>//экспортируется функция для содания класса модели "SomeModel" +module.exports = mongoose.model('SomeModel', SomeModelSchema );</strong></code></pre> + +<p>You can then require and use the model immediately in other files. Below we show how you might use it to get all instances of the model.</p> + +<pre class="brush: js"><code>//Создаем модель SomeModel просто вызовом модуля из файла +var SomeModel = require('../models/somemodel') + +// Используем объект SomeModel (модель) для поиска всех записей в SomeModel +SomeModel.find(callback_function);</code></pre> + +<h2 id="Установка_базы_данных_MongoDB">Установка базы данных MongoDB</h2> + +<p>Мы уже немного понимаем, что может делать Mongoose и как следует проектировать модели. Теперь самое время начать работу над сайтом <em>LocalLibrary</em>. Самое первое, что мы должны сделать - установить базу данных MongoDb, в которой будут храниться данные нашей библиотеки.</p> + +<p>В этом руководстве мы будем использовать базу данных в "песочнице" ("<a href="https://mlab.com/plans/pricing/">sandbox</a>") - бесплатный облачный сервис, предоставляемый <a href="https://mlab.com/welcome/">mLab</a>. Такая база не очень подходит для промышленных вебсайтов, поскольку не имеет избыточности, но она очень удобна для разработки и прототипирования. Мы используем ее, так как она бесплатна, ее легко установить, и потому что mLab - популярный поставщик <em>базы данных как сервиса, </em>и это может быть разумным выбором для промышленной базы данных (на данный момент другие известные возможности включают <a href="https://www.compose.com/">Compose</a>, <a href="https://scalegrid.io/pricing.html">ScaleGrid</a> и <a href="https://www.mongodb.com/cloud/atlas">MongoDB Atlas</a>).</p> + +<div class="note"> +<p><strong>Заметка:</strong> При желании можно установить БД MongoDb локально, загрузив и установив <a href="https://www.mongodb.com/download-center">подходящие для вашей системы двоичные файлы</a>. В этом случае приводимые ниже инструкции не изменятся, за исключением URL базы данных, который нужно будет задать для установки соединения.</p> +</div> + +<p>Первым делом надо <a href="https://mlab.com/signup/">создать аккаунт</a> на mLab (это бесплатно, требует только основных контактных данных и ознакомления с условиями обслуживания). </p> + +<p>После входа в систему вы увидите главную страницу <a href="https://mlab.com/home">home</a>:</p> + +<ol> + <li>Щелкните <strong>Create New</strong> в разделе <em>MongoDB Deployments</em> для создания новой БД.<img alt="" src="https://mdn.mozillademos.org/files/14446/mLabCreateNewDeployment.png" style="height: 415px; width: 1000px;"></li> + <li>Откроется экран <em>Cloud Provider Selection - раздела провайдера облака</em>.<br> + <img alt="MLab - screen for new deployment" src="https://mdn.mozillademos.org/files/15661/mLab_new_deployment_form_v2.png" style="height: 931px; width: 1297px;"><br> + + <ul> + <li>Выберите план SANDBOX (Free) из раздела Plan Type (тип плана). </li> + <li>Выберите любого провайдера в разделе <em>Cloud Provider (провайдер облака)</em>. Разные провайдеры обслуживают разные регионы (показаны под выбранным типом плана).</li> + <li>Щелкните кнопку <strong>Continue</strong>.</li> + </ul> + </li> + <li>Откроется экран выбора региона <em>Select Region</em>. + <p><img alt="Select new region screen" src="https://mdn.mozillademos.org/files/15662/mLab_new_deployment_select_region_v2.png" style="height: 570px; width: 1293px;"></p> + + <ul> + <li> + <p>Выберите ближайщий к Вам регион и щелкните кнопку <strong>Continue</strong>.</p> + </li> + </ul> + </li> + <li> + <p>Откроется экран <em>Final Details </em>для ввода названия БД.</p> + + <ul> + <li> + <p>Введите имя новой базы - <code>local_library</code> и нажмите <strong>Continue</strong>.</p> + </li> + </ul> + </li> + <li> + <p>Откроется экран подтверждения заказа <em>Order Confirmation</em>.<br> + <img alt="Order confirmation screen" src="https://mdn.mozillademos.org/files/15664/mLab_new_deployment_order_confirmation.png" style="height: 687px; width: 1290px;"></p> + + <ul> + <li> + <p>Щелкните <strong>Submit Order</strong> (подтвердить заказ), чтобы создать БД.</p> + </li> + </ul> + </li> + <li> + <p>Вы вернетесь на главный (home) экран. Щелкните по вновь созданной базе, чтобы открыть экран с детальной информацией. Как видно, в БД нет коллекций (данных).<br> + <img alt="mLab - Database details screen" src="https://mdn.mozillademos.org/files/15665/mLab_new_deployment_database_details.png" style="height: 700px; width: 1398px;"><br> + <br> + На форме выше обведен URL для соединения с вашей БДthat you need to use to access your database is displayed on the form above (shown for this database circled above). Чтобы его использовать, необходимо создать пользователя БД, который позже введет этот URL.</p> + </li> + <li>Щелкните по вкладке <strong>Users</strong> и выберите кнопку <strong>Add database user </strong>(добавить пользователя БД).</li> + <li>Введите имя пользователя и пароль (дважды), затем нажмите <strong>Create </strong>(создать). Не отмечайте <em>Make read only </em>(только для чтения)!<br> + <img alt="" src="https://mdn.mozillademos.org/files/14454/mLab_database_users.png" style="height: 204px; width: 600px;"></li> +</ol> + +<p>Теперь БД создана, и для доступа к ней есть URL, имя пользователя и пароль. Это должно выглядеть примерно так: <code>mongodb://your_user_namer:your_password@ds119748.mlab.com:19748/local_library</code>.</p> + +<h2 id="Установка_Mongoose">Установка Mongoose</h2> + +<p>Откройте окно команд и перейдите в каталог, в котором создан <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">каркас вебсайта Local Library</a>. Введите команду install, чтобы установить Mongoose (и ее зависимости), а также добавьте ее в файл <strong>package.json</strong>, если вы еще не сделали этого ранее, при чтении примера <a href="#Installing_Mongoose_and_MongoDB">Mongoose Primer</a>.</p> + +<pre class="brush: bash">npm install mongoose +</pre> + +<h2 id="Подключение_к_MongoDB">Подключение к MongoDB</h2> + +<p>Откройте <strong>/app.js</strong> (в корне проекта) и скопируйте приведенный ниже текст, в котором объявляется <em>объект приложения</em> <em>Express</em> (после строки <code>var app = express();</code>). Замените строку url БД ('<em>insert_your_database_url_here</em>') тем URL, который представляет вашу БД (т.е. используйте информацию, полученную от<a href="#Setting_up_the_MongoDB_database"> mLab</a>).</p> + +<pre class="brush: js">//Устанавливаем соединение с mongoose +var mongoose = require('mongoose'); +var mongoDB = '<em>insert_your_database_url_here</em>';//замените url!!! +mongoose.connect(mongoDB); +mongoose.Promise = global.Promise; +var db = mongoose.connection; +db.on('error', console.error.bind(console, 'MongoDB connection error:'));</pre> + +<p>Как указано ранее в примере <a href="#Connecting_to_MongoDB">Mongoose primer</a>, этот код задает соединение по умолчанию с привязкой события ошибки error (так что ошибки будут выведены в консоль). </p> + +<h2 id="Определение_схемы_LocalLibrary">Определение схемы LocalLibrary</h2> + +<p>Мы определим отдельный модуль для каждой модели как уже обсуждалось <a href="#One_schemamodel_per_file">выше</a>. Начнем с создания каталога для моделей в корне проекта (<strong>/models</strong>), после чего создадим отдельные файлы для кажой модели:</p> + +<pre>/express-locallibrary-tutorial //the project root + <strong>/models</strong> + <strong>author.js</strong> + <strong>book.js</strong> + <strong>bookinstance.js</strong> + <strong>genre.js</strong> +</pre> + +<h3 id="Модель_автора_Author">Модель автора Author</h3> + +<p>Скопируйте код схемы автора <code>Author</code> (приведен ниже) в файл <strong>./models/author.js</strong> . В схеме определено, что у автора есть обязательные поля имени и фамилии типа <code>String</code> длиной не более 100 символов, и поля типа <code>Date</code> дата рождения и дата смерти.</p> + +<pre class="brush: js">var mongoose = require('mongoose'); + +var Schema = mongoose.Schema; + +var AuthorSchema = new Schema( + { + first_name: {type: String, required: true, max: 100}, + family_name: {type: String, required: true, max: 100}, + date_of_birth: {type: Date}, + date_of_death: {type: Date}, + } +); + +<strong>// Виртуальное свойство для полного имени автора +AuthorSchema +.virtual('name') +.get(function () { + return this.family_name + ', ' + this.first_name; +});</strong> + +// Виртуальное свойство - URL автора +AuthorSchema +.virtual('url') +.get(function () { + return '/catalog/author/' + this._id; +}); + +//Export model +module.exports = mongoose.model('Author', AuthorSchema); + +</pre> + +<p>Мы объявим также в схеме AuthorSchema <a href="#Virtual_properties">виртуальное</a> свойство "url" , которое позволит получить абсолютный URL конкретного экземпляра модели — используем это свойство в шаблонах, если потребуется получить связь с конкретным автором.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Объявить в схеме URL как виртуальные свойства - хорошая идея, т.к. URL отдельного элемента при необходимости изменения требует коррекции только в одном месте.<br> + Сейчас связь при помощи этого URL еще не работает, так как у нас еще нет кода, поддерживающего маршруты для экземпляров модели. Мы построим его в следующей статье!</p> +</div> + +<p>В конце модуля экспортируется модель.</p> + +<h3 id="Модель_книги_Book">Модель книги Book</h3> + +<p>Скопируйте код схемы <code>Book</code> (приведен ниже) в файл <strong>./models/book.js</strong>. Большая часть кода подобна коду для модели автора — объявляется схема с рядом строковых полей, с виртуальным свойством URL для получения URL конкретных книг, затем модель экспортируется.</p> + +<pre class="brush: js">var mongoose = require('mongoose'); + +var Schema = mongoose.Schema; + +var BookSchema = new Schema( + { + title: {type: String, required: true}, + <strong> author: {type: Schema.ObjectId, ref: 'Author', required: true},</strong> + summary: {type: String, required: true}, + isbn: {type: String, required: true}, + <strong> genre: [{type: Schema.ObjectId, ref: 'Genre'}]</strong> + } +); + +// Virtual for book's URL +BookSchema +.virtual('url') +.get(function () { + return '/catalog/book/' + this._id; +}); + +//Export model +module.exports = mongoose.model('Book', BookSchema); +</pre> + +<p>Основное отличие в том, что созданы две ссылки на другие модели:</p> + +<ul> + <li>author - это ссылка на единственный объект модели <code>Author</code> , обязательный элемент.</li> + <li>genre (жанр) - ссылка на массив объектов модели <code>Genre</code>. Эта модель еще не объявлена!</li> +</ul> + +<h3 id="Модель_экземпляра_книги_BookInstance">Модель экземпляра книги BookInstance</h3> + +<p>Наконец, скопируйте код схемы <code>BookInstance</code> (приведен ниже) в файл <strong>./models/bookinstance.js</strong>. Схема <code>BookInstance</code> представляет конкретный экземпляр книги, которую можно одолжить на время, и содержит информацию о доступности экземпляров книги, о дате возврата одолженной книги, о деталях версии или печатного экземпляра.</p> + +<pre class="brush: js">var mongoose = require('mongoose'); + +var Schema = mongoose.Schema; + +var BookInstanceSchema = new Schema( + { + book: { type: Schema.ObjectId, ref: 'Book', required: true }, //ссылка на книгу + imprint: {type: String, required: true}, + status: {type: String, required: true, <strong>enum: ['Available', 'Maintenance', 'Loaned', 'Reserved']</strong>, <strong>default: 'Maintenance'</strong>}, + due_back: {type: Date, <strong>default: Date.now</strong>} + } +); + +// Virtual for bookinstance's URL +BookInstanceSchema +.virtual('url') +.get(function () { + return '/catalog/bookinstance/' + this._id; +}); + +//Export model +module.exports = mongoose.model('BookInstance', BookInstanceSchema);</pre> + +<p>В этой схеме новыми являются опции поля:</p> + +<ul> + <li><code>enum</code>: Позволяет указать допустимые значения строки. В нашем случае используются, чтобы задать статус доступности книги (применение enum (перечисления) означает, что мы ходим предотвратить ошибочное написание и произвольные значения статуса)</li> + <li><code>default</code>: определяет значание статуса по умолчанию (maintenance) при создании экземпляра книги, и дату <code>due_back </code>возврата книги (<code>now,</code> сейчас). Отметьте, как используется функция Date при установке даты!</li> +</ul> + +<p>Все остальное знакомо по предыдущим схемам.</p> + +<h3 id="Модель_жанра_Genre_-_проверьте_себя!">Модель жанра Genre - проверьте себя!</h3> + +<p>Откройте файл <strong>./models/genre.js</strong> и создайте схему для хранения жанра (категории книги, т.е. художественная или научная, романтика или военная история и т.д.).</p> + +<p>Определение будет подобно другим моделям:</p> + +<ul> + <li>В модели должно быть поле <code>name</code> типа <code>String</code> для указания жанра.</li> + <li>Это поле должно быть обязательным, допустимый размер - от 3 до 100 символов.</li> + <li>Объявите виртуальное (<a href="#Virtual_properties">virtual</a>) свойство с именем <code>url</code> для URL жанра.</li> + <li>Экспортируйте модель.</li> +</ul> + +<h2 id="Тестирование_—_создаем_элементы_БД">Тестирование — создаем элементы БД</h2> + +<p>Вот так. У нас теперь есть все модели для создания сайта!</p> + +<p>Для тестирования моделей (и для создания примеров книг и других элементов, которые потребуются в следующих статьях) выполним <em>независимый </em>скрипт, который создаст элементы каждого типа:</p> + +<ol> + <li>Загрузите (или создайте) файл <a href="https://raw.githubusercontent.com/hamishwillee/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a> в каталоге <em>express-locallibrary-tutorial</em> (на том же уровне, что и <code>package.json</code>). + + <div class="note"> + <p><strong>Заметка:</strong> Не обязательно понимать, как работает <a href="https://raw.githubusercontent.com/hamishwillee/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a>; он просто помещает некоторые данные в базу данных.</p> + </div> + </li> + <li>Введите в корне проекта команду для установки модуля <em>async, </em>который потребуется скрипту populatedb.js (роль async обсудим в следующих руководствах) + <pre class="brush: bash">npm install async</pre> + </li> + <li>Запустите скрипт из командной строки, используя node. В качестве параметра укажите URL вашей БД <em>MongoDB</em> (тот самый, которым вы ранее заменили <em>insert_your_database_url_here </em>в<em> </em>файле <code>app.js</code> ): + <pre class="brush: bash">node populatedb <your mongodb url></pre> + </li> + <li>Скрипт должен выполниться до конца, выводя в терминал создаваемые элементы.</li> +</ol> + +<div class="note"> +<p><strong>Совет:</strong> Откройте свою базу на <a href="https://mlab.com/home">Lab</a>. Вы сможете увидеть коллекции Book, Author, Genre, BookInstance (книги, авторы, жанры, экземпляры книг) и просмотреть содержащиеся в них документы.</p> +</div> + +<h2 id="Итог">Итог</h2> + +<p>В этой статье мы познакомились с БД и ОРМ (объектно-реляционными моделями) в системе Node/Express, узнали, как определяются схемы и модели Mongoose. Мы применили эти знания при проектировании и реализации моделей <code>Book</code>, <code>BookInstance</code>, <code>Author</code> и <code>Genre</code> для вебсайта <em>LocalLibrary</em>.</p> + +<p>В конце мы испытали свои модели путем создания ряда элементов (при помощи автономного скрипта). В следующей статье мы рассмотрим создание страниц, на которых будут показаны эти элементы.</p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> Интеграция БД (документация Express)</li> + <li><a href="http://mongoosejs.com/">Mongoose website</a> Вебсайт Mongoose (документация Mongoose)</li> + <li><a href="http://mongoosejs.com/docs/guide.html">Mongoose Guide</a> Справочник Mongoose (документация Mongoose)</li> + <li><a href="http://mongoosejs.com/docs/validation.html">Validation</a> Валидация (документация Mongoose)</li> + <li><a href="http://mongoosejs.com/docs/schematypes.html">Schema Types</a> Типы в схемах (документация Mongoose)</li> + <li><a href="http://mongoosejs.com/docs/models.html">Models</a> Модели (документация Mongoose)</li> + <li><a href="http://mongoosejs.com/docs/queries.html">Queries</a> Запросы (документация Mongoose)</li> + <li><a href="http://mongoosejs.com/docs/populate.html">Population</a> Пополнение БД (документация Mongoose)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> + +<p> </p> diff --git a/files/ru/learn/server-side/express_nodejs/routes/index.html b/files/ru/learn/server-side/express_nodejs/routes/index.html new file mode 100644 index 0000000000..c8610eba1b --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/routes/index.html @@ -0,0 +1,655 @@ +--- +title: 'Учебник Express часть 4: Маршруты и контроллеры' +slug: Learn/Server-side/Express_Nodejs/routes +translation_of: Learn/Server-side/Express_Nodejs/routes +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">В этом уроке мы настроим маршруты (код обработки URL) с "фиктивными" функциями-обработчиками для всех конечных точек ресурса, которые нам понадобятся на веб-сайте <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a>. По завершении мы получим модульную структуру для нашего кода обработки маршрута, который будет расширен реальными функциями-обработчиками в следующих статьях. У нас также будет хорошее понимание того, как создавать модульные маршруты с помощью Express!</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row"> + <table> + <tbody> + <tr> + <th scope="row">Предварительные знания:</th> + </tr> + </tbody> + </table> + </th> + <td>Прочесть <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">введение в Express/Node </a>. Завершить предыдущие уроки (включая <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a>).</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понять, как создать простые маршруты. Настроить конечные точки URL.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>В <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">последней статье </a>мы определили модели <em>Mongoose</em> для взаимодействия с базой данных, и использовали (автономный) скрипт, который создал некоторые исходные записи библиотеки. Теперь можно написать код, чтобы представить эту информацию пользователям. Первое, что нужно сделать, это решить, какие возможности для отображения информации мы хотим иметь на наших страницах, а затем определить соответствующие URL-адреса для получения этих ресурсов. Затем нужно будет создать маршруты (обработчики URL-адресов) и представления (шаблоны) для отображения этих страниц.</p> + +<p>Приведенная ниже диаграмма напоминает об основном потоке данных и об элементах, которые необходимо реализовать при обработке HTTP-запроса/ответа. Кроме представлений и маршрутов на диаграмме показаны "контроллеры" - функции, которые отделяют код для маршрутизации запросов от кода, который фактически обрабатывает запросы.</p> + +<p>Поскольку модели уже созданы, основные элементы, которые следует создать, таковы:</p> + +<ul> + <li>"Маршруты" для перенаправления поддерживаемых запросов (и любой закодированной информации в URL-запросах) соответствующим функциям контроллера.</li> + <li>Контроллеры -функции для получения запрашиваемых данных из моделей, создание HTML страницы, отображающей данные, и возращение их пользователю для просмотра в браузере.</li> + <li>Представления (шаблоны), используемые контроллерами для отрисовки данных.</li> +</ul> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14456/MVC%20Express.png" style="height: 460px; width: 800px;"></p> + +<p>В итоге, у нас должны быть страницы для вывода списков и детальной информации по книгам, жанрам, авторам и экземплярам книг, а также страницы для создания, обновления и удаления записей. Это много для одной статьи. Поэтому большая часть этой статьи будет сосредоточена на настройке наших маршрутов и контроллеров для возврата "фиктивного" контента. Мы расширим методы контроллеров для работы с данными модели в следующих статьях .</p> + +<p>В первом разделе ниже приведен краткие основы того, как использовать промежуточное средство (middleware) Express <a href="http://expressjs.com/en/4x/api.html#router">Router</a>. Эти знания будут использованы в следующих разделах при настройке маршрутов для LocalLibrary.</p> + +<h2 id="Маршруты_-_основы">Маршруты - основы</h2> + +<p>Маршруты - это часть кода Express, связывающая HTTP действия (<code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>DELETE</code>, etc.), URL пути (шаблона), и функцию, которая обрабатывает этот шаблон.</p> + +<p>Есть несколько способов создания маршрутов. В этом уроке мы используем промежуточные запросы <code><a href="http://expressjs.com/en/guide/routing.html#express-router">express.Router</a>,</code> так как они позволяют группировать обработчики маршрутов для определенной части сайта и получать к ним доступ через общий префикс маршрута. Все маршруты, связанные с библиотекой, будут сохранены в модуле "catalog", и если мы добавим маршруты для обработки учетных записей пользователей или других функций, мы сможем сгруппировать их отдельно.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Маршруты приложения Express уже кратко рассматривались в <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Creating_route_handlers">Express Introduction > Creating route handlers</a> (Введение -> Создание обработчиков маршрутов). Применение <em>Router </em>обеспечивает лучшую поддержку модульности (как обсуждается в первой подсекции ниже), а в остальном очень похоже на определение маршрутов непосредственно в объекте приложения <em>Express</em>.</p> +</div> + +<p>В оставшейся части этого раздела представлен обзор того, как Router может быть использован для определения маршрутов.</p> + +<h3 id="Определение_и_использование_отдельных_модулей_маршрутов">Определение и использование отдельных модулей маршрутов</h3> + +<p>Код ниже является реальным примером того, как можно создать модуль маршрута, а затем использовать его в приложении <em>Express</em>.</p> + +<p>Первым делом в модуле <strong>wiki.js </strong>создадим маршруты для wiki . Код сначала импортирует объект приложения Express, использует его для получения объекта <code>Router</code> и затем, применяя метод <code>get(),</code> добавляет к объекту пару маршрутов. В завершение модуль экспортирует объект <code>Router</code> .</p> + +<pre class="brush: js"><code>// wiki.js - Wiki route module. + +var express = require('express'); +var router = express.Router(); + +// Home page route. +router.get('/', function (req, res) { + res.send('Wiki home page'); +}) + +// About page route. +router.get('/about', function (req, res) { + res.send('About this wiki'); +}) + +module.exports = router;</code> + +</pre> + +<div class="note"> +<p><strong>Заметка:</strong> В примере callback-функции обработчиков маршрутов определены непосредственно в функциях роутеров. А в LocalLibrary мы определим эти callback-функции в отдельном модуле контроллера.</p> +</div> + +<p>Чтобы использовать модуль роутера в главном приложении, прежде всего следует выполнить <code>require()</code> модуля маршрута (<strong>wiki.js</strong>). Потом вызовем <code>use()</code> для приложения Express с аргументом, в котором указан URL-путь 'wiki', что добавит Router к пути обработки промежуточного слоя.</p> + +<pre class="brush: js"><code>var wiki = require('./wiki.js'); +// ... +app.use('/wiki', wiki);</code></pre> + +<p>После этого два маршрута, определенные в нашем модуле маршрутов wiki, станут доступны из <code>/wiki/</code> и <code>/wiki/about/</code>.</p> + +<h3 id="Функции_Route">Функции Route</h3> + +<p>В модуле выше определена пара типовых функций маршрута. Маршрут "about" (еще раз показан ниже) определен при помощи метода <code>Router.get()</code>, который отвечает только на HTTP GET-запросы. Первый аргумент метода - URL-путь, а второй - callback-функция, которая будет вызвана, если получен HTTP GET-запрос с указанным путем.</p> + +<pre class="brush: js"><code>router.get('/about', function (req, res) { + res.send('About this wiki'); +})</code> +</pre> + +<p>Эта callback-функция имеет три аргумента takes three arguments (обычно именуемых как указано: <code>req</code>, <code>res</code>, <code>next</code>), которые соответствуют объекту HTTP запроса, ответу HTTP, и <em>следующей</em> <br> + функции в цепочке промежуточных элементов.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Фукции в Router - это промежуточный слой (<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Using_middleware">middleware</a>) are <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Using_middleware">Express </a>, что означает, что они должны или завершить (ответить на) запрос reqили вызвать следующую (<code>next)</code> функцию в цепочке. В нашем случае запрос завершается вызовом <code>send()</code>, поэтому аргумент <code>next</code> не нужен (и поэтому не указан).</p> + +<p>Выше у функции роутера только один callback-аргумент, но можно указать столько таких аргументов, сколько хотите, или указать массив callback-функций. каждая из функций - это элемент в цепочке промежуточного слоя, и они будут вызываться в порядке их добавления в цепочку (если предыдущая функция не завершит запрос).</p> +</div> + +<p>Здесь, когда приходит GET-запрос с путем ('<code>/about'</code>) callback-функция при ответе вызывает <code><a href="https://expressjs.com/en/4x/api.html#res.send">send()</a></code> , возвращая строку "About this wiki". Существует <a href="https://expressjs.com/en/guide/routing.html#response-methods">ряд других методов ответа</a> , завершающих цикл запрос-ответ. Например, можно вызвать <code><a href="https://expressjs.com/en/4x/api.html#res.json">res.json()</a></code> , чтобы послать ответ JSON, или <code><a href="https://expressjs.com/en/4x/api.html#res.sendFile">res.sendFile()</a>,</code> чтобы послать файл. Метод ответа, который будет использован чаще всего при построении нашей библиотеки - это <a href="https://expressjs.com/en/4x/api.html#res.render">render()</a>, создающий, на основе шаблонов и данных, и возвращающий HTML-файлы —мы поговорим об этом подробнее в следующей статье!</p> + +<h3 id="HTTP_глаголы_(действия)">HTTP глаголы (действия)</h3> + +<p>Рассмотренный пример использует метод <code>Router.get()</code> для ответа на HTTP GET- запросы с указанным путем.</p> + +<p>Кроме того, <code>Router</code> обеспечивает также методы маршрутизации для других HTTP глаголов, которые обычно используются точно таким же способом: <code>post()</code>, <code>put()</code>, <code>delete()</code>, <code>options()</code>, <code>trace()</code>, <code>copy()</code>, <code>lock()</code>, <code>mkcol()</code>, <code>move()</code>, <code>purge()</code>, <code>propfind()</code>, <code>proppatch()</code>, <code>unlock()</code>, <code>report()</code>, <code>mkactivity()</code>, <code>checkout()</code>, <code>merge()</code>, <code>m-</code><code>search()</code>, <code>notify()</code>, <code>subscribe()</code>, <code>unsubscribe()</code>, <code>patch()</code>, <code>search()</code>, и <code>connect()</code>.</p> + +<p>Например, код ниже делает то же, что и предыдущий, с путем <code>/about,</code> но отвечает на HTTP POST-запросы.</p> + +<pre class="brush: js"><code>router.post('/about', function (req, res) { + res.send('About this wiki'); +})</code></pre> + +<h3 id="Маршруты_путей">Маршруты путей</h3> + +<p>Маршруты путей определяют конечные точки, в которых могут быть сделаны запросы. В уже рассмотренных примерах это были просто строки, которые использовались точно так, как были записаны: '/', '/about', '/book', '/any-random.path'.</p> + +<p>Маршруты путей могут быть также образцами строк. Образцы строк используют подмножество синтаксиса регулярных выражений для определения <em>образцов</em> конечных точек, для которых должно проверяться соответствие. Подмножество приведено ниже (отметим, что перенос (<code>-</code>) и точка (<code>.</code>) в путях на основе строк понимаются буквально):</p> + +<ul> + <li>? : Конечная точка должна иметь 0 или 1 предыдущий символ. Так, путь маршрута <code>'/ab?cd'</code> соответствует конечным точкам <code>acd</code> или <code>abcd</code> (т.е. b может присутствовать или нет).</li> + <li>+ : Конечная точка должна иметь 1 или более предыдущих символов. Так, путь маршрута <code>'/ab+cd'</code> будет соответствовать конечным точкам <code>abcd</code>, <code>abbcd</code>, <code>abbbcd</code>, и так далее.</li> + <li>* : Конечная точка может содержать произвольную строку там, где находится символ '*'. Так, путь маршрута <code>'ab\*cd'</code> будет соответствовать конечным точкам <code>abcd</code>, <code>abXcd</code>, <code>abSOMErandomTEXTcd</code>, и так далее.</li> + <li>() : Группировка символов для выполнения другой операции. Так, <code>'/ab(cd)?e'</code> выполнит ?-проверку (0 или 1 появление) для группы (cd) — соответствия таковы: <code>abe</code> и <code>abcde</code>.</li> +</ul> + +<p>Пути маршрутов могут быть также <a href="/en-US/docs/Web/JavaScript/Guide/Regular_Expressions">регулярными выражениями</a> JavaScript. Например, путь маршрутов (ниже) будет соответствовать <code>catfish и</code> <code>dogfish</code>, но не <code>catflap</code>, <code>catfishhead</code>, и так далее. Отметим, регулярное выражение как путь использует синтаксис регулярных выражений (это не строка в кавычках, как в предыдущих случаях).</p> + +<pre class="brush: js"><code>app.get(/.*fish$/, function (req, res) { + ... +})</code></pre> + +<div class="note"> +<p><strong>Заметка:</strong> Большинство наших маршрутов для библиотеки будут просто строками, а не образцами строк или регулярными выражениями. Кроме того, будут использоваться параметры маршрутов, что обсуждается в следующем разделе.</p> +</div> + +<h3 id="Параметры_маршрутов">Параметры маршрутов</h3> + +<p>Параметры маршрутов - это <em>именованные сегменты URL</em> , которые используются для выбора значений из указанной позиции URL. Именованные сегменты начинаются двоеточием, после которого следует имя (например, <code>/<strong>:</strong>your_parameter_name/</code>. Выбранные значения сохраняются в объекте <code>req.params,</code> причем имя параметра используется как ключ (т.е. <code>req.params.your_parameter_name</code>).</p> + +<p>Предположим, например, что URL содержит информацию о пользователях и книгах: <code>http://localhost:3000/users/34/books/8989</code>. Можно извлечь эту информацию (см. ниже) в параметры <code>userId</code> и <code>bookId</code> пути:</p> + +<pre><code>app.get('/users/:userId/books/:bookId', function (req, res) { + // доступ к userId через: req.params.userId + // доступ к bookId через: req.params.bookId + res.send(req.params); +}) +</code></pre> + +<p>Имена параметров пути должны состоять из “символов слова” (A-Z, a-z, 0-9, и _).</p> + +<div class="note"> +<p><strong>Заметка:</strong> URL <em>/book/create</em> будет соответствовать маршрутам вида <code>/book/:bookId</code> (и '<code>create</code>' станет значением "bookId"). Будет использован первый маршрут, соответствующий введенному URL, поэтому, если необходимо обрабатывать URL вида <code>/book/create</code> отдельно, обработчик этого маршрута должен быть расположен до маршрута <code>/book/:bookId</code> .</p> +</div> + +<p>Для начала этих сведений достаточно - если потребуется, можно найти дополнительную информацию в документации Express: <a href="http://expressjs.com/en/starter/basic-routing.html">Basic routing</a> (основы маршрутизации) и <a href="http://expressjs.com/en/guide/routing.html">Routing guide</a> (руководство по маршрутизации). В следующем разделе показано, как задать маршруты и контроллеры для нашей библиотеки LocalLibrary.</p> + +<h2 id="Маршруты_необходимые_для_библиотеки_LocalLibrary">Маршруты, необходимые для библиотеки LocalLibrary</h2> + +<p>Те URL, котрые в итоге будут нужны для наших страниц, показаны ниже. Слово <em>object</em> должно быть заменено на имя каждой из наших моделей (book, bookinstance, genre, author), слово <em>objects</em> - множественное число для <em>object, </em>а <em>id</em> - уникальное значение для поля(<code>_id</code>), которое Mongoose создает по умолчанию для каждого экземпляра модели.</p> + +<ul> + <li><code>catalog/</code> — Домашняя страница home/index.</li> + <li><code>catalog/<objects>/</code> — Список всех книг, экземпляров книг, жанров и авторов (т.е. /<code>catalog/books/</code>, /<code>catalog/genres/</code>, и т.д.)</li> + <li><code>catalog/<object>/<em><id></em></code> — Страница с подробностями для отдельной книги, экземпляра книги, жанра или автора с заданным полем идентификатора <code><em>_id</em></code> (т.е. <code>/catalog/book/584493c1f4887f06c0e67d37)</code>.</li> + <li><code>catalog/<object>/create</code> — Форма для создания новой книги, экземпляра книги, жанра или автора (т.е. <code>/catalog/book/create)</code>.</li> + <li><code>catalog/<object>/<em><id></em>/update</code> — Форма для обновления отдельной книги, экземпляра книги, жанра или автора с заданным идентификатором <code><em>_id</em></code> (т.е. <code>/catalog/book/584493c1f4887f06c0e67d37/update)</code>.</li> + <li><code>catalog/<object>/<em><id></em>/delete</code> — Форма для удаления отдельной книги, экземпляра книги, жанра или автора по заданному идентификатору <code><em>_id</em></code> (т.е. <code>/catalog/book/584493c1f4887f06c0e67d37/delete)</code>.</li> +</ul> + +<p>Первая домашняя страница и страницы со списками не кодируют никакой дополнительной информации. Хотя результаты, возвращаемые запросами, будут зависеть от типа модели и от содержимого БД, запросы для получения этой информации всегда будут одинаковы (подобно тому, как код для создания объектов всегда будет одним и тем же). </p> + +<p>В противоположность этому, другие URL используются для работы с определенными экземплярами документов и моделей— индивидуальность элементов кодируется в URL (как <code><em><id></em></code> выше). Параметры путей используются для извлечения информации и передачи ее в обработчик пути (и в следующей статье мы применим этот прием для того, чтобы динамически определять, какую информацию следует получить из БД). By encoding the information in our URL we only need one route for every resource of a particular type (e.g. one route to handle the display of every single book item).</p> + +<div class="note"> +<p><strong>Заметка</strong>: Express позволяет строить URL любым способом, который вам нравится — можно кодировать информацию в теле URL как показано выше или использовать URL <code>GET</code> -запрос с параметрами (например, <code>/book/?id=6</code>). Какой бы подход вы не применяли, URL должны быть ясными, логичными и читаемыми (ознакомьтесь с советами<a href="https://www.w3.org/Provider/Style/URI"> W3C</a>).</p> +</div> + +<p>Далее мы создадим callback-функции обработчиков маршрутов и код маршрутов для всех указанных выше URL.</p> + +<h2 id="Создаем_callback-функции_обработчиков_маршрутов">Создаем callback-функции обработчиков маршрутов</h2> + +<p>Перед определением маршрутов сначала создадим фиктивные (каркасные) callback-функции, которые они будут вызывать. Эти функции будут храниться в отдельных модулях -"контроллерах" для моделей Book, BookInstance, Genre, и Author (можно использовать любую структуру моделей и файлов, но кажется, что выбранная обеспечивает приемлемую модульность нашего проекта).</p> + +<p>Начнем с создания каталога для контроллеров в корне проекта (<strong>/controllers</strong>), а затем создадим отдельные файлы (модули) контроллеров для работы с моделями:</p> + +<pre>/express-locallibrary-tutorial //корень проекта + <strong>/controllers</strong> + <strong>authorController.js</strong> + <strong>bookController.js</strong> + <strong>bookinstanceController.js</strong> + <strong>genreController.js</strong></pre> + +<h3 id="Контроллер_автора">Контроллер автора</h3> + +<p>Скопируем следующий код в файл <strong>/controllers/authorController.js</strong>:</p> + +<pre class="brush: js">var Author = require('../models/author'); + +// Показать список всех авторов. +exports.author_list = function(req, res) { + res.send('NOT IMPLEMENTED: Author list'); +}; + +// Показать подробную страницу для данного автора. +exports.author_detail = function(req, res) { + res.send('NOT IMPLEMENTED: Author detail: ' + req.params.id); +}; + +// Показать форму создания автора по запросу GET. +exports.author_create_get = function(req, res) { + res.send('NOT IMPLEMENTED: Author create GET'); +}; + +// Создать автора по запросу POST. +exports.author_create_post = function(req, res) { + res.send('NOT IMPLEMENTED: Author create POST'); +}; + +// Показать форму удаления автора по запросу GET. +exports.author_delete_get = function(req, res) { + res.send('NOT IMPLEMENTED: Author delete GET'); +}; + +// Удалить автора по запросу POST. +exports.author_delete_post = function(req, res) { + res.send('NOT IMPLEMENTED: Author delete POST'); +}; + +// Показать форму обновления автора по запросу GET. +exports.author_update_get = function(req, res) { + res.send('NOT IMPLEMENTED: Author update GET'); +}; + +// Обновить автора по запросу POST. +exports.author_update_post = function(req, res) { + res.send('NOT IMPLEMENTED: Author update POST'); +}; +</pre> + +<p>В модуле сначала подключается (requires) модель, которая далее будет использована для получения данных и их обновления. Далее экспортируются функции для каждого URL, который мы хотим обрабатывать (операции create-создать, update-обновить и delete-удалить используют формы, следовательно, должны быть дополнительные методы для обработки post-запросов от форм - эти методы обсуждаются далее, в статье "forms article" ("формы")).</p> + +<p>Все функции имеют стандартную форму функций среднего слоя <em>Express </em>, с арнументами для запроса, ответа и следующей <code>(next)</code> функции, которая должна быть вызвана, если метод не завершил цикл запроса (во всех приведенных в коде случаях - завершает!). Методы просто возвращают строку, информирующую о том, что соответствующая страница еще не создана. Если функция контроллера должна получить параметры маршрута, эти параметры будут выведены в строке сообщения (смотри выше <code>req.params.id</code> ).</p> + +<h4 id="BookInstance_controller">BookInstance controller</h4> + +<p>Скопируйте следующий код в файл <strong>/controllers/bookinstanceController.js</strong> (он построен по образцу модуля контроллера для автора <code>Author</code> ):</p> + +<pre class="brush: js">var BookInstance = require('../models/bookinstance'); + +// Display list of all BookInstances. +exports.bookinstance_list = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance list'); +}; + +// Display detail page for a specific BookInstance. +exports.bookinstance_detail = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance detail: ' + req.params.id); +}; + +// Display BookInstance create form on GET. +exports.bookinstance_create_get = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance create GET'); +}; + +// Handle BookInstance create on POST. +exports.bookinstance_create_post = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance create POST'); +}; + +// Display BookInstance delete form on GET. +exports.bookinstance_delete_get = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance delete GET'); +}; + +// Handle BookInstance delete on POST. +exports.bookinstance_delete_post = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance delete POST'); +}; + +// Display BookInstance update form on GET. +exports.bookinstance_update_get = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance update GET'); +}; + +// Handle bookinstance update on POST. +exports.bookinstance_update_post = function(req, res) { + res.send('NOT IMPLEMENTED: BookInstance update POST'); +}; +</pre> + +<h4 id="Контроллер_жанра">Контроллер жанра</h4> + +<p>Скопируйте следующий код в файл <strong>/controllers/genreController.js</strong> (он построен по образцу модулей контроллеров для автора <code>Author</code> и экземпляра книги <code>BookInstance</code>):</p> + +<pre class="brush: js">var Genre = require('../models/genre'); + +// Display list of all Genre. +exports.genre_list = function(req, res) { + res.send('NOT IMPLEMENTED: Genre list'); +}; + +// Display detail page for a specific Genre. +exports.genre_detail = function(req, res) { + res.send('NOT IMPLEMENTED: Genre detail: ' + req.params.id); +}; + +// Display Genre create form on GET. +exports.genre_create_get = function(req, res) { + res.send('NOT IMPLEMENTED: Genre create GET'); +}; + +// Handle Genre create on POST. +exports.genre_create_post = function(req, res) { + res.send('NOT IMPLEMENTED: Genre create POST'); +}; + +// Display Genre delete form on GET. +exports.genre_delete_get = function(req, res) { + res.send('NOT IMPLEMENTED: Genre delete GET'); +}; + +// Handle Genre delete on POST. +exports.genre_delete_post = function(req, res) { + res.send('NOT IMPLEMENTED: Genre delete POST'); +}; + +// Display Genre update form on GET. +exports.genre_update_get = function(req, res) { + res.send('NOT IMPLEMENTED: Genre update GET'); +}; + +// Handle Genre update on POST. +exports.genre_update_post = function(req, res) { + res.send('NOT IMPLEMENTED: Genre update POST'); +}; +</pre> + +<h4 id="Контроллер_книги">Контроллер книги</h4> + +<p>Скопируйте следующий код в файл <strong>/controllers/bookController.js</strong>. Он построен по образцу других модулей контроллеров, но еще содержит функцию <code>index()</code> для вывода странички с приветствием:</p> + +<pre class="brush: js">var Book = require('../models/book'); + +<strong>exports.index = function(req, res) { + res.send('NOT IMPLEMENTED: Site Home Page'); +};</strong> + +// Display list of all books. +exports.book_list = function(req, res) { + res.send('NOT IMPLEMENTED: Book list'); +}; + +// Display detail page for a specific book. +exports.book_detail = function(req, res) { + res.send('NOT IMPLEMENTED: Book detail: ' + req.params.id); +}; + +// Display book create form on GET. +exports.book_create_get = function(req, res) { + res.send('NOT IMPLEMENTED: Book create GET'); +}; + +// Handle book create on POST. +exports.book_create_post = function(req, res) { + res.send('NOT IMPLEMENTED: Book create POST'); +}; + +// Display book delete form on GET. +exports.book_delete_get = function(req, res) { + res.send('NOT IMPLEMENTED: Book delete GET'); +}; + +// Handle book delete on POST. +exports.book_delete_post = function(req, res) { + res.send('NOT IMPLEMENTED: Book delete POST'); +}; + +// Display book update form on GET. +exports.book_update_get = function(req, res) { + res.send('NOT IMPLEMENTED: Book update GET'); +}; + +// Handle book update on POST. +exports.book_update_post = function(req, res) { + res.send('NOT IMPLEMENTED: Book update POST'); +}; +</pre> + +<h2 id="Создание_модуля_для_маршрута_catalog">Создание модуля для маршрута catalog</h2> + +<p>Далее мы создадим маршруты для всех URL, необходимых веб-сайту<a href="#local_libary_routes"> LocalLibrary</a>, которые будут вызывать функции контроллеров, определенные в предыдущем разделе.</p> + +<p>Каркас приложения уже содержит каталог <strong>./routes</strong>, в котором есть маршруты для <em>index</em> и <em>users</em>. Внутри этого каталога создадим еще один файл маршрутов — <strong>catalog.js</strong> ( см. ниже).</p> + +<pre>/express-locallibrary-tutorial //the project root + /routes + index.js + users.js + <strong>catalog.js</strong></pre> + +<p>Скопируйте приведенный ниже код в файл <strong>/routes/</strong><strong>catalog.js</strong> :</p> + +<pre class="brush: js"><strong>var express = require('express'); +var router = express.Router(); +</strong> +// Требующиеся модули контроллеров. +var book_controller = require('../controllers/bookController'); +var author_controller = require('../controllers/authorController'); +var genre_controller = require('../controllers/genreController'); +var book_instance_controller = require('../controllers/bookinstanceController'); + +/// BOOK ROUTES МАРШРУТЫ КНИГ/// + +// GET catalog home page. +router.get('/', book_controller.index); + +// GET request for creating a Book. NOTE This must come before routes that display Book (uses id). +// GET запрос для создания книги. Должен появиться до маршрута, показывающего книгу(использует id) +router.get('/book/create', book_controller.book_create_get); + +// POST request for creating Book. +router.post('/book/create', book_controller.book_create_post); + +// GET request to delete Book. +router.get('/book/:id/delete', book_controller.book_delete_get); + +// POST request to delete Book. +router.post('/book/:id/delete', book_controller.book_delete_post); + +// GET request to update Book. +router.get('/book/:id/update', book_controller.book_update_get); + +// POST request to update Book. +router.post('/book/:id/update', book_controller.book_update_post); + +// GET request for one Book. +router.get('/book/:id', book_controller.book_detail); + +// GET request for list of all Book items. +router.get('/books', book_controller.book_list); + +/// AUTHOR ROUTES /// + +// GET request for creating Author. NOTE This must come before route for id (i.e. display author). +// GET-запрос для создания автора. Должен появиться до маршрута для id (для вывода автора) +router.get('/author/create', author_controller.author_create_get); + +// POST request for creating Author. +router.post('/author/create', author_controller.author_create_post); + +// GET request to delete Author. +router.get('/author/:id/delete', author_controller.author_delete_get); + +// POST request to delete Author. +router.post('/author/:id/delete', author_controller.author_delete_post); + +// GET request to update Author. +router.get('/author/:id/update', author_controller.author_update_get); + +// POST request to update Author. +router.post('/author/:id/update', author_controller.author_update_post); + +// GET request for one Author. +router.get('/author/:id', author_controller.author_detail); + +// GET request for list of all Authors. +router.get('/authors', author_controller.author_list); + +/// GENRE ROUTES /// + +// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id). +// GET-запрос для создания жанра. Должен появиться до маршрута, выводящего жанр (( с использованием id) +router.get('/genre/create', genre_controller.genre_create_get); + +//POST request for creating Genre. +router.post('/genre/create', genre_controller.genre_create_post); + +// GET request to delete Genre. +router.get('/genre/:id/delete', genre_controller.genre_delete_get); + +// POST request to delete Genre. +router.post('/genre/:id/delete', genre_controller.genre_delete_post); + +// GET request to update Genre. +router.get('/genre/:id/update', genre_controller.genre_update_get); + +// POST request to update Genre. +router.post('/genre/:id/update', genre_controller.genre_update_post); + +// GET request for one Genre. +router.get('/genre/:id', genre_controller.genre_detail); + +// GET request for list of all Genre. +router.get('/genres', genre_controller.genre_list); + +/// BOOKINSTANCE ROUTES /// + +// GET request for creating a BookInstance. NOTE This must come before route that displays BookInstance (uses id). +// GET-запрос для создания экземпляра книги. Должен появиться до маршрута, выводящего BookInstance с использованием id +router.get('/bookinstance/create', book_instance_controller.bookinstance_create_get); + +// POST request for creating BookInstance. +router.post('/bookinstance/create', book_instance_controller.bookinstance_create_post); + +// GET request to delete BookInstance. +router.get('/bookinstance/:id/delete', book_instance_controller.bookinstance_delete_get); + +// POST request to delete BookInstance. +router.post('/bookinstance/:id/delete', book_instance_controller.bookinstance_delete_post); + +// GET request to update BookInstance. +router.get('/bookinstance/:id/update', book_instance_controller.bookinstance_update_get); + +// POST request to update BookInstance. +router.post('/bookinstance/:id/update', book_instance_controller.bookinstance_update_post); + +// GET request for one BookInstance. +router.get('/bookinstance/:id', book_instance_controller.bookinstance_detail); + +// GET request for list of all BookInstance. +router.get('/bookinstances', book_instance_controller.bookinstance_list); + +<strong>module.exports = router;</strong> +</pre> + +<p>Модуль загружает Express и использует его для создания объекта <code>Router</code> . В маршутизаторе задаются маршруты и производится их экспорт.</p> + +<p>Маршруты определяют в объекте маршрутизатора или <code>.get()</code> или <code>.post()</code> методы. Все пути заданы как строки (образцы строк и регулярные выражения не использовались). Маршруты, которые взаимодействуют с конкретным ресурсом (скажем, с книгой), для получения из URL идентификатора объекта используют параметры путей.</p> + +<p>Все функции-обработчики импортируются из созданных в предыдущем разделе модулей контроллеров.</p> + +<h3 id="Обновление_модуля_маршрута_index">Обновление модуля маршрута index</h3> + +<p>Все новые маршруты заданы, а маршрут на начальную страницу остался без изменения. Давайте перенаправим его на новую страницу "index", которая создана в каталоге '/catalog'.</p> + +<p>Откройте <strong>/routes/index.js</strong> и замените существущий маршрут нприведенную ниже.</p> + +<pre class="brush: js">// GET home page. +router.get('/', function(req, res) { + res.redirect('/catalog'); +});</pre> + +<div class="note"> +<p><strong>Заметка:</strong> Это первое использование метода ответа <a href="https://expressjs.com/en/4x/api.html#res.redirect">redirect()</a> . Он делает перенаправление на указанную страницу, и по умолчанию устанавливает код возврата HTTP в "302 Found" (найдено). Если требуется, можно изменить код возврата. Путь можно задавать как абсолютный или как относительный.</p> +</div> + +<h3 id="Обновление_app.js">Обновление app.js</h3> + +<p>Завершающий шаг - добавление маршрутов в цепочку промежуточного слоя. Это будет сделано в <code>app.js</code>.</p> + +<p>Откройте файл <strong>app.js</strong> и поместите require для маршрута каталог ниже других маршрутов (добавьте третью строку. показанную ниже, после имеющихся двух строк):</p> + +<pre class="brush: js">var indexRouter = require('./routes/index'); +var usersRouter = require('./routes/users'); +<strong>var catalogRouter = require('./routes/catalog'); //Import routes for "catalog" area of site</strong></pre> + +<p>Далее, добавьте маршрут каталога в стек промежуточного слоя после других маршрутов (добавтьте третью строку после имеющихся двух):</p> + +<pre class="brush: js">app.use('/', indexRouter); +app.use('/users', usersRouter); +<strong>app.use('/catalog', catalogRouter); // Add catalog routes to middleware chain.</strong></pre> + +<div class="note"> +<p><strong>Заметка:</strong> Мы добавили модуль каталога в путь<code>'/catalog'</code>. Этот путь будет предшествовать всем путям, определенным в модуле каталога. Например, для доступа к списку книг URL будет таким: <code>/catalog/books/</code>.</p> +</div> + +<p>Вот так. Теперь у нас есть пути и фиктивные функции, подготовленные для всех URL, которые мы собираемся поддерживать на веб-сайте LocalLibrary.</p> + +<h3 id="Проверка_маршрутов">Проверка маршрутов</h3> + +<p>Чтобы проверить маршруты, сначала запустим веб-сайт обычным способом</p> + +<ul> + <li>Обычный способ + <pre class="brush: bash"><code>// Windows +SET DEBUG=express-locallibrary-tutorial:* & npm start + +// macOS or Linux +DEBUG=express-locallibrary-tutorial:* npm start</code> +</pre> + </li> + <li>Если предварительно установлен <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">nodemon</a>, для запуска можно использовать: + <pre><code>// Windows +SET DEBUG=express-locallibrary-tutorial:* & npm <strong>run devstart</strong> + +// macOS or Linux +</code> DEBUG=express-locallibrary-tutorial:* npm <strong style="font-family: inherit; font-size: 1rem;">run devstart</strong> +</pre> + </li> +</ul> + +<p>После запуска перейдите к совокупности URL нашей LocalLibrary, и проверьте, что не появляется страница ошибки (HTTP 404). Небольшая часть наших URL для удобства приводится ниже:</p> + +<ul> + <li><a href="http://localhost:3000/">http://localhost:3000/</a></li> + <li><a href="http://localhost:3000/catalog">http://localhost:3000/catalog</a></li> + <li><a href="http://localhost:3000/catalog/books">http://localhost:3000/catalog/books</a></li> + <li><a href="http://localhost:3000/catalog/bookinstances/">http://localhost:3000/catalog/bookinstances/</a></li> + <li><a href="http://localhost:3000/catalog/authors/">http://localhost:3000/catalog/authors/</a></li> + <li><a href="http://localhost:3000/catalog/genres/">http://localhost:3000/catalog/genres/</a></li> + <li><a href="http://localhost:3000/catalog/book/5846437593935e2f8c2aa226/">http://localhost:3000/catalog/book/5846437593935e2f8c2aa226</a></li> + <li><a href="http://localhost:3000/catalog/book/create">http://localhost:3000/catalog/book/create</a></li> +</ul> + +<h2 id="Итог">Итог</h2> + +<p>Созданы все маршруты для нашего сайта. Созданы также фиктивные функции контроллеров, которые мы полностью реализуем в последующих статьях. Попутно мы изучили массу базовых сведений о маршрутах Express, и ознакомились с некоторыми подходами по структурированию маршрутов и контроллеров.</p> + +<p>В следующей статье мы создадим настоящую страничку приветствия нашего сайта, для чего используем представления (шаблоны) и данные, хранящиеся в наших моделях.</p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="http://expressjs.com/en/starter/basic-routing.html">Basic routing</a> Основы маршрутизации (документация Express)</li> + <li><a href="http://expressjs.com/en/guide/routing.html">Routing guide</a> Руководство по маршрутизации (документация Express)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}</p> + + + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> diff --git a/files/ru/learn/server-side/express_nodejs/skeleton_website/index.html b/files/ru/learn/server-side/express_nodejs/skeleton_website/index.html new file mode 100644 index 0000000000..51af5515d4 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/skeleton_website/index.html @@ -0,0 +1,507 @@ +--- +title: 'Учебник Express часть 2: Создание скелета сайта' +slug: Learn/Server-side/Express_Nodejs/skeleton_website +translation_of: Learn/Server-side/Express_Nodejs/skeleton_website +--- +<div>{{LearnSidebar}}</div> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs")}}</p> + +<p class="summary">Эта вторая статья в нашем <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">учебнике Express</a> показывает, как создать каркас проекта веб-сайта, который позже можно будет заполнить с помощью путей сайта, шаблонов представлений и обращений к базе данных.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Необходимые знания:</th> + <td><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Установить среду разработки Node</a>. Просмотреть учебник Express.</td> + </tr> + <tr> + <th scope="row">Задача:</th> + <td>Научиться запускать свои проекты используя <em>Express Application Generator</em>.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>В этой статье показано, как создать каркас сайта с помощью средства <a href="https://expressjs.com/en/starter/generator.html">Express Application Generator</a>. Каркас затем можно будет заполнить с помощью путей сайта, шаблонов/представлений и обращений к базе данных. Мы используем это средство для создания основы нашего <a>сайта Local Library</a>. К основе будет добавлен код, необходимый сайту. Создание каркаса чрезвычайно просто -- требуется только вызвать генератор в командной строке, указав имя нового проекта, дополнительно можно указать также движок шаблона сайта и генератор CSS.</p> + +<p>Далее показано, как вызвать генератор приложений, и даётся небольшое пояснение различных вариантов представлений и CSS. Мы поясним структуру каркаса веб-сайта. В конце мы покажем, как запустить веб-сайт, чтобы убедиться, что он работает.</p> + +<div class="note"> +<p><span style="line-height: 1.5;"><strong>Замечание</strong>: </span><em>Express Application Generator</em> — не единственный генератор Express-приложений, и созданный проект --не единственный жизнеспособный способ организации ваших файлов и каталогов. Однако созданный сайт имеет модульную структуру, которую легко понять и расширить. О <em>минимальном</em> Express приложении смотрите <a href="https://expressjs.com/en/starter/hello-world.html">Hello world example</a> в документации Express.</p> +</div> + +<h2 id="Применение_генератора_приложений">Применение генератора приложений</h2> + +<p>Вы уже должны были устанавить <code>express-generator</code>, читая статью <a>установка среды разработки Node</a>. Напомним, что генератор установлен с помощью менеджера пакетов NPM, при выполнении команды:</p> + +<pre class="brush: bash notranslate"><code>npm install express-generator -g</code> +</pre> + +<p><code>E</code><code>xpress-generator </code>имеет ряд параметров, которые можно увидеть, выполнив команду express --help (или express -h):</p> + +<pre class="brush: bash notranslate">> express --help + + Usage: express [options] [dir] + + Options: + + -h, --help output usage information (информация по применению) + --version output the version number (номер версии express) + -e, --ejs add ejs engine support (добавить поддержку движка ejs) + --pug add pug engine support (добавить поддержку движка pug) + --hbs add handlebars engine support (добавить поддержку движка handlebar) + -H, --hogan add hogan.js engine support (добавить поддержку движка hogan.js) + -v, --view <engine> add view <engine> support (ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade) + (добавить поддержку движков представлений. По умолчанию - jade) + -c, --css <engine> add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css) + (добавить поддержку движков стилей, по умолчанию - простой CSS) + --git add .gitignore (добавить поддержку .gitignore) + -f, --force force on non-empty directory (работать в каталоге с файлами) +</pre> + +<p>Команда <code>express</code> создаст проект в <em>текущем</em> каталоге с использованием (устаревшего) движка представления <em>Jade</em> и обычного CSS. Если указать <font face="Courier New, monospace">express </font><font face="Courier New, monospace">name</font>, проект будет создан в подкаталоге name текущего каталога. </p> + +<pre class="brush: bash notranslate"><code>express</code></pre> + +<p>Можно выбрать движок представления (шаблон), используя --<code>view; </code><code><font face="Times New Roman, serif">параметр</font></code><code> --</code><code>css </code> позволяет выбрать движок для создания CSS.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Другие опции (<code>--hogan</code>, <code>--ejs</code>, <code>--hbs</code> и пр.) для выбора шаблонизатора устарели. Используйте <code>--view</code> (или<code> -v</code>)!</p> +</div> + +<h3 id="Какой_движок_представлений_следует_использовать">Какой движок представлений следует использовать?</h3> + +<p><em>Express-generator</em><em> </em>дает возможность сконфигурировать несколько популярных движков, включая <a href="https://www.npmjs.com/package/ejs">EJS</a>, <a href="http://github.com/donpark/hbs">Hbs</a>, <a href="https://pugjs.org/api/getting-started.html">Pug</a> (Jade), <a href="https://www.npmjs.com/package/twig">Twig</a>, и <a href="https://www.npmjs.com/package/vash">Vash</a>, но по умолчанию выбран Jade. Экспресс сразу после установки может поддерживать большое количество и других шаблонизаторов.</p> + +<div class="note"> +<p><strong>Заметка:</strong> При желании использовать шаблонизатор, который не поддерживается генератором, просмотрите документацию <a href="https://expressjs.com/en/guide/using-template-engines.html"><font color="#3d7e9a"><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><font size="3">Using template engines with Express</font></font></font></a><font color="#333333"><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><font size="3"> </font></font></font> и документацию для нужного шаблонизатора.</p> +</div> + +<p>Как правило, следует выбрать шаблонизатор, который имеет весь необходимый вам функционал и обеспечивает вам высокую производительность - так же, как вы выбираете любой другой компонент! Некоторые критерии для сравнения шаблонизаторов:</p> + +<ul> + <li>Время до получения результата — если ваша команда уже имела дело с шаблонизатором, то, скоре всего, продуктивнее будет использовать этот шаблонизатор. Если нет, тогда следует учесть все относительные сложности изучения кандидатов в шаблонизаторы.</li> + <li>Популярность и активность — проверьте популярность движка, возможно, у него есть активное сообщество. Очень важно иметь поддержку для движка, если у вас возникнут проблемы в течении жизни вебсайта.</li> + <li>Стиль — некоторые шаблонизаторы используют особую разметку для отображения вставленного контента внутри "обычного" HTML, а другие строят HTML, используя специальный синтаксис (например, используя отступы или блочные имена).</li> + <li>Производительность и время интерпретации.</li> + <li>Особенности — следует выбирать движок с учетом таких особенностей: + <ul> + <li>Наследование макета: позволяет определить базовый шаблон и затем наследовать только те части, которые отличаются для конкретной страницы. Это, как правило, лучший подход, чем создание шаблонов путём включения нескольких необходимых компонентов или создания шаблона с нуля каждый раз.</li> + <li>Поддержка «Include»: позволяет создавать шаблоны, включая другие шаблоны.</li> + <li>Краткий синтаксис управления переменными и циклами.</li> + <li>Возможность фильтровать значения переменных на уровне шаблона (например, делать переменные в верхнем регистре или форматировать значение даты).</li> + <li>Возможность создавать выходные форматы, отличные от HTML (например, JSON или XML).</li> + <li>Поддержка асинхронных операций и потоковой передачи.</li> + <li> Возможность использования как на клиенте, так и на сервере. Возможность применения движка шаблона на клиенте позволяет обслуживать данные и выполнять все действия или их большую часть на стороне клиента.</li> + </ul> + </li> +</ul> + +<div class="note"> +<p><strong>Совет:</strong> В интернете множество ресурсов, которые помогут сравнить различные варианты!</p> +</div> + +<p>Для этого проекта мы используем шаблонизатор <a href="https://pugjs.org/api/getting-started.html">Pug</a> (в прошлом назывался Jade) -- один из популярнейших Express/JavaScript шаблонизаторов, который поддерживается в Express-generator "из коробки".</p> + +<h3 id="Какие_шаблонизаторы_CSS_следует_использовать">Какие шаблонизаторы CSS следует использовать?</h3> + +<p><em>Express Application Generator</em> позволяет создавать проекты, настроенные для применения шаблонизаторов CSS: <a href="http://lesscss.org/">LESS</a>, <a href="http://sass-lang.com/">SASS</a>, <a href="http://compass-style.org/">Compass</a>, <a href="http://stylus-lang.com/">Stylus</a>.</p> + +<div class="note"> +<p><strong>Заметка: </strong>простой<strong> </strong>CSS имеет некоторые ограничения, затрудняющие выполнение задач. Шаблонизаторы CSS позволяют использовать более эффективный подход для создании таблиц стилей CSS, но требуют компиляции файлов таблиц стилей в стандартный CSS для применения в браузере.</p> +</div> + +<p>Как и в случае с шаблонизаторами сайта, следует применять шаблонизатор, обеспечивающий высокую производительность работы. В этом проекте мы используем обычный CSS (по умолчанию), поскольку простота наших требований к CSS не оправдает применение чего-то более сложного.</p> + +<h3 id="Какую_базу_данных_следует_использовать">Какую базу данных следует использовать?</h3> + +<p>Сгенерированный код не использует и не содержит в себе какой-либо базы данных. <em>Express</em> может использовать любой движок <a href="https://expressjs.com/en/guide/database-integration.html">базы данных</a>, который поддерживается <em>Node</em> (<em>Express</em> не предъявляет каких-либо особых требований к базе данных).</p> + +<p>Мы обсудим взаимодействие с базой данных в следующей статье.</p> + +<h2 id="Создание_проекта">Создание проекта</h2> + +<p>Разрабатывая пример - приложение <em>Local Library, </em>мы построим проект с именем <em>express-locallibrary-tutorial. </em>Используем библиотеку шаблонов Pug, а движок CSS применять не будем.</p> + +<p>Выберем место для нового проекта — каталог <font face="Courier New, monospace">express-locallibrary-tutorial - </font><font face="Times New Roman, serif">и выполним</font><font face="Courier New, monospace"> </font>команду:</p> + +<pre class="brush: bash notranslate">express express-locallibrary-tutorial --view=pug +</pre> + +<p>Будет создан каталог <font face="Courier New, monospace">express-locallibrary-tutorial </font><font face="Times New Roman, serif">и выведен список созданных внутри каталога проектных файлов</font>.</p> + +<pre class="brush: bash notranslate"> create : express-locallibrary-tutorial + create : express-locallibrary-tutorial/package.json + create : express-locallibrary-tutorial/app.js + create : express-locallibrary-tutorial/public/images + create : express-locallibrary-tutorial/public + create : express-locallibrary-tutorial/public/stylesheets + create : express-locallibrary-tutorial/public/stylesheets/style.css + create : express-locallibrary-tutorial/public/javascripts + create : express-locallibrary-tutorial/routes + create : express-locallibrary-tutorial/routes/index.js + create : express-locallibrary-tutorial/routes/users.js + create : express-locallibrary-tutorial/views + create : express-locallibrary-tutorial/views/index.pug + create : express-locallibrary-tutorial/views/layout.pug + create : express-locallibrary-tutorial/views/error.pug + create : express-locallibrary-tutorial/bin + create : express-locallibrary-tutorial/bin/www + + install dependencies: + > cd express-locallibrary-tutorial && npm install + + run the app: + > SET DEBUG=express-locallibrary-tutorial:* & npm start</pre> + +<p>После списка файлов генератор выведет инструкции для установки зависимостей (указанных в файле <strong>package.json</strong>) и запуска приложения (инструкции предназначены для Windows; для Linux/Mac OS X они могут слегка отличаться).</p> + +<h2 id="Запускаем_каркас_сайта">Запускаем каркас сайта</h2> + + + +<p>Сейчас у нас есть готовый каркас проекта. Сайт пока ничего не делает, но его стоит запустить, чтобы убедиться в его работоспособности.</p> + + + +<ol> + <li>Прежде всего установим зависимости (команда <code>install</code> запросит все пакеты зависимостей, указанные в файле<strong> package.json</strong>). + + <pre class="brush: bash notranslate">cd express-locallibrary-tutorial +npm install</pre> + </li> + <li>Затем запустим приложение. + <ul> + <li>В Windows используйте команду: + <pre class="brush: bash notranslate">SET DEBUG=express-locallibrary-tutorial:* & npm start</pre> + </li> + <li>В Mac OS X или Linux используйте команду: + <pre class="brush: bash notranslate">DEBUG=express-locallibrary-tutorial:* npm start +</pre> + </li> + </ul> + </li> + <li>Откроем <a href="http://localhost:3000/">http://localhost:3000/</a> в браузере. Мы должны увидеть такую страницу:</li> +</ol> + +<p><img alt="Browser for default Express app generator website" src="https://mdn.mozillademos.org/files/14375/ExpressGeneratorSkeletonWebsite.png" style="display: block; height: 403px; margin: 0px auto; width: 576px;"></p> + +<p>У нас получилось веб-приложение на базе Express, работающее по адресу <em>localhost:3000</em>.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Можно также запустить приложение командой <code>npm start</code>. Переменная DEBUG, указанная в примере, включает логгирование в консоль для дальнейшей отладки. Так, при посещении страницы веб-приложения, вы увидите похожий вывод в консоль:</p> + +<pre class="brush: bash notranslate">>SET DEBUG=express-locallibrary-tutorial:* & npm start + +> express-locallibrary-tutorial@0.0.0 start D:\express-locallibrary-tutorial +> node ./bin/www + + express-locallibrary-tutorial:server Listening on port 3000 +0ms +GET / 200 288.474 ms - 170 +GET /stylesheets/style.css 200 5.799 ms - 111 +GET /favicon.ico 404 34.134 ms - 1335</pre> +</div> + +<h2 id="Обеспечиваем_перезапуск_сервера_при_изменении_файлов">Обеспечиваем<br> + перезапуск сервера при изменении файлов</h2> + +<p>Любые изменения, внесенные на веб-сайт Express, не будут отображаться до перезапуска сервера. Остановка (Ctrl-C) и перезапуск сервера каждый раз после внесения изменений быстро становится раздражающей, поэтому стоит автоматизировать перезапуск.</p> + +<p>Одно из самых простых средств для этого --<br> + <a href="https://github.com/remy/nodemon">nodemon</a>. Его обычно устанавливают глобально (так как это "инструмент"), но сейчас мы устанавим его и будем применять локально как зависимость разработки, так что любые разработчики проекта получат его автоматически при установке приложения. Выполним следующую команду (предполагаем, что мы находимся в корневом каталоге):</p> + +<pre class="brush: bash notranslate">npm install --save-dev nodemon</pre> + + + +<p>Если вы предпочитаете установить nodemon глобально, не только для этого проекта, надо выполнить команду</p> + + + +<pre class="notranslate"><code><font color="#333333"><font face="Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace"><font size="3">npm install -g nodemon</font></font></font></code></pre> + + + +<p>В файле <strong>package.json </strong>проекта появится новый раздел с этой зависимостью (на вашей машине номер версии nodemon может бытьдругим) :</p> + +<pre class="brush: json notranslate"> "devDependencies": { + "nodemon": "^1.11.0" + } +</pre> + +<p>Поскольку nodemon не установлен глобально, его нельзя запустить из командной строки (пока мы не добавим его в путь), но его можно вызвать из сценария NPM, так как NPM знает все об установленных пакетах. Раздел <code>scripts</code> в файле package.json исходно будет содержать одну строку, которая начинается с <code>"start"</code>. Обновите его, поставив запятую в конце строки, и добавьте строку <code>"devstart",</code> показанную ниже:</p> + +<pre class="brush: json notranslate"> "scripts": { + "start": "node ./bin/www"<strong>,</strong> +<strong> "devstart": "nodemon ./bin/www"</strong> + }, +</pre> + +<p>Теперь можно запустить сервер почти так же, как и ранее, но командой npm run devstart:</p> + +<ul> + <li>В Windows:</li> +</ul> + +<pre class="brush: bash notranslate">SET DEBUG=express-locallibrary-tutorial:* & npm <strong>run devstart</strong></pre> + +<ul> + <li>Для macOS или Linux:</li> +</ul> + +<pre class="notranslate"><code>DEBUG=express-locallibrary-tutorial:* npm <strong>run devstart</strong></code></pre> + +<div class="note"> +<p><strong>Заметка:</strong> Сейчас после изменения любого файла проекта сервер будет перезапускаться (или можно самостоятельно перезапустить его, введя <code>rs</code> в командной строке). Вам все равно придется обновить страницу в браузере .</p> + +<p>Теперь мы должны выполнять команду "<code>npm run </code><em><scriptname></em>" а не просто <code>npm start</code>, поскольку "start", это, по сути, команда NPM, сопоставленная сценарию в файле package.json. Можно заменить команду в сценарии "start", но, так как мы хотим использовать nodemon только во время разработки, разумно создать новую команду сценария.</p> +</div> + +<h2 id="Созданный_проект">Созданный проект</h2> + +<p>Давайте посмотрим на созданный проект.</p> + +<h3 id="Структура_каталогов">Структура каталогов</h3> + +<p>После установки зависимостей проект имеет такую структуру файлов (файлы - это элементы <strong>без </strong>префикса"/"). Файл <strong>package.json</strong> определяет имя файла с приложением, сценарии запуска, зависимости и др. Сценарий запуска задает точку входа приложения, у нас -- файл JavaScript <strong>/bin/www</strong>. Этот файл настраивает некоторые обработчики ошибок приложения, а затем загружает <strong>app.js </strong>для выполнения остальной работы. Пути приложения хранятся в отдельных модулях каталога <strong>routes/</strong>. Шаблоны хранятся в каталоге /<strong>views</strong>.</p> + +<pre class="notranslate">/express-locallibrary-tutorial + <strong>app.js</strong> + /bin + <strong>www</strong> + <strong>package.json</strong> + /node_modules + [about 4,500 subdirectories and files] + /public + /images + /javascripts + /stylesheets + <strong>style.css</strong> + /routes + <strong>index.js</strong> + <strong>users.js</strong> + /views + <strong>error.pug</strong> + <strong>index.pug</strong> + <strong>layout.pug</strong> + +</pre> + +<p>Далее файлы описаны более подробно.</p> + +<h3 id="package.json">package.json</h3> + +<p>Файл <strong>package.json </strong>указывает зависимости приложения и содержит другие данные:</p> + +<pre class="brush: json notranslate">{ + "name": "express-locallibrary-tutorial", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www", + "devstart": "nodemon ./bin/www" + }, + "dependencies": { + "body-parser": "~1.15.2", + "cookie-parser": "~1.4.3", + "debug": "~2.2.0", +<strong> "express": "~4.14.0",</strong> + "morgan": "~1.7.0", +<strong> "pug": "~2.0.0-beta6",</strong> + "serve-favicon": "~2.3.0" + }, + "devDependencies": { + "nodemon": "^1.11.0" + } +}</pre> + +<p>Зависимости включают пакет express и пакет для выбранного движка представления (pug). Кроме того, указаны пакеты, полезные во многих веб-приложениях:</p> + +<ul> + <li><a href="https://www.npmjs.com/package/body-parser">body-parser</a>: -- анализирует часть тела входящего запроса HTTP и облегчает извлечение из него различных частей. Например, мы можно читать <code>POST-</code>параметры.</li> + <li><a href="https://www.npmjs.com/package/cookie-parser">cookie-parser</a>: разбирает заголовок и заполняет <code>req.cookies</code> (по сути, дает удобный метод для доступа к информации cookie).</li> + <li><a href="https://www.npmjs.com/package/debug">debug</a>: небольшой отладчик, работающий по образцу методики отладки ядра node.</li> + <li><a href="https://www.npmjs.com/package/morgan">morgan</a>: средство логгирования запросов HTTP для node.</li> + <li><a href="https://www.npmjs.com/package/serve-favicon">serve-favicon</a>: средство обработки <a href="https://en.wikipedia.org/wiki/Favicon">favicon</a> (значка, используемого для представления сайта на вкладках браузера, закладках и т. д).</li> +</ul> + +<p>Раздел "scripts" определяет скрипт" start", выполняемый при запуске сервера командой <code>npm start</code>. Можно видеть, что самом деле выполняется команда node <strong>./bin/www</strong>. Кроме того, определяется script "<em>devstart</em>", который вызывается командой <code>npm run devstart</code>. Запускается тот же файл <strong>./bin/www</strong> ,но командой <em>nodemon</em> вместо <em>node</em>.</p> + +<pre class="notranslate"><code>"scripts": { + "start": "node ./bin/www", + "devstart": "nodemon ./bin/www" + },</code></pre> + +<h3 id="Файл_www">Файл www</h3> + +<p>Файл <strong>/bin/www</strong> – это входная точка приложения. Сначала в файле создается объект основного приложения, расположенного в app.js — выполняется app=<code>require(./</code><code>app</code><code>).</code></p> + +<pre class="brush: js notranslate">#!/usr/bin/env node + +/** + * Module dependencies. + */ + +<strong>var app = require('../app');</strong> +</pre> + +<div class="note"> +<p><strong>Заметка:</strong> <code>require()</code> -- это глобальная функция node для импорта модулей в текущий файл. Для модуля <strong>app.js </strong>указан относительный путь, а расширение файла по умолчанию (.js) опущено.</p> +</div> + +<p>Оставшаяся часть кода настраивает порт сервера node для HTTP (определен в переменной среды или 3000, если не определен), и начинает прослушивание и протоколирование соединений и ошибок сервера. Сейчас вам не требуется дополнительных сведений о коде (все в этом файле шаблонно), но, при желании, его можно посмотреть.</p> + +<h3 id="Файл_app.js">Файл app.js</h3> + +<p>Этот файл создает объект приложения <code>express </code>(с именем<code>app</code>, по соглашению), настраивает приложение и промежуточное ПО, а затем экспортирует приложение из модуля. В приведенном ниже коде показаны только те части файла, которые создают и экспортируют объект приложения:</p> + +<pre class="brush: js notranslate"><code>var express = require('express'); +var app = express(); +... +</code>module.exports = app; +</pre> + +<p>Именно этот экспортированный объект использован в рассмотренном ранее файле www.</p> + +<p>Рассмотрим детали файла app.js. Сначала при помощи require(...) выполняется импорт некоторых полезных библиотек node: <em>express,</em> s<em>erve-favicon</em>, <em>morgan</em>, <em>cookie-parse, body-parser </em>(они ранее были загружены для нашего приложения командой npm install), а также path из основной библиотеки node (применяется для разбора путей каталогов и файлов).</p> + +<pre class="brush: js notranslate">var express = require('express'); +var path = require('path'); +var favicon = require('serve-favicon'); +var logger = require('morgan'); +var cookieParser = require('cookie-parser'); +var bodyParser = require('body-parser'); +</pre> + +<p>Затем require запрашивает модули из каталога путей route. Эти модули и файлы содержат код для обработки конкретного набора соответствующих путей (URL маршрутов). Если мы расширим каркас приложения, например, чтобы получить список книг библиотеки, нам следует добавить новый файл для обработки пути, связанного с книгами.</p> + +<pre class="brush: js notranslate">var index = require('./routes/index'); +var users = require('./routes/users'); +</pre> + +<div class="note"> +<p><strong>Заметка:</strong> Здесь мы только импортируем модули. В действительности эти пути еще не используются — это произойдет в файле несколько позже.</p> +</div> + +<p>Далее, импортированные модули express применяются для создания объекта app, который потом устанавливает движки-шаблоны представления. Установка движков состоит их двух частей. В первой мы задаем значение 'view', указывая папку, в которой будут размещаться шаблоны (у нас это /views). Во второй мы задаем значение движка 'view engine', указывая на библиотеку шаблона (у нас — "pug").</p> + +<pre class="brush: js notranslate">var app = express(); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'pug'); +</pre> + +<p>Следующие строки вызывают app.use(...), чтобы добавить промежуточные (middleware) библиотеки в цепочку обработки запросов. Кроме сторонних библиотек, импортированных ранее, используем библиотеку Express.static, что позволит обрабатывать статические файлы из папки <strong>/public </strong>корня проекта.</p> + +<pre class="brush: js notranslate">// uncomment after placing your favicon in /public +//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); +app.use(logger('dev')); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); +<strong>app.use(express.static(path.join(__dirname, 'public')));</strong> +</pre> + +<p>Теперь, когда промежуточные библиотеки настроены, мы добавляем (импортированный ранее) код обработки путей в цепочку обработки запросов. Импортированный код будет задавать отдельные пути для разных частей сайта:</p> + +<pre class="brush: js notranslate">app.use('/', index); +app.use('/users', users); +</pre> + +<div class="note"> +<p><strong>Заметка:</strong> . пути, указанные выше ('/' и '<code>/users'</code>) рассматриваются как префиксы путей, определенных в импортированных файлах. Так, например, если импортированный модуль users определяет путь для /profile, для доступа следует указать /users/profile. Мы поговорим подробнее о путях в последующей статье.</p> +</div> + +<p>Последняя в файле промежуточная библиотека добавляет методы обработки ошибок и ответов 404 от HTTP.</p> + +<pre class="brush: js notranslate">// catch 404 and forward to error handler +app.use(function(req, res, next) { + var err = new Error('Not Found'); + err.status = 404; + next(err); +}); + +// error handler +app.use(function(err, req, res, next) { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.render('error'); +}); +</pre> + +<p>Объект app приложения Express теперь полностью настроен. Остался последний шаг - добавить его к экпортируемым элементам модуля (это позволит импортировать его в файле <strong>/bin/www</strong>).</p> + +<pre class="brush: js notranslate">module.exports = app;</pre> + +<h3 id="Пути_Routes">Пути (Routes)</h3> + +<p>Файл путей /routes/users.js приведен ниже (файлы путей имеют сходную структуру, поэтому нет необходимости приводить также index.js). Сначала загружается модуль Express, затем он используется для получения объекта express.Router. После этого для этого объекта задается путь, и, наконец, объект-роутер экспортируется из модуля (именно это позволяет импортировать файл в app.js):.</p> + +<pre class="brush: js notranslate">var express = require('express'); +var router = express.Router(); + +/* GET users listing. */ +<strong>router.get('/', function(req, res, next) { + res.send('respond with a resource'); +});</strong> + +module.exports = router; +</pre> + +<p>Путь определяет функцию обратного вызова (далее — callback-функцию), которая будет вызвана, когда обнаружится HTTP GET-запрос корректного вида. Образец для сопоставления пути задается при импорте модуля -- ('<code>/users</code>') плюс что-то, определяемое в этом файле ('<code>/</code>'). Иными словами, этот путь будет использован, когда получен URL-запрос <code>/users/</code>.</p> + +<div class="note"> +<p><strong>Совет:</strong> запустите сервер и задайте в браузере URL <a href="http://localhost:3000/users/">http://localhost:3000/users/</a>. Вы должны увидеть<strong> </strong>сообщение: 'respond with a resource'.</p> +</div> + +<p>Стоит отметить, что callback-функция имеет третий аргумент - '<code>next</code>', т. е. является не простой callback-функцией, а callback-функцией промежуточного модуля. Пока третий аргумент не используется, но будет полезен в дальнейшем, если мы захотим создать несколько обработчиков пути <code><font color="#333333"><font face="consolas, Liberation Mono, courier, monospace"><font size="3">'/'</font></font></font></code>.</p> + +<h3 id="Представления_шаблоны">Представления (шаблоны)</h3> + +<p>Файлы преставлений (шаблонов) хранятся в каталоге <strong><font color="#333333"><font face="Arial, x-locale-body, sans-serif"><font size="3">/views </font></font></font></strong><font color="#333333"><font face="Times New Roman, serif"><font size="3">(это указано в </font></font><font face="Courier New, monospace"><font size="3">app.js</font></font><font face="Times New Roman, serif"><font size="3">) и имеют расширение</font></font></font><strong><font color="#333333"><font face="Times New Roman, serif"><font size="3"> </font></font></font></strong><strong>.pug</strong>. Метод <code><a href="http://expressjs.com/en/4x/api.html#res.render">Response.render()</a></code> выполняет указанный шаблон, передавая объекту значение именованной переменной, и затем посылает результат как ответ. В коде из <strong>/routes/index.js</strong> (приводится ниже) можно увидеть, что роут отвечает, используя шаблон "index" с переданным значением переменной "title" из шаблона.</p> + +<pre class="brush: js notranslate">/* GET home page. */ +router.get('/', function(req, res) { + res.render('index', { title: 'Express' }); +}); +</pre> + +<p>Шаблон для пути '/' приведен ниже (файл <strong>index.pug</strong>). О синтаксисе мы поговорим позже. Сейчас важно знать, что переменная title со значением 'Express' помещена в определенное место шаблона.</p> + +<pre class="notranslate">extends layout + +block content + h1= title + p Welcome to #{title} +</pre> + +<h2 id="Мини-тест">Мини-тест</h2> + +<p>Создайте новый путь в <strong>/routes/users.js</strong>, чтобы выводить текст <strong>"</strong><em>You're so cool" </em>или<em> </em><em>"</em><em>Ну, вы крутой!</em><em>" </em>по URL<em> </em><code>/users/cool/</code><em>. </em>Проверьте его, запустив сервер и посетив в браузере <em><a href="http://localhost:3000/users/cool/">http://localhost:3000/users/cool/</a>.</em></p> + +<ul> +</ul> + +<h2 id="Итоги">Итоги</h2> + + + +<p>Сейчас создан каркас проекта <a>Local Library</a>. Мы проверили, что он запускается с использованием Node. Но главное, что вы поняли структуру проекта, и знаете, где и как добавить пути и представления для нашей локальной библиотеки.</p> + +<p lang="ru-RU">Далее мы изменим каркас, чтобы он работал как библиотечный вебсайт</p> + +<h2 id="Смотри_также">Смотри также</h2> + +<ul> + <li><a href="https://expressjs.com/en/starter/generator.html">Express application generator</a> (документация Express)</li> + <li><a href="https://expressjs.com/en/guide/using-template-engines.html">Using template engi nes with Express</a> (документация Express) </li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs")}}</p> diff --git a/files/ru/learn/server-side/express_nodejs/учебник_сайт_local_library/index.html b/files/ru/learn/server-side/express_nodejs/учебник_сайт_local_library/index.html new file mode 100644 index 0000000000..c7e821248e --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/учебник_сайт_local_library/index.html @@ -0,0 +1,73 @@ +--- +title: 'Учебник Express: сайт Local Library' +slug: Learn/Server-side/Express_Nodejs/Учебник_сайт_local_library +tags: + - Express + - Node + - nodejs + - Введение + - Для начинающих + - Серверная часть + - Учебник +translation_of: Learn/Server-side/Express_Nodejs/Tutorial_local_library_website +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">Первая статья в нашей серии практических уроков объясняет, что вы будете изучать, и предоставит обзор сайта "локальной библиотеки" ("local library"), над которым мы будем работать и развивать в последующих статьях.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Необходимые знания:</th> + <td>Прочтите <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Введение в Express</a>. Для следования статьям вам также надо будет <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">установить среду разработки Node</a>. </td> + </tr> + <tr> + <th scope="row">Задача:</th> + <td>Представить пример приложения, используемого в этом учебнике, и позволить читателям понять, какие темы будут рассмотрены. </td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">Добро пожаловать в учебник MDN «Local Library» Express (Node), в котором мы разрабатываем веб-сайт, который может использоваться для управления каталогом локальной библиотеки.</span></p> + +<div style="padding-bottom: 30px;"><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">В этой серии обучающих статей вы будете:</span></div> + +<ul> + <li>Использовать инструмент <em>Express Application Generator</em> для создания веб-сайта и скелета приложения.</li> + <li>Запускать и останавливать веб сервер Node.</li> + <li>Использовать базу данных для хранения данных вашего приложения.</li> + <li>Создавать маршруты для запросов различной информации и шаблонов ("представлений") для рендеринга данных в виде HTML для отображения в браузере.</li> + <li>Работать с формами.</li> + <li>Развертывать ваше приложение в производство.</li> +</ul> + +<p>Вы уже имеете знания о некоторых из этих тем и кратко касались других. К концу серии уроков вы должны знать достаточно, чтобы разрабатывать простые приложения Express самостоятельно.</p> + +<h2 id="Сайт_LocalLibrary">Сайт LocalLibrary</h2> + +<p><em>LocalLibrary</em> это название сайта который мы будем создавать и развивать в ходе прохождения этого курса уроков. Как и следовало ожидать, цель сайта - предоставить онлайн-каталог для небольшой локальной библиотеки, где пользователи могут просматривать доступные книги и управлять своими учётными записями.</p> + +<p>Этот пример был тщательно подобран, потому что он может масштабироваться, чтобы отображать насколько можно много или мало записей, и может использоваться для демонстрации почти любой возможности Express. Что ещё более важно, это позволяет нам обеспечить <em>управляемый</em> путь через функциональность, которая вам понадобится на любом веб-сайте:</p> + +<ul> + <li><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">В первых учебных статьях мы определим простую библиотеку, доступную <em>только для просмотра</em>, которую могут использовать члены библиотеки, чтобы узнать, какие книги доступны.</span> <span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_1">Это позволяет нам исследовать операции, общие для почти каждого сайта: чтение и отображение содержимого из базы данных</span>.</li> + <li><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">По мере нашего развития, пример библиотеки, естественно, будет расширяться, чтобы продемонстрировать более продвинутые функции веб-сайта.</span> <span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_1">Например, мы можем расширить библиотеку, чтобы разрешить создание новых книг, и использовать это, чтобы продемонстрировать, как использовать формы, а также поддерживать аутентификацию пользователей</span>.</li> +</ul> + +<p>Несмотря на то, что это очень масштабируемый пример, он называется <em><strong>Local</strong>Library,</em> потому что мы надеемся показать минимальную информацию, которая поможет быстро начать работать с Express. В результате мы будем хранить информацию о книгах, копиях книг, авторов и другой ключевой информации. Однако, мы не будем хранить информацию о других предметах, которые может предоставить библиотека, или предоставить инфраструктуру, необходимую для поддержки нескольких сайтов библиотек или других функций "большой библиотеки".</p> + +<h2 id="Я_застрял_где_я_могу_посмотреть_код">Я застрял, где я могу посмотреть код?</h2> + +<p><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">По мере того, как вы работаете над учебником, мы предоставим вам соответствующие фрагменты кода для копирования и вставки в каждой точке, а также будет другой код, который, мы надеемся, вы расширите самостоятельно (с некоторыми рекомендациями).</span></p> + +<p>Если вы застряли, вы можете найти полностью разработанную версию вебсайта <a href="https://github.com/mdn/express-locallibrary-tutorial">на Github</a>.</p> + +<h2 id="Резюме">Резюме</h2> + +<p>Теперь, когда вы знаете немного больше о сайте <em>LocalLIbrary</em> и о том, что мы будем изучать, пришло время приступить к созданию <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">скелета проекта</a>, который будет использован в нашем сайте.</p> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs")}}</p> |