Доброго времени суток, друзья!
Представляю вашему вниманию перевод второй части Руководства по Express — веб-феймворку для Node.js автора Flavio Copes.
Предполагается, что вы знакомы с Node.js. Если нет, то прошу сюда.
Без дальнейших предисловий.
15. Шаблонизация
Express умеет работать с серверными шаблонизаторами (server-side template engines).
Шаблонизатор позволяет динамически генерировать HTML-разметку посредством передачи данных в представление (view).
По умолчанию Express использует шаблонизатор Pug, который раньше назывался Jade.
Несмотря на то, что с момента выхода последней версии Jade прошло почти 3 года, он все еще поддерживается в целях обеспечения обратной совместимости.
В новых проектах следует использовать Pug или другой шаблонизатор. Вот официальный сайт Pug: pugjs.org.
Среди других шаблонизаторов, можно назвать Handlebars, Mustache, EJS и др.
Использование Pug
Для использования Pug, его сначала нужно установить:
npm i pug
Затем его следует добавить в Express:
const express = require('express')
const app = express()
app.set('view engine', 'pug')
После этого мы можем создавать шаблоны в файлах с расширением .pug.
Создадим представление about:
app.get('/about', (req, res) => {
res.render('about')
})
И шаблон в views/about.pug:
p Привет от Pug
Данный шаблон создаст тег «p» с текстом «Привет от Pug».
Интерполировать переменные можно так:
app.get('/about', (req, res) => {
res.render('about', { name: 'Иван' })
})
p #{name} говорит привет
Подробнее о Pug мы поговорим в следующем разделе.
При использовании шаблонизатора для динамической генерации страниц можно столкнуться с некоторыми проблемами, особенно, когда возникает необходимость преобразовать HTML, например, в Pug. Для этого в сети существуют готовые решения. Вот одно из них: html-to-pug.com.
Использование Handlebars
Попробуем использовать Handlebars вместо Pug.
Устанавливаем его с помощью npm i handlebars.
В директории views создаем файл about.hbs следующего содержания:
{{name}} говорит привет
Перепишем код Express:
const express = require('express')
const app = express()
const hbs = require('hbs')
app.set('view engine', 'hbs')
app.set('views', path.join(__dirname, 'views'))
app.get('/about', (req, res) => {
res.render('about', { name: 'Иван' })
})
app.listen(3000, () => console.log('Сервер готов'))
Вы также можете рендерить React на стороне сервера с помощью пакета express-react-views.
Устанавливаем данный пакет:
npm i express-react-views
Теперь вместо Handlebars укажем Express использовать express-react-views в качестве движка для обработки jsx-файлов:
const express = require('express')
const app = express()
app.set('view engine', 'jsx')
app.engine('jsx', require('express-react-views').createEngine())
app.get('/about', (req, res) => {
res.render('about', { name: 'Иван' })
})
app.listen(3000, () => console.log('Сервер запущен'))
В директории views создаем файл about.jsx:
const React = require('react')
class HelloMessage extends React.Component {
render() {
return <div>{this.props.name} говорит привет</div>
}
}
module.exports = HelloMessage
16. Справочник по Pug
Что такое Pug? Это шаблонизатор или движок для динамического рендеринга HTML-разметки, используемый Express по умолчанию.
Установка:
npm i pug
Настройка Express:
const path = require('path')
cpnst express = require('express')
const app = express()
app.set('view engine', 'pug')
app.set('views', path.join(__dirname, 'views'))
app.get('/about', (req, res) => {
res.render('about', { name: 'Иван' })
})
Шаблон (about.pug):
p #{name} говорит привет
Передача функции, возвращающей значение:
app.get('about', (req, res) => {
res.render('about', { getName: () => 'Иван' })
})
p #{getName()} говорит привет
Добавление элементу идентификатора или класса:
p#title
p.title
Установка doctype:doctype html
Мета-теги:
html
head
meta(charset='utf-8')
meta(http-equiv='X-UA-Compatible', content='IE=edge')
meta(name='description', content='Описание')
meta(name='viewport', content='width=device-width, initial-scale=1')
Добавление скриптов или стилей:
html
head
link(rel="stylesheet", href="style.css")
script(src="script.js", defer)
Встроенные скрипты:
script alert('тест')
script
(function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]= function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date; e=o.createElement(i);r=o.getElementsByTagName(i)[0]; e.src='//www.google-analytics.com/analytics.js'; r.parentNode.insertBefore(e,r)}(window,document,'script','ga')); ga('create','UA-XXXXX-X');ga('send','pageview');
Циклы:
ul
each color in ['Red', 'Green', 'Blue']
li= color
ul
each color, index in ['Red', 'Green', 'Blue']
li= 'Номер цвета ' + index + ':' + color
Условия:
if name
h2 #{name} говорит привет
else
h2 Привет
if name
h2 #{name} говорит привет
else if anotherName
h2 #{anotherName} говорит привет
else
h2 Привет
Переменные:
- var name = 'Иван'
- age = 30
- var petr = { name: 'Петр' }
- var friends = ['Иван', 'Петр']
Инкремент:
age++
Приваивание переменной элементу:
p= name
span= age
Перебор переменных:
for friend in friends
li= friend
ul
each friend in friends
li= friend
Получение количества элементов:p #{values.length}
While:
- var n = 0
ul
while n <= 5
li= n++
Включение одного файла в другой:
include otherfile.pug
Определение блоков.
Хорошо организованная система шаблонов предполагает создание базового шаблона и его расширение другими шаблонами по мере необходимости.
Базовый шаблон расширяется с помощью блоков:
html
head
link(rel="stylesheet", href="style.css")
script(src="script.js", defer)
body
block header
block main
h1 Домашняя страница
p Добро пожаловать
block footer
Расширение базового шаблона.
Шаблон расширяется с помощью ключевого слова extends:
extends home.pug
После этого нужно пеопределить блоки. Весь контент шаблона должен содержаться в блоках, иначе движок не будет знать, куда его поместить:
extends home.pug
block main
h1 Другая страница
p Привет
ul
li Раз элемент списка
li Два элемент списка
Можно переопределять как один, так и несколько блоков. Те блоки, которые не переопределяются, сохраняют оригинальное содержимое.
Комментарии.
Видимые (сохраняются в разметке):
// однострочный комментарий
//
многострочный
комментарий
Невидимые (удаляются при рендеринге):
//- однострочный комментарий
//-
многострочный
комментарий
17. Middleware (промежуточный слой, промежуточное программное обеспечение)
Middleware — это промежуточный слой между получением запроса от клиента и отправкой ему ответа. Другими словами, это функция, которая выполняет дополнительные операции в процессе маршрутизации после получения запроса и перед выполнением основных операций сервером.
Обычно, middleware применяется для обработки запроса/ответа либо для перехвата запроса перед его обработкой роутером. Middleware в общем виде выглядит так:
app.use((req, res, next) => {/* */})
Метод next() служит для передачи запроса следующему middleware, если в этом есть необходимость.
Если опустить next() в middleware, то обработка ответа завершится и он будет отправлен клиенту.
Middleware редко пишется вручную, как правило, в качестве таковых используются npm-пакеты.
Примером подобного пакета является cookie-parser, применяемый для преобразования куки в объект req.cookies. Данный пакет устанавливается с помощью npm i cookie-parser и используется следующим образом:
const express = require('express')
const app = express()
const cookieParser = require('cookie-parser')
app.get('/', (req, res) => res.send('Привет!'))
app.use(cookieParser())
app.listen(3000, () => console.log('Сервер готов'))
Middleware может использоваться в определенном маршрутизаторе. Для этого он передается роутеру в качестве второго аргумента:
const myMiddleware = (req, res, next) => {
// ...
next()
}
app.get('/', myMiddleware, (req, res) => res.send('Привет!'))
Для того, чтобы сохранить данные, сформированные middleware, для их передачи другим middleware или обработчику запроса, используется объект Request.locals. Он позволяет записывать данные в текущий запрос:
req.locals.name = 'Иван'
18. Обработка статических файлов
Обычной практикой является хранение изображений, стилей и других файлов в директории public и использование их на верхнем уровне:
const express = require('express')
const app = express()
app.use(express.static('public'))
// ...
app.listen(3000, () => console.log('Сервер готов'))
Если в директории public имеется файл index.html, он будет отправлен в ответ на запрос к localhost:3000.
19. Отправка файлов
Express предоставляет удобный метод для отправки файлов в ответ на запрос — Reaponse.download().
После обращения клиента к маршруту, отправляющему в ответ файл, клиенту будет направлен запрос на скачивание файла.
Метод Response.download() позволяет отправлять файлы в ответ на запрос вместо отображения страницы:
app.get('/', (req, res) => res.download('./file.pdf'))
В контексте приложения это выглядит так:
const express = require('express')
const app = express()
app.get('/', (req, res) => res.download('./file.pdf'))
app.listen(3000, () => console.log('Сервер готов'))
При отправке файла можно определить его название:
res.download('./file.pdf', 'some-custom-name.pdf')
Третим параметром рассматриваемого метода является колбэк, вызываемый после отправки файла:
res.download('./file.pdf', 'some-custom-name.pdf', error => {
if (error) {
// обрабатываем ошибку
} else {
console.log('Файл успешно отправлен')
}
})
20. Сессии
По умолчанию запросы в Express являются одноразовыми, т.е. запросы не могут связываться между собой. Не существует способа определить, что повторный запрос отправлен тем же клиентом.
Пользователи не могут быть идентифицированы нативными средствами.
Вот где в игру вступают сессии.
После реализации названного механизма, каждому пользователю и сайту присваивается сессия, хранящая пользовательское состояние.
Мы будем использовать express-session, поддерживаемый командой Express.
Устанавливаем его:
npm i express-session
Инициализируем:
const session = require('express-session')
Добавляем в Express в качестве middleware:
const express = require('express')
const session = require('express-session')
const app = express()
app.use(session(
'secret': '343ji43j4n3jn4jk3n'
))
После этого все запросы к приложению будут сессионными.
Единственным обязательным параметром является secret, но существуют и другие. secret — произвольная случайная строка, уникальная для приложения.
Сессия включается в состав запроса, доступ к ней можно получить через req.session:
app.get('/', (req, res, next) => {
// req.session
})
Данный объект может быть использован как для получения данных, так и для их записи:
req.sessions.name = 'Иван'
console.log(req.sessions.name) // Иван
При записи данные сериализуются (преобразуются в формат JSON), так что можно смело использовать вложенные объекты.
Сессии используются для передачи данных другим middleware или для их извлечения при последующих запросах.
Где хранятся сессионные данные? Это зависит от того, как настроен модуль express-session.
Такие данные могут храниться в:
- памяти — только при разработке
- базе данных, например, MySQL или Mongo
- кэше, например, Redis или Memcached
В любом случае идентификатор сессии записывается в куки, а сами данные хранятся на сервере. Куки с идентификатором сессии включается в каждый запрос клиента.
По идентификатору сервер осуществляет поиск хранящихся на нем данных.
По умолчанию для хранения данных используется память, но это подходит только для разработки.
Лучшим решением является Redis, однако, она требует дополнительной настройки инфраструктуры.
Другим популярным решением является пакет cookie-session. Он сохраняет данные в куки на стороне клиента. Данный способ использовать не рекомендуется, поскольку данные будут включаться в каждый запрос клиента и размер данных ограничен 4 Кб. Кроме того, обычные куки не подходят для хранения конфиденциальной информации. Существуют безопасные куки, передаваемые по протоколу HTTPS, но такие куки требуют дополнительной настройки с помощью прокси-сервера.
На сегодня это все. В следующей и последней части мы поговорим о валидации данных, полученных от пользователя, обработке форм, извлечении добавленных в них файлов, а также рассмотрим, как сделать сервер безопасным.
Следите за обновлениями. Благодарю за внимание и хороших выходных.
DmitryKoterov
Экспресс зря выбрал в качестве дефолтного языка шаблонов Пук. Экспресс и так древен и страшен, как ядерная зима (весь коллбэками обвешан, это в 2020-м то! — прямо как extensions api в Chrome), а такой выбор еще ухудшает ему карму.
MarcusAurelius
Нужно бы написать статью, про мидлвары, как антипаттерн, провоцирующий примеси, асинхронную работу с состоянием, приводящую к состоянию гонки и коррапшену данных, а так же, навязывающий смешенее уровней абстракции и портяночный код. Уже задолбался все это объяснять, насмотрятся своих клипов в ютюбе… а про паттерн chain of responsibility (неправильной реализацией которого являются мидлвары) среди нодовцев мало кто слышал.