Разработка бэкенда сайта на Node.js (Fastify)
Fastify выбирают не ради хайпа — его выбирают, когда пропускная способность имеет значение. Фреймворк обрабатывает до 76 000 запросов в секунду на одном ядре в синтетических тестах, и эта разница ощущается в реальных проектах при работе с высокочастотными API: price-feed, real-time аналитика, публичные API с непредсказуемым трафиком.
Архитектура и организация кода
Fastify строится вокруг концепции плагинов — каждый плагин это изолированный контекст с собственными декораторами, хуками и обработчиками. Это не просто соглашение, а встроенная инкапсуляция через fastify-plugin:
// plugins/db.js
import fp from 'fastify-plugin'
import { Pool } from 'pg'
async function dbPlugin(fastify, opts) {
const pool = new Pool({ connectionString: opts.connectionString })
fastify.decorate('db', pool)
fastify.addHook('onClose', async () => pool.end())
}
export default fp(dbPlugin, { name: 'db' })
// routes/products/index.js
export default async function productsRoutes(fastify) {
fastify.get('/', {
schema: {
querystring: {
type: 'object',
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 20 },
offset: { type: 'integer', minimum: 0, default: 0 }
}
},
response: {
200: {
type: 'array',
items: { $ref: 'Product#' }
}
}
}
}, async (request, reply) => {
const { limit, offset } = request.query
const { rows } = await fastify.db.query(
'SELECT * FROM products ORDER BY id LIMIT $1 OFFSET $2',
[limit, offset]
)
return rows
})
}
Схема маршрута — не документация, это входная валидация и сериализация ответа одновременно. Fastify использует ajv для валидации входящих данных и fast-json-stringify для сериализации ответа. Ответ сериализуется в 2–5 раз быстрее, чем через JSON.stringify.
Структура проекта для production
src/
app.js # инициализация fastify, регистрация плагинов
server.js # точка входа, listen
plugins/
db.js # postgres через pg или knex
redis.js # ioredis
auth.js # fastify-jwt или custom
sensible.js # fastify-sensible — удобные http-ошибки
routes/
v1/
users/
index.js # GET /users, POST /users
_id/
index.js # GET /users/:id, PATCH, DELETE
products/
schemas/
user.json
product.json
hooks/
authentication.js
services/ # бизнес-логика, не завязанная на fastify
Авторегистрация маршрутов через @fastify/autoload убирает необходимость явного импорта:
import autoload from '@fastify/autoload'
import { fileURLToPath } from 'url'
import { join, dirname } from 'path'
const __dirname = dirname(fileURLToPath(import.meta.url))
await fastify.register(autoload, {
dir: join(__dirname, 'routes'),
options: { prefix: '/api/v1' }
})
Аутентификация и авторизация
JWT через @fastify/jwt. Декоратор fastify.authenticate добавляет проверку на любой маршрут:
fastify.decorate('authenticate', async function(request, reply) {
try {
await request.jwtVerify()
} catch (err) {
reply.send(err)
}
})
// Использование на маршруте
fastify.get('/profile', {
onRequest: [fastify.authenticate]
}, async (request) => {
return request.user
})
Для ролевых разрешений добавляем хук preHandler:
const requireRole = (role) => async (request, reply) => {
if (request.user.role !== role) {
return reply.code(403).send({ error: 'Forbidden' })
}
}
fastify.delete('/users/:id', {
onRequest: [fastify.authenticate],
preHandler: [requireRole('admin')]
}, handler)
База данных: pg vs Prisma vs Drizzle
Выбор ORM под Fastify зависит от задачи:
| Инструмент | Когда использовать |
|---|---|
pg + @databases/pg-typed |
Полный контроль над SQL, критичная производительность |
Drizzle ORM |
Type-safe SQL без overhead Prisma, ESM-friendly |
Prisma |
Быстрый старт, генерация типов, команда знает Prisma |
Knex |
Query builder с миграциями, без ORM overhead |
Drizzle сейчас оптимальный выбор для новых проектов на Node.js 18+:
import { pgTable, serial, varchar, timestamp } from 'drizzle-orm/pg-core'
import { drizzle } from 'drizzle-orm/node-postgres'
const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
createdAt: timestamp('created_at').defaultNow()
})
const db = drizzle(pool)
// Полностью типизированный запрос
const result = await db.select().from(users).where(eq(users.email, email)).limit(1)
Кеширование
Redis для кеша через @fastify/redis:
fastify.register(fastifyRedis, { url: process.env.REDIS_URL })
fastify.get('/catalog', async (request, reply) => {
const cacheKey = `catalog:${JSON.stringify(request.query)}`
const cached = await fastify.redis.get(cacheKey)
if (cached) {
reply.header('X-Cache', 'HIT')
return JSON.parse(cached)
}
const data = await fetchCatalog(request.query)
await fastify.redis.setex(cacheKey, 300, JSON.stringify(data))
reply.header('X-Cache', 'MISS')
return data
})
WebSocket и SSE
Fastify поддерживает WebSocket через @fastify/websocket:
fastify.register(fastifyWs)
fastify.get('/ws/notifications', { websocket: true }, (socket, req) => {
socket.on('message', (raw) => {
const msg = JSON.parse(raw.toString())
// обработка
})
const interval = setInterval(() => {
if (socket.readyState === socket.OPEN) {
socket.send(JSON.stringify({ type: 'ping', ts: Date.now() }))
}
}, 30000)
socket.on('close', () => clearInterval(interval))
})
Мониторинг и observability
@fastify/metrics для Prometheus:
fastify.register(fastifyMetrics, {
endpoint: '/metrics',
defaultMetrics: { enabled: true },
defaultLabels: { app: 'api', version: process.env.APP_VERSION }
})
Структурированное логирование встроено через pino — быстрейший логгер для Node.js:
const fastify = Fastify({
logger: {
level: process.env.LOG_LEVEL || 'info',
transport: process.env.NODE_ENV === 'development'
? { target: 'pino-pretty' }
: undefined
}
})
Сроки разработки
Типовой REST API для сайта или веб-приложения на Fastify:
- Проектирование API (OpenAPI/Swagger) — 3–5 дней: эндпоинты, схемы данных, аутентификация
- Базовый каркас: плагины, роуты, миграции — 3–5 дней
- Бизнес-логика — зависит от сложности; 1–2 недели для CRUD-сайта, 3–6 недель для сложного приложения
- Интеграции (платёжка, email, внешние API) — 1–3 недели
- Тесты + документация — 3–7 дней
Для несложного корпоративного сайта с API (каталог, формы, личный кабинет) полный цикл занимает 4–8 недель. Fastify здесь даёт преимущество прежде всего в том, что валидация и документация пишутся один раз в схеме маршрута — это экономит время при поддержке.







