Код-ревью Middle HTTP-сервер, Express, NestJS спрашивают часто обновлено

Код-ревью: Express-эндпоинт заказа

Вопрос

Эндпоинт создаёт заказ. Найди баги, проблемы безопасности и производительности.

app.post('/orders', (req, res) => {
  const { userId, items } = req.body;
  let total = 0;
  for (const it of items) {
    const product = db.querySync('SELECT * FROM products WHERE id = ' + it.id);
    total += product.price * it.qty;
  }
  db.query('INSERT INTO orders(user_id, total) VALUES ($1, $2)', [userId, total])
    .then((order) => res.status(200).json(order));
  if (total > 100000) res.json({ warning: 'big order' });
});

app.use((err, req, res) => {
  res.status(500).send(err.stack);
});

Коротко

Классический «собесный» эндпоинт, где ошибок больше, чем строк. Порядок разбора на собесе: безопасность (кто делает запрос, инъекция, утечка стека), корректность (валидация, двойной ответ, необработанный промис), потом производительность (sync-запрос, N+1).

Что должен найти ревьюер

  1. Безопасностьстрока 2
    userId берётся из тела запроса: любой создаст заказ от чужого имени. Пользователь должен браться из сессии/токена (req.user).
  2. Ошибкастрока 4
    Нет валидации items: не массив, пустой, отрицательное или дробное qty (отрицательная сумма заказа).
  3. Безопасностьстрока 5
    SQL-инъекция: it.id вклеен в строку запроса. Нужны параметры $1.
  4. Производительностьстрока 5
    Синхронный запрос к БД блокирует event loop, и это N+1: по запросу на товар. Нужен один WHERE id = ANY($1) асинхронно.
  5. Ошибкастрока 6
    Несуществующий товар даёт product undefined и падение; деньги считаются во float, нужны целые копейки или numeric.
  6. Ошибкастрока 8
    Промис без catch: ошибка вставки станет unhandled rejection, клиент не получит ответа. Плюс создание ресурса это 201, а не 200.
  7. Ошибкастрока 10
    Второй ответ на тот же запрос: res.json вызывается ещё раз, будет ERR_HTTP_HEADERS_SENT.
  8. Ошибкастрока 13
    У error-middleware Express должно быть 4 аргумента (err, req, res, next), иначе это обычный middleware и ошибки сюда не попадут.
  9. Безопасностьстрока 14
    Стек ошибки уходит клиенту: утечка путей, версий и SQL. Клиенту общий текст и id запроса, стек в лог.

Разбор

Классический «собесный» эндпоинт, где ошибок больше, чем строк. Порядок разбора на собесе: безопасность (кто делает запрос, инъекция, утечка стека), корректность (валидация, двойной ответ, необработанный промис), потом производительность (sync-запрос, N+1).

Как выглядит исправленная версия: пользователь из req.user, тело валидируется схемой, товары грузятся одним параметризованным запросом, цены берутся из БД (а не от клиента), сумма считается в копейках, вставка заказа и позиций в одной транзакции, ответ 201 с телом заказа, ошибки уходят в next(err) и общий обработчик.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.