Как реализовать CORS в Fastify с помощью @fastify/cors?
@fastify/cors регистрируется как плагин и принимает объект опций с полями origin, methods, credentials, allowedHeaders. Можно задать динамическую функцию для origin, чтобы проверять домен по белому списку.
Установка и базовая настройка
Устанавливайте пакет только внутри контейнера или через проектный package.json:
npm install @fastify/cors
Минимальная регистрация разрешает все источники — подходит только для разработки:
import Fastify from 'fastify';
import cors from '@fastify/cors';
const app = Fastify({ logger: true });
await app.register(cors, {
origin: true, // отражает Origin запроса обратно
});
await app.listen({ port: 3000 });
Продакшн-конфигурация
В боевой среде передавайте явный белый список или функцию-валидатор:
const ALLOWED_ORIGINS = [
'https://app.example.com',
'https://admin.example.com',
];
await app.register(cors, {
origin: (origin, callback) => {
// origin === undefined для server-to-server запросов без заголовка
if (!origin || ALLOWED_ORIGINS.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'), false);
}
},
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
exposedHeaders: ['X-Total-Count', 'X-RateLimit-Remaining'],
credentials: true, // разрешает куки и заголовок Authorization
maxAge: 86400, // кэш preflight на 24 часа
preflight: true, // автоматически обрабатывает OPTIONS
strictPreflight: true,
});
Маршрут-специфичный CORS
Если разные части API должны иметь разные правила, используйте опцию hook или регистрируйте плагин в скопированном контексте:
// Публичный API — широкий CORS
app.register(async (publicCtx) => {
publicCtx.register(cors, { origin: '*' });
publicCtx.get('/public/data', async () => ({ ok: true }));
});
// Внутренний API — строгий CORS
app.register(async (adminCtx) => {
adminCtx.register(cors, { origin: 'https://admin.example.com', credentials: true });
adminCtx.get('/admin/stats', async () => ({ users: 42 }));
});
Preflight и кэширование
Браузер отправляет preflight OPTIONS-запрос перед любым "не простым" запросом. Плагин автоматически отвечает на OPTIONS со статусом 204 и правильными заголовками. Поле maxAge задаёт Access-Control-Max-Age в секундах — увеличьте его в продакшне, чтобы снизить число preflight-запросов.
Заголовки, устанавливаемые плагином
Access-Control-Allow-Origin— разрешённый источникAccess-Control-Allow-Methods— список HTTP-методовAccess-Control-Allow-Headers— разрешённые заголовки запросаAccess-Control-Expose-Headers— заголовки, доступные JS в браузереAccess-Control-Allow-Credentials— приcredentials: trueAccess-Control-Max-Age— TTL preflight-кэшаVary: Origin— при динамическом origin, важно для CDN-кэширования
Подводные камни
- origin: '*' + credentials: true — браузер блокирует такой ответ; при
credentials: trueorigin должен быть конкретным доменом, а не звёздочкой. - Отсутствие Vary: Origin — CDN закэширует ответ для одного origin и вернёт его другому. Плагин добавляет этот заголовок автоматически при динамическом origin, но убедитесь, что вы не переопределяете его вручную.
- Порядок регистрации — плагин CORS должен быть зарегистрирован до роутов; иначе preflight OPTIONS вернёт 404 без нужных заголовков.
- strictPreflight: false по умолчанию — без флага Fastify отвечает на OPTIONS даже без Origin и Access-Control-Request-Method, что маскирует ошибки конфигурации.
- Не обрабатываются server-side запросы — если backend делает запросы к вашему API от имени другого сервиса, заголовок Origin будет отсутствовать; не отклоняйте такие запросы без отдельной авторизационной проверки.
- exposedHeaders не включает Authorization — если клиент должен читать токен из заголовка ответа, добавьте его в
exposedHeadersявно. - Динамический origin и производительность — функция-валидатор вызывается на каждый запрос; вынесите массив разрешённых доменов в Set для O(1)-поиска.
- HTTP vs HTTPS — браузер считает
http://example.comиhttps://example.comразными origin; в белом списке нужны оба варианта, если поддерживаются.
Common mistakes
- Дает общий ответ про Node.js и не называет конкретные API Fastify.
- Не объясняет, где в lifecycle находится CORS через @fastify/cors.
- Не разделяет validation, authorization, business logic и persistence.
- Игнорирует ошибки, лимиты входных данных, observability и тестирование.
What the interviewer is testing
- Может объяснить CORS через @fastify/cors на примере кода.
- Называет ключевые API: @fastify/cors.
- Использует точные API Fastify, а не вымышленные hooks/decorators/methods.
- Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.