Express.js
Express
Что это такое
Express.js — небольшой веб-фреймворк для Node. Входящий запрос он сопоставляет с обработчиком по методу и пути, затем по порядку выполняет стек функций промежуточной обработки и отдаёт обработчику объект запроса вместе со способом отправить ответ. Этим фреймворк почти и исчерпывается, а мнений он намеренно не навязывает: структуру, валидацию, обработку ошибок и доступ к базе данных выбирает приложение, а не сам фреймворк.
Почему это важно
Достоинство фреймворка — в его размере. Для большинства задач фреймворк-специфичного способа просто нет, поэтому приложение на Express остаётся в основном обычным JavaScript: читать его легко, а застрять в нём трудно. Хорошая абстракция для того, что нужно каждому запросу, — промежуточные обработчики: разбор тела, журналирование, аутентификация, ограничение частоты. Порядок их выполнения задан явно, а не спрятан внутри. Однако у отсутствия мнений есть и обратная сторона: два проекта на Express могут почти не иметь общей структуры, а команде, которой нужны соглашения, придётся придумать их и поддерживать самой.
Ключевые идеи
Обработчик выбирается по методу и пути
Сопоставляются маршруты в порядке объявления; путь при этом может содержать параметры, которые извлекаются для обработчика. Побеждает первое совпадение, и потому порядок решает: широкий шаблон, объявленный раньше, заслоняет конкретный, объявленный позже. Отсюда и берутся маршруты, которые как будто игнорируются.
get /orders/:id -> карточка заказа
post /orders -> создание
use /orders -> выполняется для любого метода под этим префиксом
Промежуточные обработчики — цепочка, и порядок и есть весь замысел
Получив запрос, промежуточный обработчик может его изменить, а затем либо завершить ответ, либо передать управление дальше. Всё это — аутентификация, разбор тела, журналирование, обработка ошибок — тоже промежуточные обработчики; на что каждый из них вправе опираться, решает порядок. Классическая ошибка здесь одна: обработчик аутентификации ставят после маршрута, который он должен защищать.
Ошибки идут по отдельной цепочке
Обработчик ошибок узнаётся по четырём аргументам, и срабатывает он тогда, когда любой более ранний обработчик передаст ошибку дальше. Исключение, брошенное в асинхронном обработчике, до него не доходит — если, конечно, его не поймать и не передать дальше, — поэтому путь ошибки приходится прописывать явно для каждого асинхронного маршрута. Централизованный обработчик ошибок полезен, но получает он только то, что ему передали.
Структура — ответственность приложения
Мнений фреймворк не навязывает, поэтому форму проекта задаёт команда: где живут маршруты, как внедряются сервисы, как читается конфигурация. Лучше других масштабируется подход, при котором определения маршрутов остаются тонкими, логика уезжает в тестируемые функции, а зависимости внедряются вместо импорта одиночек. Именно от этого зависит, удастся ли протестировать обработчик напрямую, не поднимая сервер.
Объект запроса — общее место для контекста
К запросу промежуточные обработчики часто прикрепляют аутентифицированного пользователя, идентификатор запроса или разобранное тело, чтобы дальше его читали другие слои и сам обработчик маршрута. Удобство оборачивается неявной связанностью: если маршрут смонтирован без промежуточного слоя, обработчик, который рассчитывал на выставленное им поле, падает. Спасает положение небольшой и задокументированный набор таких полей.
Это тонкий слой над встроенным сервером
HTTP-сервер среды выполнения фреймворк не заменяет: всё, что она предлагает — потоки, заголовки, «сырые» сокеты, — по-прежнему доступно. Это выручает, когда ответ нужно отдавать потоком или обрабатывать нестандартный метод, и напоминает, что фреймворк — удобство, а не платформа.
Практика
Выстройте цепочку промежуточных обработчиков осознанно
Соберите сервер с журналированием запросов, разбором тела, защищённым и публичным маршрутами и центральным обработчиком ошибок, а каждый обработчик разместите так, чтобы ничего не выполнялось раньше времени.
Готово, когда: неаутентифицированный запрос к защищённому маршруту отклоняется ещё до разбора тела, а брошенная ошибка даёт структурированный ответ, а не трассировку стека.
Протестируйте обработчик без сервера
Возьмите один маршрут, вынесите его логику в функцию, которая принимает запрос и возвращает результат, и протестируйте её напрямую.
Готово, когда: поведение маршрута покрыто тестами, которые ни разу не занимают порт.
Частые ошибки
- Объявление широкого маршрута раньше конкретного — конкретный не совпадёт никогда; исправить: идите от конкретных маршрутов к общим.
- Размещение аутентификации после защищаемых маршрутов — защищённый обработчик срабатывает без проверки; исправить: поставьте проверку перед маршрутом.
- Позволение асинхронной ошибке уйти наружу — центральный обработчик её не увидит, и запрос зависнет; исправить: ловите ошибки в каждом асинхронном маршруте и передавайте их дальше.
- Бизнес-логика в обработчиках маршрутов — без сервера такую логику не протестировать; исправить: вынесите её в функции, а обработчики оставьте тонкими.
- Предположение, что промежуточный слой выполнился — обработчик читает поле запроса, выставленное не всегда; исправить: прикрепляйте меньше полей и документируйте их.
Что читать дальше
- Node.js (05-backend-data/02-nodejs) — среда выполнения, на которой стоит этот фреймворк; начинать стоит с неё.
- RESTful API (05-backend-data/06-restful-api) — соглашения о ресурсах и кодах состояния, которых этим маршрутам и следует держаться.
- Проектирование API (OpenAPI / Swagger) (05-backend-data/09-api-design-openapi-swagger) — о том, как описать эти эндпоинты ещё до реализации.