FastifyMiddleTechnical

В чём разница между reply.send() и return в обработчиках маршрутов?

return value и reply.send(value) равнозначны для отправки ответа; смешивать их нельзя — двойная отправка выбросит исключение. return предпочтительнее в async-обработчиках.

reply.send() vs return в обработчиках Fastify

Fastify поддерживает два равнозначных способа отправить ответ из обработчика маршрута: вернуть значение через return или явно вызвать reply.send(value). За обоими стоит один и тот же механизм сериализации.

Эквивалентность в async-обработчиках

// Вариант 1: через return (рекомендуется)
app.get('/v1', async (request, reply) => {
  return { hello: 'world' };
});

// Вариант 2: через reply.send
app.get('/v2', async (request, reply) => {
  reply.send({ hello: 'world' });
  // return здесь не нужен, но и не вреден, если возвращаем undefined
});

// Оба вернут HTTP 200 с телом {"hello":"world"}

Когда нужен reply.send()

1. Callback-стиль (не-async обработчики)

// В обычной функции нет автоматического перехвата возвращаемого значения
app.get('/callback', function (request, reply) {
  someAsyncLib(function (err, data) {
    if (err) return reply.send(err);
    reply.send(data);
  });
});

2. Ответ из middleware / хука

app.addHook('preHandler', async (request, reply) => {
  if (!request.headers.authorization) {
    reply.code(401).send({ error: 'Unauthorized' });
    // После reply.send() в хуке Fastify прерывает цепочку хуков
    // и не вызывает обработчик маршрута
  }
});

3. Потоковая передача

import { createReadStream } from 'node:fs';

app.get('/file', async (request, reply) => {
  const stream = createReadStream('/path/to/file.pdf');
  reply.type('application/pdf');
  return reply.send(stream); // stream нельзя «вернуть» через return
});

Двойная отправка — критическая ошибка

Если вызвать reply.send() и одновременно вернуть значение через return, Fastify выбросит предупреждение FST_ERR_REP_ALREADY_SENT:

// НЕПРАВИЛЬНО — двойная отправка
app.get('/wrong', async (request, reply) => {
  reply.send({ first: true }); // отправляет ответ
  return { second: true };     // FST_ERR_REP_ALREADY_SENT
});

// ПРАВИЛЬНО — только одно из двух
app.get('/right', async (request, reply) => {
  return { first: true }; // единственная точка отправки
});

reply.send() возвращает Promise

Начиная с Fastify v4, reply.send() возвращает Promise. Это позволяет использовать await reply.send() для ожидания завершения записи в сокет:

app.get('/async-send', async (request, reply) => {
  await reply.send({ ok: true });
  // Ответ уже отправлен, можно делать post-response логику
  logToAudit(request);
});

Установка статус-кода и заголовков

// Цепочки методов reply
app.post('/created', async (request, reply) => {
  reply.code(201).header('Location', '/resource/1');
  return { id: 1 }; // код 201 будет использован
});

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

  • return после reply.send() в async-функции: Fastify видит возвращённое из async-функции значение. Если вы вызвали reply.send() и не вернули undefined, фреймворк попытается отправить ответ дважды. Всегда добавляйте явный return после reply.send() в async-обработчиках: return reply.send(...).
  • return в callback внутри async-обработчика: return value внутри вложенного callback не доходит до Fastify — нужен reply.send(value).
  • Отправка после хука: если хук отправил ответ через reply.send(), но обработчик маршрута тоже вызывает reply.send(), будет ошибка. Проверяйте reply.sent: if (!reply.sent) reply.send(...).
  • Сериализация только при наличии схемы: return и reply.send() оба проходят через fast-json-stringify только если для маршрута задана response-схема. Без схемы используется обычный JSON.stringify().
  • Возврат null: return null отправит пустое тело с кодом 200, что может удивить, если ожидался 204 No Content. Для пустого ответа используйте reply.code(204).send().

Common mistakes

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

What the interviewer is testing

  • Может объяснить reply.send() против return на примере кода.
  • Называет ключевые API: reply.send(), return payload.
  • Использует точные API Fastify, а не вымышленные hooks/decorators/methods.
  • Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.

Sources

Related topics