FastifyMiddleTechnical

Как Fastify обрабатывает асинхронные обработчики маршрутов по сравнению с Express?

Fastify нативно поддерживает async/await в обработчиках: брошенный Error автоматически превращается в JSON-ответ с кодом 500. Express требует явного вызова next(err), иначе ошибка «провалится» и сервер зависнет.

Как Fastify обрабатывает async-обработчики

В Fastify обработчик маршрута может быть обычной async-функцией, возвращающей значение. Fastify сам сериализует возвращаемое значение через fast-json-stringify по схеме маршрута и отправляет ответ. Если async-функция выбрасывает исключение, Fastify перехватывает его через внутренний try/catch и передаёт в setErrorHandler. Явно вызывать reply.send() не нужно — хотя это тоже допустимо.

Сравнение с Express

В Express async-обработчик не перехватывается автоматически. Если внутри async (req, res, next) => {} выбрасывается ошибка, Express «не видит» её, если не вызвать next(err) вручную или не обернуть весь обработчик в try/catch. Это классический источник утечек запросов (hanging requests) — сервер просто не отвечает клиенту.

// Fastify: бросить достаточно
fastify.get("/users/:id", async (request, reply) => {
  const user = await db.findUserById(request.params.id);
  if (!user) throw fastify.httpErrors.notFound("User not found");
  return user; // автоматически сериализуется
});

// Express: нужен явный next(err)
app.get("/users/:id", async (req, res, next) => {
  try {
    const user = await db.findUserById(req.params.id);
    if (!user) return res.status(404).json({ error: "Not found" });
    res.json(user);
  } catch (err) {
    next(err); // без этого запрос зависнет
  }
});

Lifecycle и hook-интеграция

Fastify выполняет async-обработчики внутри строгого lifecycle: onRequestpreParsingpreValidationpreHandler → handler → preSerializationonSendonResponse. Каждый hook тоже может быть async-функцией и выбрасывать ошибки. Хук onError позволяет перехватывать ошибки до отправки ответа — например, для логирования или трансформации.

fastify.setErrorHandler(async (error, request, reply) => {
  request.log.error({ err: error, reqId: request.id }, "Unhandled error");
  const statusCode = error.statusCode ?? 500;
  return reply.status(statusCode).send({
    error: statusCode < 500 ? error.message : "Internal Server Error",
  });
});

Параллельные async-операции

Поскольку Fastify не блокирует event loop между хуками, несколько параллельных запросов обрабатываются конкурентно. Важно: если в async-обработчике используется reply.hijack(), Fastify полностью передаёт управление разработчику — автоматической сериализации и перехвата ошибок не будет.

Подводные камни

  • Возврат undefined из async-обработчика без reply.send() отправит пустой 200 — часто нежелательно; лучше всегда возвращать явное значение или использовать reply.
  • Использование reply.send() и возврата значения одновременно приведёт к ошибке «Reply already sent».
  • Ошибки в onResponse-хуке не могут изменить ответ — он уже отправлен; перехватывать критические ошибки нужно в onSend.
  • В Express без express-async-errors или ручного try/catch async-ошибки «глотаются» — сервер виснет без ответа клиенту.
  • Fastify fast-json-stringify сериализует только поля, описанные в response schema; лишние поля удаляются — это фича, но может удивить.
  • Не путать throw new Error() с throw fastify.httpErrors.badRequest(): первое даёт 500, второе — 400 с правильным statusCode.
  • reply.hijack() отключает автоматику — при использовании SSE или WebSocket нужно самостоятельно завершать соединения в onClose.
  • Тяжёлые синхронные операции внутри async-обработчика блокируют event loop для всех параллельных запросов.

Common mistakes

  • Дает общий ответ про Node.js и не называет конкретные API Fastify.
  • Не объясняет, где в lifecycle находится async handlers в Fastify и Express.
  • Не разделяет validation, authorization, business logic и persistence.
  • Игнорирует ошибки, лимиты входных данных, observability и тестирование.

What the interviewer is testing

  • Может объяснить async handlers в Fastify и Express на примере кода.
  • Называет ключевые API: async handler, throw.
  • Использует точные API Fastify, а не вымышленные hooks/decorators/methods.
  • Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.

Sources

Related topics