Тело запроса и заголовки

Тело запроса и заголовки — то, что клиент присылает помимо метода и пути.
«Заголовки — это метаданные письма, тело — само содержимое. Express помогает читать и то, и другое.»

Чтобы принять данные формы регистрации или JSON от мобильного приложения, сервер должен прочитать тело запроса. А чтобы понять, в каком формате эти данные и кто их прислал, — заголовки. В этом уроке научимся читать тело через встроенные парсеры Express и доставать нужные заголовки.

Чтение тела запроса

Тело приходит потоком байтов. Чтобы превратить его в удобный объект, нужен middleware-парсер. Express встроил два главных:

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

app.use(express.json());                          // тело в формате JSON
app.use(express.urlencoded({ extended: true }));  // данные HTML-форм

app.post('/register', (req, res) => {
  const { email, password } = req.body;
  if (!email || !password) {
    return res.status(400).json({ error: 'нужны email и password' });
  }
  res.status(201).json({ email });
});

Без express.json() поле req.body в Express 5 будет undefined — частый источник ошибок "cannot read property of undefined".

Чтение заголовков

Заголовки доступны через req.headers (объект) или удобный метод req.get():

app.get('/whoami', (req, res) => {
  const ua = req.get('User-Agent');
  const type = req.get('Content-Type');
  res.json({ userAgent: ua, contentType: type });
});

Валидация тела своими руками

Любые входные данные нужно проверять. Соберём простой валидатор в браузере: он проходит по правилам и собирает ошибки. Express-приложения делают так же, часто с библиотекой вроде Zod или Joi:

function validate(body, rules) {
  const errors = [];
  for (const field in rules) {
    const value = body[field];
    if (rules[field].required && (value === undefined || value === '')) {
      errors.push(field + ' обязательно');
    }
    if (value && rules[field].minLength && value.length < rules[field].minLength) {
      errors.push(field + ' слишком короткое');
    }
  }
  return errors;
}

const body = { email: '[email protected]', password: '123' };
const errors = validate(body, {
  email: { required: true },
  password: { required: true, minLength: 6 }
});
console.log(errors); // ['password слишком короткое']

Эта функция — прообраз middleware-валидатора, который мы прикрутим к маршрутам в следующем разделе.

Как работает под капотом

Когда приходит запрос с телом, Node отдаёт данные как поток. Middleware express.json() подписывается на события потока, накапливает чанки, а в конце парсит собранную строку через JSON.parse и кладёт результат в req.body. Поэтому парсер должен стоять до маршрутов — иначе к моменту обработчика тело ещё не разобрано.

Частые ошибки

  • Парсер после маршрутов. app.use(express.json()) должен идти раньше маршрутов, которым нужно тело.
  • Имена заголовков с регистром. В req.headers все ключи приведены к нижнему регистру; req.get() регистронезависим — используй его.
  • Слепое доверие телу. Никогда не передавай req.body в базу без валидации.

Best practices

  • Подключай express.json() и express.urlencoded() один раз в начале приложения.
  • Ограничивай размер тела (опция limit), чтобы не получить отказ в обслуживании огромным запросом.
  • Валидируй входные данные до бизнес-логики и возвращай 400 с понятным сообщением.

Итоги

Тело читается встроенными парсерами и попадает в req.body, заголовки — в req.headers и req.get(). Главное правило: парсеры — до маршрутов, данные — всегда под подозрением. Эти парсеры — наш первый пример middleware, и в следующем разделе мы разберём этот механизм во всю глубину.

Content-Type решает всё

Какой парсер сработает, определяет заголовок Content-Type запроса. express.json() разбирает тело только если клиент прислал application/json; express.urlencoded() — только для application/x-www-form-urlencoded (обычные HTML-формы). Если заголовок не совпал, парсер просто пропускает запрос, и тело остаётся неразобранным. Отсюда классическая ловушка: фронтенд шлёт JSON, но забывает выставить Content-Type, и сервер видит пустое тело. При отладке "почему req.body пуст" первым делом смотри именно на этот заголовок запроса — в девяти случаях из десяти причина там.

Проверьте себя
1. Почему express.json() должен стоять до маршрутов?
AЭто требование стиля
BИначе к моменту обработчика тело ещё не разобрано и req.body пуст
CExpress иначе выдаст синтаксическую ошибку
DПорядок middleware не имеет значения
2. Где находятся данные тела запроса после express.json()?
AВ req.query
BВ req.params
CВ req.body
DВ req.headers