Перейти к содержимому

22. Fastify: быстрая альтернатива

Иллюстрация к уроку

Fastify — высокопроизводительный Node.js фреймворк. До 2x быстрее Express, встроенная валидация, плагины, TypeScript-friendly.

Express Fastify
──────────────── ────────────────
~15,000 req/s ~30,000 req/s
Нет валидации JSON Schema встроен
Нет TypeScript TypeScript из коробки
callback-based async/await нативно
middleware цепи плагины (инкапсуляция)
Огромная экосист. Растущая экосистема
Окно терминала
npm install fastify
const fastify = require('fastify');
const app = fastify({
logger: true, // встроенный логгер pino
});
// GET роут
app.get('/', async (request, reply) => {
return { message: 'Привет от Fastify!' };
// return автоматически сериализует JSON
});
// POST с валидацией через JSON Schema
app.post('/users', {
schema: {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string', minLength: 2, maxLength: 50 },
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 0 },
},
},
response: {
201: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' },
},
},
},
},
}, async (request, reply) => {
const { name, email, age } = request.body;
const user = { id: 1, name, email, age };
reply.status(201);
return user;
});
// Запуск
app.listen({ port: 3000, host: '0.0.0.0' }, (err) => {
if (err) {
app.log.error(err);
process.exit(1);
}
});
// Параметры URL
app.get('/users/:id', async (request) => {
const { id } = request.params;
return { id };
});
// Query параметры
app.get('/search', {
schema: {
querystring: {
type: 'object',
properties: {
q: { type: 'string' },
page: { type: 'integer', default: 1 },
limit: { type: 'integer', default: 10, maximum: 100 },
},
required: ['q'],
},
},
}, async (request) => {
const { q, page, limit } = request.query;
return { query: q, page, limit, results: [] };
});
// Wildcard
app.get('/files/*', async (request) => {
return { path: request.params['*'] };
});
// plugins/db.js — плагин для БД
const fp = require('fastify-plugin');
async function dbPlugin(fastify, opts) {
const db = await connectToDatabase(opts.url);
// Декорируем — добавляем db ко всем запросам
fastify.decorate('db', db);
// Закрываем при выключении
fastify.addHook('onClose', async () => {
await db.disconnect();
});
}
module.exports = fp(dbPlugin);
// app.js — регистрация плагинов
const app = fastify({ logger: true });
// Плагины
app.register(require('./plugins/db'), {
url: process.env.DATABASE_URL,
});
app.register(require('@fastify/cors'), {
origin: ['http://localhost:3000'],
credentials: true,
});
app.register(require('@fastify/helmet'));
// Роуты
app.register(require('./routes/users'), { prefix: '/api/users' });
app.register(require('./routes/auth'), { prefix: '/api/auth' });
// routes/users.js — роуты как плагин
async function userRoutes(fastify, opts) {
// fastify.db доступен через плагин
fastify.get('/', async (request) => {
const users = await fastify.db.user.findMany();
return { data: users };
});
fastify.get('/:id', async (request, reply) => {
const user = await fastify.db.user.findUnique({
where: { id: parseInt(request.params.id) },
});
if (!user) {
reply.status(404);
return { error: 'Not found' };
}
return { data: user };
});
fastify.post('/', {
schema: {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' },
},
},
},
}, async (request, reply) => {
const user = await fastify.db.user.create({ data: request.body });
reply.status(201);
return { data: user };
});
}
module.exports = userRoutes;
// Хуки — аналог middleware, но более структурированные
// onRequest — первый, для авторизации
app.addHook('onRequest', async (request, reply) => {
request.startTime = Date.now();
});
// preValidation — до валидации
app.addHook('preValidation', async (request, reply) => {
// Проверяем токен
const token = request.headers.authorization?.slice(7);
if (!token) {
reply.status(401).send({ error: 'Unauthorized' });
return;
}
request.user = jwt.verify(token, secret);
});
// preHandler — после валидации, до обработчика
app.addHook('preHandler', async (request, reply) => {
app.log.info({ userId: request.user?.id, url: request.url });
});
// onSend — перед отправкой ответа
app.addHook('onSend', async (request, reply, payload) => {
// Можно модифицировать ответ
return payload;
});
// onResponse — после отправки ответа (для логирования)
app.addHook('onResponse', async (request, reply) => {
const duration = Date.now() - request.startTime;
app.log.info(`${request.method} ${request.url} ${reply.statusCode} ${duration}ms`);
});
// onError — при ошибке
app.setErrorHandler(async (error, request, reply) => {
app.log.error(error);
reply.status(error.statusCode || 500).send({
error: error.message,
statusCode: error.statusCode || 500,
});
});
// Добавить свойство к request
app.decorateRequest('user', null);
// Добавить метод к reply
app.decorateReply('success', function (data) {
this.status(200).send({ success: true, data });
});
app.decorateReply('fail', function (message, status = 400) {
this.status(status).send({ success: false, error: message });
});
// Использование
app.get('/profile', async (request, reply) => {
if (!request.user) return reply.fail('Unauthorized', 401);
return reply.success(request.user);
});
// Express → Fastify: основные отличия
// Express:
app.use(middleware);
app.get('/path', (req, res) => { res.json(data); });
// Fastify:
app.register(plugin);
app.addHook('onRequest', hook);
app.get('/path', async (request, reply) => { return data; });
// req.body → request.body
// req.params → request.params
// req.query → request.query
// res.json() → return data (или reply.send(data))
// res.status(201).json() → reply.status(201); return data;
// app.use(cors()) → app.register(require('@fastify/cors'))
  1. Создай Fastify сервер с JSON Schema валидацией для POST /users
  2. Реализуй плагин для подключения к БД
  3. Структурируй роуты через app.register(routes, { prefix: '/api' })
  4. Добавь хук onRequest для логирования
  5. Создай кастомный error handler через setErrorHandler