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 Также в библиотеке
Laravel Passport для PHP: полное руководство с примерами
Интернационализация (i18n) в C# (.NET): полное руководство
React для JavaScript (Node.js): полное руководство с примерами
Отправка email на Swift (iOS/macOS): MFMailComposeViewController и SMTP
Мониторинг приложений Java: инструменты и библиотеки для сбора метрик