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-риски: безопасность, отказоустойчивость, логирование и тесты.