FastifyMiddleTechnical

Как реализовать 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: true
  • Access-Control-Max-Age — TTL preflight-кэша
  • Vary: Origin — при динамическом origin, важно для CDN-кэширования

Подводные камни

  • origin: '*' + credentials: true — браузер блокирует такой ответ; при credentials: true origin должен быть конкретным доменом, а не звёздочкой.
  • Отсутствие 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-риски: безопасность, отказоустойчивость, логирование и тесты.

Sources

Related topics