Express.js — Примеры

Пример 1 — Упорядочите цепочку, чтобы ничего не выполнялось слишком рано

У сервера — публичный маршрут проверки работоспособности, защищённые заказы, журналирование запросов, разбор тела и центральный обработчик ошибок. В черновике ниже проверка стоит после маршрутов, которые она защищает, а брошенная ошибка уходит наружу; расставьте части так, чтобы каждая следующая могла опираться на предыдущую.

app.use(express.json());
app.get('/orders', listOrders);
app.post('/orders', createOrder);
app.use(requireAuth);            // слишком поздно: они уже выполнились

Решение

const app = express();
app.use(logRequests);
app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.use(express.json());         // тело читают только маршруты ниже
app.use(requireAuth);            // до защищаемых маршрутов
app.get('/orders', listOrders);
app.post('/orders', createOrder);
app.use((err, req, res, next) => {
  res.status(500).json({ code: 'internal_error' });
});

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

Пример 2 — Обработчик, достаточно тонкий, чтобы тестировать его без сервера

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

app.post('/orders', async (req, res) => {
  const items = req.body.items;
  if (!items.length) return res.status(422).json({ code: 'empty_order' });
  let total = 0;
  for (const item of items) total += item.price * item.quantity;
  const order = await db.orders.insert({ items, total });
  res.status(201).json(order);
});

Решение

// create-order.js — ни запроса, ни ответа, тест вызывает напрямую
function buildOrder(body) {
  const items = body.items || [];
  if (!items.length) return { error: 'empty_order' };
  const total = items.reduce((s, i) => s + i.price * i.quantity, 0);
  return { order: { items, total } };
}

// routes.js
app.post('/orders', async (req, res) => {
  const { error, order } = buildOrder(req.body);
  if (error) return res.status(422).json({ code: error });
  res.status(201).json(await db.orders.insert(order));
});

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