Skip to content

Express.js: Веб-фреймворк для Node.js

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

Ключевые особенности Express.js

  • Минималистичность: Express предоставляет базовую структуру веб-приложения, оставляя разработчику свободу выбора дополнительных компонентов и библиотек.
  • Маршрутизация: Мощная система маршрутизации позволяет определять, как приложение отвечает на клиентские запросы к определенным URL-адресам (маршрутам).
  • Промежуточное ПО (Middleware): Express использует концепцию промежуточного ПО — функций, которые имеют доступ к объекту запроса (req), объекту ответа (res) и следующей функции промежуточного ПО в цикле "запрос-ответ" приложения. Промежуточное ПО может выполнять различные задачи, такие как логирование, аутентификация, обработка ошибок и т.д.
  • Система шаблонов: Поддержка различных движков шаблонов (например, Pug, EJS, Handlebars) для динамической генерации HTML.
  • Статические файлы: Простое обслуживание статических файлов (HTML, CSS, JavaScript, изображения и др.).
  • Надежность и производительность: Express построен на Node.js, что обеспечивает его неблокирующую и асинхронную природу, делая приложения масштабируемыми и производительными.
  • Огромное сообщество и экосистема: Большое количество сторонних пакетов и промежуточного ПО доступны через npm, что позволяет легко расширять функциональность Express.

Установка

Прежде чем начать использовать Express, вам необходимо установить его в свой проект Node.js. Убедитесь, что у вас уже есть файл package.json (если нет, создайте его с помощью npm init -y).

bash
npm install express --save

Флаг --save добавит Express в список зависимостей вашего файла package.json.

Основы использования Express.js

Создание Express-приложения

javascript
// app.js
const express = require('express');
const app = express();
const port = 3000;

// Определяем маршрут для корневого URL ('/')
app.get('/', (req, res) => {
  res.send('Привет, мир от Express!');
});

// Запускаем сервер и начинаем прослушивать запросы на указанном порту
app.listen(port, () => {
  console.log(`Сервер запущен на http://localhost:${port}`);
});
  1. Импортируем модуль express.
  2. Создаем экземпляр Express-приложения с помощью express().
  3. Определяем маршрут с помощью app.get(). Этот маршрут обрабатывает HTTP GET-запросы к корневому URL (/).
    • Первый аргумент — путь маршрута.
    • Второй аргумент — функция-обработчик маршрута (route handler), которая принимает объекты запроса (req) и ответа (res). В этом примере мы отправляем текстовый ответ клиенту с помощью res.send().
  4. Запускаем сервер с помощью app.listen(), указывая порт и необязательную функцию обратного вызова, которая выполняется после успешного запуска сервера.

Чтобы запустить это приложение, выполните команду node app.js в терминале. Затем вы можете открыть браузер и перейти по адресу http://localhost:3000/, чтобы увидеть сообщение "Привет, мир от Express!".

Маршрутизация

Express предоставляет различные методы для определения маршрутов, соответствующие HTTP-методам (GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD).

javascript
// Обработка GET-запроса на '/users'
app.get('/users', (req, res) => {
  res.send('Список пользователей');
});

// Обработка POST-запроса на '/users' (например, для создания нового пользователя)
app.post('/users', (req, res) => {
  res.send('Создание нового пользователя');
});

// Обработка GET-запроса на '/users/:id', где ':id' — это параметр маршрута
app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  res.send(`Информация о пользователе с ID: ${userId}`);
});

// Обработка PUT-запроса на '/users/:id' (например, для обновления пользователя)
app.put('/users/:id', (req, res) => {
  const userId = req.params.id;
  res.send(`Обновление пользователя с ID: ${userId}`);
});

// Обработка DELETE-запроса на '/users/:id' (например, для удаления пользователя)
app.delete('/users/:id', (req, res) => {
  const userId = req.params.id;
  res.send(`Удаление пользователя с ID: ${userId}`);
});
  • Параметры маршрута (:param) захватывают значения из URL и доступны через req.params.
  • Строка запроса (?key=value&anotherKey=anotherValue) доступна через req.query.
javascript
// Пример использования req.query на маршруте '/search?q=keywords&page=2'
app.get('/search', (req, res) => {
  const query = req.query.q;
  const page = req.query.page;
  res.send(`Результаты поиска по запросу: "${query}", страница: ${page}`);
});

Промежуточное ПО (Middleware)

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

  • Логирование: Запись информации о каждом запросе.
  • Аутентификация: Проверка подлинности пользователя.
  • Авторизация: Проверка прав доступа пользователя.
  • Парсинг тела запроса: Обработка данных, отправленных в теле запроса (например, JSON, URL-encoded).
  • Обработка ошибок: Перехват и обработка ошибок.

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

Встроенное промежуточное ПО:

  • express.static(root, [options]): Обслуживает статические файлы из указанной директории.
javascript
// Обслуживание статических файлов из директории 'public'
app.use(express.static('public'));
  • express.json([options]): Парсит входящие запросы с JSON-телом. Доступ к распарсенным данным через req.body.
javascript
app.use(express.json());
  • express.urlencoded({ extended: false }): Парсит входящие запросы с URL-encoded телом (обычно из HTML-форм). Доступ к распарсенным данным через req.body.
javascript
app.use(express.urlencoded({ extended: false }));

Пользовательское промежуточное ПО:

javascript
// Промежуточное ПО уровня приложения (выполняется для всех запросов)
app.use((req, res, next) => {
  console.log('Прошел запрос:', req.method, req.url);
  next(); // Передает управление следующему промежуточному ПО или обработчику маршрута
});

// Промежуточное ПО уровня маршрута (выполняется только для запросов к '/admin')
const adminMiddleware = (req, res, next) => {
  const isAdmin = true; // Здесь должна быть реальная логика проверки
  if (isAdmin) {
    next();
  } else {
    res.status(403).send('Доступ запрещен');
  }
};

app.get('/admin', adminMiddleware, (req, res) => {
  res.send('Панель администратора');
});

// Промежуточное ПО обработки ошибок (определяется с четырьмя аргументами: err, req, res, next)
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Что-то пошло не так!');
});

Обработка статических файлов

Как упоминалось выше, express.static() используется для обслуживания статических файлов. Предположим, у вас есть директория public в корне вашего приложения со следующей структурой:

public/
├── css/
│   └── style.css
└── images/
    └── logo.png

Вы можете получить доступ к этим файлам через URL-адреса /css/style.css и /images/logo.png соответственно, после использования app.use(express.static('public')).

Использование движков шаблонов

Express поддерживает множество движков шаблонов, которые позволяют динамически генерировать HTML. Для использования движка шаблонов необходимо:

  1. Установить соответствующий пакет (например, npm install pug).
  2. Настроить Express на использование этого движка.
  3. Создать файлы шаблонов.
  4. Использовать метод res.render() для отображения шаблонов.

Пример использования Pug (ранее Jade):

javascript
// Установка: npm install pug

app.set('view engine', 'pug'); // Устанавливаем Pug в качестве движка шаблонов
app.set('views', './views'); // Указываем директорию с файлами шаблонов

app.get('/template', (req, res) => {
  res.render('index', { title: 'Привет', message: 'Добро пожаловать на мою страницу!' });
});

Здесь res.render('index', { ... }) отобразит файл index.pug, находящийся в директории views, и передаст ему объект с данными (title и message).

Стороннее промежуточное ПО

Существует огромное количество стороннего промежуточного ПО для Express, доступного через npm, которое может выполнять различные задачи, такие как:

  • body-parser (устаревший, теперь встроен в Express): Парсинг тела запроса (JSON, URL-encoded, raw, text).
  • cookie-parser: Парсинг и установка куки.
  • express-session: Управление сессиями.
  • passport: Аутентификация.
  • cors: Обработка CORS (Cross-Origin Resource Sharing).
  • morgan: Логирование HTTP-запросов.

Для использования стороннего промежуточного ПО его необходимо установить с помощью npm и затем подключить к вашему приложению с помощью app.use().

Production: безопасность и надежность

Express дает базовый каркас, но “из коробки” не делает приложение безопасным и готовым к продакшену. Обычно добавляют набор практик, которые закрывают самые частые риски.

Минимальный security baseline

js
const express = require('express')
const app = express()

app.disable('x-powered-by')
app.use(express.json({ limit: '1mb' }))

Рекомендации:

  • отключайте заголовок X-Powered-By;
  • ограничивайте размер тела запроса, особенно для публичных API;
  • аккуратно настраивайте CORS: разрешайте только нужные домены и методы.

Заголовки безопасности и защита от злоупотреблений

Часто подключают пакеты:

  • helmet — безопасные HTTP-заголовки;
  • express-rate-limit — ограничение частоты запросов.
bash
npm i helmet express-rate-limit
js
const helmet = require('helmet')
const rateLimit = require('express-rate-limit')

app.use(helmet())

app.use(
  rateLimit({
    windowMs: 60_000,
    max: 100
  })
)

Валидация входных данных

Всегда валидируйте:

  • параметры маршрута (req.params);
  • query (req.query);
  • тело запроса (req.body).

Нужно защищаться и от “плохих типов”, и от семантики (пустые значения, слишком длинные строки, невалидные enum-значения).

Обработка ошибок и async

Сделайте единый обработчик ошибок и старайтесь возвращать единый формат ответа.

js
app.use((err, req, res, next) => {
  const status = err.statusCode ?? 500
  res.status(status).json({
    message: err.message ?? 'Internal Server Error'
  })
})

Для асинхронных обработчиков удобно использовать обертку, чтобы не писать try/catch в каждом маршруте:

js
const asyncHandler = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next)

app.get(
  '/users',
  asyncHandler(async (req, res) => {
    const users = await loadUsers()
    res.json(users)
  })
)

Graceful shutdown

В продакшене процесс должен корректно завершаться (например, при деплое). Минимально — обработка SIGTERM:

js
const server = app.listen(3000)

process.on('SIGTERM', () => {
  server.close(() => process.exit(0))
})

Структура Express-приложения

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

my-express-app/
├── node_modules/
├── public/         // Статические файлы (CSS, JS, images)
├── routes/         // Файлы с определениями маршрутов
│   ├── users.js
│   └── products.js
├── middleware/     // Пользовательское промежуточное ПО
├── views/          // Файлы шаблонов
├── app.js          // Основной файл приложения
├── package.json
└── package-lock.json

Заключение

Express.js предоставляет мощный и гибкий набор инструментов для создания веб-приложений и API на Node.js. Его минималистичная природа и концепция промежуточного ПО позволяют разработчикам легко расширять функциональность и строить приложения любой сложности. Благодаря огромному сообществу и богатой экосистеме, Express остается одним из самых популярных и востребованных фреймворков для Node.js.