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));
});
Правила и арифметика переехали в функцию от обычных значений: тест вызывает её, не занимая порт, а обработчику остаётся лишь то, что требует фреймворка — разобранное тело, внедрённая зависимость и код состояния. Ошибаются же обычно на тестировании через настоящий сервер или на расчёте, что поле вроде аутентифицированного пользователя уже выставлено, хотя маршрут смонтирован без промежуточного слоя.