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