FastifyJuniorTechnical

Как @fastify/sensible улучшает опыт разработчика?

@fastify/sensible добавляет в reply методы-хелперы для HTTP-ошибок (reply.notFound(), reply.badRequest() и др.) и декораторы httpErrors, assert, to, делая код обработчиков короче и читаемее.

@fastify/sensible — набор полезных дополнений для Fastify

@fastify/sensible — официальный плагин, который добавляет к Fastify-приложению коллекцию небольших, но часто нужных улучшений. Он не меняет архитектуру приложения, а заполняет «пробелы», с которыми сталкиваются разработчики каждый день: стандартные HTTP-ошибки, безопасная обработка async-операций, удобные шорткаты для ответов.

Установка и регистрация

import Fastify from 'fastify';
import sensible from '@fastify/sensible';

const app = Fastify({ logger: true });

await app.register(sensible);

HTTP-ошибки через reply

После регистрации плагина на объект reply добавляются методы для всех стандартных HTTP-ошибок. Они автоматически устанавливают статус-код и возвращают объект ошибки в формате, совместимом с http-errors:

app.get('/users/:id', async (request, reply) => {
  const user = await db.findById(request.params.id);

  if (!user) {
    // Вместо: return reply.code(404).send({ message: 'Not Found' })
    return reply.notFound(`User ${request.params.id} not found`);
  }

  if (!request.user.canRead(user)) {
    return reply.forbidden('Access denied');
  }

  return user;
});

Полный список методов ошибок

reply.badRequest('Invalid input');          // 400
reply.unauthorized('Login required');       // 401
reply.paymentRequired();                    // 402
reply.forbidden('No access');               // 403
reply.notFound('Resource missing');         // 404
reply.methodNotAllowed();                   // 405
reply.notAcceptable();                      // 406
reply.conflict('Duplicate entry');          // 409
reply.gone();                               // 410
reply.unprocessableEntity('Bad data');      // 422
reply.tooManyRequests('Slow down');         // 429
reply.internalServerError('Oops');          // 500
reply.notImplemented();                     // 501
reply.badGateway();                         // 502
reply.serviceUnavailable('Maintenance');    // 503
reply.gatewayTimeout();                     // 504

httpErrors — создание ошибок вне обработчика

Декоратор app.httpErrors позволяет создавать объекты ошибок в middleware, сервисном слое или хуках, где reply недоступен:

// Сервисный слой
async function getUserOrThrow(id) {
  const user = await db.findById(id);
  if (!user) {
    throw app.httpErrors.notFound(`User ${id} not found`);
  }
  return user;
}

// Обработчик просто вызывает сервис — ошибка всплывает автоматически
app.get('/users/:id', async (request) => {
  return getUserOrThrow(request.params.id);
});

Утилита assert

app.assert(condition, statusCode, message) — компактный способ выбросить HTTP-ошибку при нарушении инварианта:

app.post('/transfer', async (request) => {
  const { amount, toId } = request.body;

  app.assert(amount > 0, 400, 'Amount must be positive');
  app.assert(amount <= 10_000, 422, 'Amount exceeds limit');

  const sender = await getUser(request.user.id);
  app.assert(sender.balance >= amount, 409, 'Insufficient funds');

  return transfer(sender, toId, amount);
});

Утилита to — безопасный await

app.to(promise) оборачивает Promise в стиле Go-like error handling, возвращая [error, data]:

app.get('/profile', async (request, reply) => {
  const [err, user] = await app.to(fetchUser(request.user.id));

  if (err) {
    request.log.error(err, 'Failed to fetch user');
    return reply.internalServerError('Could not load profile');
  }

  return user;
});

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

  • Инкапсуляция плагина: если @fastify/sensible зарегистрирован внутри дочернего контекста без fastify-plugin, методы reply.notFound() будут доступны только в этом контексте. Регистрируйте его на уровне корневого приложения или используйте fp().
  • Конфликт с setErrorHandler: если вы определили кастомный setErrorHandler, убедитесь, что он корректно обрабатывает объекты ошибок из http-errors (у них есть поля statusCode и status).
  • app.to не логирует ошибки: to() просто возвращает ошибку, не бросая её. Если забыть проверить err, ошибка будет молча проглочена.
  • assert бросает исключение: в отличие от библиотеки assert из Node.js, app.assert создаёт HTTP-ошибку, а не AssertionError. Не используйте её для unit-тестов — только для бизнес-инвариантов.
  • Сообщения ошибок попадают к клиенту: строки, переданные в reply.forbidden('секретная причина'), будут видны в ответе. Не передавайте внутренние детали (имена таблиц, стек и т.д.).

Common mistakes

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

What the interviewer is testing

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

Sources

Related topics