Tea Taste: восемь скрытых отказов бэкенда, которые нашло код-ревью
Разбор восьми стабильностных дефектов в API Tea Taste, найденных при код-ревью после июльского релиза: двойной HTTP-сервер, ошибки, которые молча проваливались мимо обработчиков, недостижимый маршрут PATCH, бессмысленный из-за прокси rate limiting, проверка живости базы и вынос секретов из образа.
Июльский релиз превратил Tea Taste в сервис, которым пользуется не только его автор: вход через VK, публичная лента, обратная связь, админ-панель. Но сервис, который работает без присмотра неделями, отличается от демо не набором функций, а тем, переживает ли он ошибки, которых не видно за пять минут показа. Код-ревью бэкенда — Express-приложения поверх MongoDB — нашло восемь таких мест. Ни одно из них не добавляет функциональности; каждое отделяет «работает у меня» от «продолжает работать». Ниже они разобраны по тому, чему учат.
Два HTTP-сервера в одном процессе
Приложение стартовало дважды. Исторический для Express-генератора файл bin/www поднимал сервер, и параллельно тот же сервер поднимал app.js — в каждом процессе слушателя оказывалось два. Второй экземпляр висел на ресурсах, дублировал обработчики жизненного цикла и создавал классы отказов, которые невозможно воспроизвести предсказуемо. Точка входа сведена к единственному app.js (его же запускают npm start, npm run dev и CMD в Docker), а bin/www удалён.
Ошибки, которые молча проваливались
Два дефекта в обработке ошибок выглядели корректно, но не останавливали запрос.
В middlewares/auth.js перед next(error) не хватало return. Неаутентифицированный запрос получал команду на 401, но выполнение не прерывалось и проваливалось дальше — в приватный контроллер, который падал на req.user._id, потому что пользователя не было. Ответ уже был поставлен в очередь, а обработчик всё равно исполнялся.
В utils/getTeaDataBy.js параметр next был написан с опечаткой. Любая ошибка базы вместо того, чтобы уйти в обработчик, бросала ReferenceError на несуществующем имени — запрос зависал без ответа. Там же statusCode - 500 (вычитание) стояло на месте statusCode = 500 (присваивание). Оба случая — про то, что путь ошибки существовал, но фактически не прекращал запрос.
Маршруты и запросы, которые ничего не делали
В routes/teaforms.js обнаружились три дублирующие регистрации маршрута, из-за которых patchTeaForm был недостижим: PATCH /create-form/:sessionId доходил до более раннего, неправильного обработчика и молча ничего не делал. Это ровно тот путь обновления, на который опирается редактирование дегустаций из июльского релиза, — ревью убедилось, что PATCH действительно достаёт до своего обработчика, а не до тени под ним.
Рядом — неверное использование Mongoose orFail в utils/delAllDocsFromCollection.js и controllers/users.js: колбэки теперь возвращают ошибку, как и требует API. И проверка дубликата ключа сравнивалась со строкой 'DuplicateKey' вместо настоящего кода ошибки Mongo 11000, так что повторный email не распознавался как дубликат и уходил в общий обработчик вместо понятного пользователю ответа.
Границы: rate limiting и доверие к прокси
Ограничение частоты запросов стояло, но не работало по существу. За внешним nginx каждый запрос нёс IP прокси, а не клиента, поэтому лимитер видел одного отправителя на всех. Включение trust proxy вернуло реальные адреса клиентов; общий лимит — 1000 запросов за 15 минут, а на /sign-in и /sign-up — жёсткие 25 за 15 минут, потому что именно эти эндпоинты имеет смысл перебирать.
Наблюдаемость: /health, логи и проверка секрета
GET /health теперь проверяет соединение с MongoDB и отдаёт 503, когда база недоступна, — оркестратор или healthcheck Docker может по этому сигналу перезапустить контейнер или увести с него трафик. Добавлены логи разрыва и восстановления соединения, логгер unhandledRejection и проверка на старте, что в production задан JWT_SECRET: приложение падает сразу при загрузке, а не тихо ломает каждый вход позже.
Сборка и секреты
Docker-часть тоже подтянута. .env больше не запекается в образ API — файл в .dockerignore и передаётся через env_file в момент запуска, так что JWT-секрет перестаёт протекать в слои образа. Установка идёт через npm ci вместо игнорировавшегося флага --frozen-lockfile — сборка воспроизводима. В compose добавлены политики перезапуска и healthcheck’и. Сборочный аргумент фронтенда REACT_APP_API_URL теперь действительно объявлен и учитывается, поэтому готовый образ указывает на правильный API, а не на дефолт.
Итог
Ничего из перечисленного не видно на экране, и всё вместе — это и есть разница между демонстрацией и сервисом. Тесты, добавленные вместе с июльским релизом (Jest и Supertest на бэкенде, Testing Library на фронтенде, запуск в GitHub Actions при каждом пуше), теперь стерегут именно эти регрессии — у обновления дегустации через PATCH, например, есть собственный тест. Функции делают продукт заметным; такие правки делают его пригодным к тому, чтобы оставить его работать.
- Репозиторий (API): github.com/nikita-konkin/tea-taste-api
- Репозиторий (фронтенд): github.com/nikita-konkin/tea-taste-frontend
- Сервис: teaform.ru
Теги
Другие обновления по проекту
Tea Taste: вход через VK, публичная лента и админ-панель — большое обновление
Разбор крупного обновления Tea Taste (teaform.ru): авторизация через VK ID, редактирование дегустаций, восстановление пароля, публичная лента с профилями авторов, обратная связь...
Tea Taste: как превратить дегустацию чая в воспроизводимые данные
Разбор идеи и функций Tea Taste — веб-приложения, которое фиксирует параметры заваривания и сенсорные впечатления по проливам и превращает субъективную оценку чая в структуриров...