Доброго времени суток, друзья!

Представляю вашему вниманию перевод второй части Руководства по 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, но такие куки требуют дополнительной настройки с помощью прокси-сервера.

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

Следите за обновлениями. Благодарю за внимание и хороших выходных.