Fastify для JavaScript (Node.js): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Fastify для JavaScript (Node.js): полное руководство с примерами

1. Что это и зачем нужна

Fastify — это высокопроизводительный веб-фреймворк для Node.js, вдохновленный Hapi и Express, но с фокусом на скорость и низкое потребление памяти. Он был создан в 2016 году разработчиками из команды NearForm и с тех пор стал одним из самых быстрых решений для создания REST API, микросервисов и веб-приложений на JavaScript/TypeScript.

Основная причина использовать Fastify — его производительность. По тестам, он способен обрабатывать до 30-40% больше запросов в секунду по сравнению с Express при том же потреблении ресурсов. Это достигается за счет асинхронной архитектуры на основе Node.js streams, эффективного парсинга JSON (через fast-json-stringify) и оптимизированного маршрутизатора на основе Radix Tree (сжатое префиксное дерево).

Кроме скорости, Fastify предлагает встроенную валидацию схем через JSON Schema, автоматическое кэширование ответов, поддержку TypeScript из коробки, расширяемую систему плагинов и отличную документацию. Это делает его идеальным выбором для production-проектов, где важна стабильность и предсказуемая работа под нагрузкой.

2. Установка

Fastify устанавливается через npm (Node Package Manager). Убедитесь, что у вас установлен Node.js версии 18 или выше (рекомендуется LTS).

# Базовая установка
npm install fastify

# Для TypeScript (дополнительно)
npm install -D @types/node typescript

# Для работы с файлами cookie, сессиями и т.д.
npm install @fastify/cookie @fastify/session

Если вы используете yarn или pnpm:

yarn add fastify
# или
pnpm add fastify

3. Быстрый старт — минимальный рабочий пример

Создайте файл server.js и запустите его через node server.js.

// server.js
const fastify = require('fastify')({
  logger: true // Включаем встроенный логгер (pino)
});

// Определяем маршрут GET /
fastify.get('/', async (request, reply) => {
  return { hello: 'world' };
});

// Запуск сервера
const start = async () => {
  try {
    await fastify.listen({ port: 3000 });
    console.log('Сервер запущен на http://localhost:3000');
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();

После запуска откройте браузер или выполните curl http://localhost:3000. Вы получите JSON-ответ {"hello":"world"}.

4. Основные методы/функции/классы

4.1. fastify.get(path, options, handler)

Сигнатура: fastify.get(path: string, opts?: RouteShorthandOptions, handler: RequestHandler)

Что делает: Регистрирует обработчик для HTTP GET-запросов по указанному пути.

fastify.get('/user/:id', async (request, reply) => {
  const { id } = request.params;
  return { userId: id, name: 'John Doe' };
});

4.2. fastify.post(path, options, handler)

Сигнатура: fastify.post(path: string, opts?: RouteShorthandOptions, handler: RequestHandler)

Что делает: Регистрирует обработчик для HTTP POST-запросов. Автоматически парсит тело запроса (JSON, form-data и т.д.).

fastify.post('/user', async (request, reply) => {
  const { name, email } = request.body;
  // Сохраняем пользователя в БД...
  return { status: 'created', name, email };
});

4.3. fastify.register(plugin, opts)

Сигнатура: fastify.register(plugin: FastifyPluginAsync | FastifyPluginCallback, opts?: object)

Что делает: Регистрирует плагин в экосистеме Fastify. Плагины могут добавлять маршруты, хуки, декораторы и т.д. Важно: каждый плагин создает свой контекст (инкапсуляцию).

// myPlugin.js
async function myPlugin(fastify, opts) {
  fastify.get('/plugin-route', async () => {
    return { from: 'plugin' };
  });
}

// Основной файл
fastify.register(myPlugin, { prefix: '/api' });
// Теперь маршрут доступен по /api/plugin-route

4.4. fastify.addHook(name, fn)

Сигнатура: fastify.addHook(name: string, fn: HookHandlerFunction)

Что делает: Добавляет хук (перехватчик) на определенный этап жизненного цикла запроса: onRequest, preParsing, preHandler, onSend, onResponse и др.

// Хук для логирования времени выполнения запроса
fastify.addHook('onRequest', async (request, reply) => {
  request.startTime = Date.now();
});

fastify.addHook('onResponse', async (request, reply) => {
  const elapsed = Date.now() - request.startTime;
  console.log(`Запрос ${request.method} ${request.url} выполнен за ${elapsed}ms`);
});

4.5. fastify.decorate(name, value)

Сигнатура: fastify.decorate(name: string, value: any)

Что делает: Добавляет пользовательское свойство или метод в экземпляр Fastify, делая его доступным во всех маршрутах и плагинах.

// Добавляем метод для работы с БД
fastify.decorate('db', {
  users: [{ id: 1, name: 'Alice' }],
  findUserById(id) {
    return this.users.find(u => u.id === id);
  }
});

fastify.get('/user/:id', async (request) => {
  const user = fastify.db.findUserById(Number(request.params.id));
  return user || { error: 'User not found' };
});

4.6. fastify.setSchemaCompiler(schemaCompiler)

Сигнатура: fastify.setSchemaCompiler(schemaCompiler: (schema: object) => (data: any) => boolean)

Что делает: Устанавливает кастомный компилятор схем для валидации запросов и ответов. По умолчанию используется ajv (Another JSON Schema Validator).

const Ajv = require('ajv');
const ajv = new Ajv();

fastify.setSchemaCompiler(({ schema }) => {
  return ajv.compile(schema);
});

// Теперь можно использовать схемы в маршрутах
fastify.post('/user', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string' },
        email: { type: 'string', format: 'email' }
      }
    }
  }
}, async (request) => {
  return { valid: true, data: request.body };
});

4.7. fastify.setNotFoundHandler(handler)

Сигнатура: fastify.setNotFoundHandler(handler: (request: FastifyRequest, reply: FastifyReply) => void)

Что делает: Задает кастомный обработчик для всех запросов, которые не соответствуют ни одному зарегистрированному маршруту (404).

fastify.setNotFoundHandler((request, reply) => {
  reply.code(404).send({
    error: 'Not Found',
    message: `Маршрут ${request.method} ${request.url} не существует`,
    statusCode: 404
  });
});

4.8. fastify.setErrorHandler(handler)

Сигнатура: fastify.setErrorHandler(handler: (error: Error, request: FastifyRequest, reply: FastifyReply) => void)

Что делает: Задает глобальный обработчик ошибок для всех необработанных исключений в маршрутах и хуках.

fastify.setErrorHandler((error, request, reply) => {
  // Логируем ошибку
  fastify.log.error(error);
  
  // Отправляем клиенту структурированный ответ
  reply.code(error.statusCode || 500).send({
    error: error.message,
    statusCode: error.statusCode || 500
  });
});

4.9. fastify.listen(options)

Сигнатура: fastify.listen(options: ListenOptions | string | number): Promise<string>

Что делает: Запускает HTTP-сервер и начинает прослушивание указанного порта и хоста. Возвращает Promise с адресом сервера.

// Запуск на порту 3000, только локальный доступ
await fastify.listen({ port: 3000, host: '127.0.0.1' });

// Запуск на случайном свободном порту
const address = await fastify.listen({ port: 0 });
console.log(`Сервер запущен на ${address}`);

4.10. fastify.inject(options)

Сигнатура: fastify.inject(options: InjectOptions | string): Promise<InjectResponse>

Что делает: Позволяет отправлять HTTP-запросы к серверу без реального запуска сети (inject). Идеально для тестирования.

// В тесте
const response = await fastify.inject({
  method: 'GET',
  url: '/user/1'
});
console.log(response.statusCode); // 200
console.log(response.json()); // { userId: 1, name: 'John Doe' }

5. Реальный практический пример (100-200 строк кода)

Создадим полноценное REST API для управления задачами (TODO-приложение) с валидацией, обработкой ошибок и использованием плагинов.

// app.js
const fastify = require('fastify')({ logger: true });

// ====== Плагин для имитации базы данных ======
fastify.register(async function dbPlugin(fastify) {
  const tasks = [
    { id: 1, title: 'Изучить Fastify', completed: false },
    { id: 2, title: 'Написать документацию', completed: true }
  ];
  let nextId = 3;

  fastify.decorate('tasksDB', {
    getAll() { return tasks; },
    getById(id) { return tasks.find(t => t.id === id); },
    create(title) {
      const task = { id: nextId++, title, completed: false };
      tasks.push(task);
      return task;
    },
    update(id, data) {
      const idx = tasks.findIndex(t => t.id === id);
      if (idx === -1) return null;
      tasks[idx] = { ...tasks[idx], ...data };
      return tasks[idx];
    },
    delete(id) {
      const idx = tasks.findIndex(t => t.id === id);
      if (idx === -1) return false;
      tasks.splice(idx, 1);
      return true;
    }
  });
});

// ====== Глобальный обработчик ошибок ======
fastify.setErrorHandler((error, request, reply) => {
  fastify.log.error(error);
  reply.code(error.statusCode || 500).send({
    error: error.message || 'Internal Server Error',
    statusCode: error.statusCode || 500
  });
});

// ====== Обработчик 404 ======
fastify.setNotFoundHandler((request, reply) => {
  reply.code(404).send({
    error: 'Route not found',
    statusCode: 404
  });
});

// ====== Маршруты ======

// GET /tasks — получить все задачи
fastify.get('/tasks', async (request, reply) => {
  return fastify.tasksDB.getAll();
});

// GET /tasks/:id — получить задачу по ID
fastify.get('/tasks/:id', async (request, reply) => {
  const id = Number(request.params.id);
  const task = fastify.tasksDB.getById(id);
  if (!task) {
    reply.code(404);
    return { error: 'Task not found' };
  }
  return task;
});

// POST /tasks — создать задачу
fastify.post('/tasks', {
  schema: {
    body: {
      type: 'object',
      required: ['title'],
      properties: {
        title: { type: 'string', minLength: 1, maxLength: 200 }
      }
    }
  }
}, async (request, reply) => {
  const { title } = request.body;
  const newTask = fastify.tasksDB.create(title);
  reply.code(201);
  return newTask;
});

// PUT /tasks/:id — обновить задачу
fastify.put('/tasks/:id', {
  schema: {
    body: {
      type: 'object',
      properties: {
        title: { type: 'string', minLength: 1 },
        completed: { type: 'boolean' }
      }
    }
  }
}, async (request, reply) => {
  const id = Number(request.params.id);
  const updated = fastify.tasksDB.update(id, request.body);
  if (!updated) {
    reply.code(404);
    return { error: 'Task not found' };
  }
  return updated;
});

// DELETE /tasks/:id — удалить задачу
fastify.delete('/tasks/:id', async (request, reply) => {
  const id = Number(request.params.id);
  const deleted = fastify.tasksDB.delete(id);
  if (!deleted) {
    reply.code(404);
    return { error: 'Task not found' };
  }
  reply.code(204);
  return;
});

// ====== Запуск сервера ======
const start = async () => {
  try {
    await fastify.listen({ port: 3000, host: '0.0.0.0' });
    console.log('TODO API запущен на http://localhost:3000');
    console.log('Доступные маршруты:');
    console.log('  GET    /tasks');
    console.log('  GET    /tasks                    

Рекомендаций