Avatar ·

Почему в JavaScript ошибка CORS, а в Postman запрос работает? Разбираемся

Всем привет! Я начинающий фронтенд-разработчик, и у меня возникла проблема, которая ставит меня в тупик. Я пишу простое веб-приложение на чистом JavaScript. Мне нужно отправить POST-запрос на сторонний API (например, https://api.example.com/data), чтобы получить какие-то данные. В консоли браузера я вижу ошибку:
Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Вот мой код:
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ key: 'value' })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Ошибка:', error));
Но самое странное: когда я копирую этот же URL, метод и тело запроса в Postman — всё работает идеально! Postman возвращает ожидаемый ответ. Я уже перепробовал кучу советов с форумов: добавлял заголовки, менял режимы fetch, но ничего не помогает. Почему так происходит? Ведь запросы вроде бы одинаковые. В чём разница между запросом из браузера (JavaScript) и запросом из Postman? Как мне заставить мой код работать? Заранее спасибо за помощь! <<>>

Ключевое отличие: браузер vs. Postman

Вы столкнулись с классической ситуацией, которая путает многих новичков. Проблема не в вашем коде, а в том, как браузер и Postman обрабатывают запросы к другому домену (кросс-доменные запросы).

Postman — это инструмент для разработчиков. Он отправляет "сырой" HTTP-запрос на сервер и просто показывает ответ. Postman не соблюдает политику Same-Origin Policy (SOP) и механизм CORS. Ему всё равно, какие заголовки приходят в ответе.

Браузер — это среда с повышенными требованиями безопасности. Когда ваш JavaScript (работающий на http://localhost:3000) пытается сделать запрос на https://api.example.com, браузер сначала отправляет предварительный запрос OPTIONS (preflight request). И только если сервер в ответе на этот OPTIONS пришлёт правильные CORS-заголовки, браузер разрешит выполнить ваш основной POST.

Вы получаете ошибку, потому что сервер api.example.com не настроен на приём запросов из внешних источников (вашего localhost). Он не возвращает заголовок Access-Control-Allow-Origin.

Как это исправить? Три пути решения

1. Серверная настройка CORS (правильный способ)

Если у вас есть доступ к серверу, который вы запрашиваете, нужно настроить его на отправку правильных CORS-заголовков. Для Node.js (Express) это выглядит так:

// Серверный код (Node.js + Express)
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'http://localhost:3000'); // Или '*' для всех
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  // Обрабатываем preflight запрос
  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
  next();
});

app.post('/data', (req, res) => {
  res.json({ message: 'Успех!' });
});

2. Использование CORS-прокси (для разработки)

Если сервер вам не принадлежит (например, публичное API), можно использовать прокси-сервер, который добавит нужные заголовки. Например, сервис cors-anywhere:

fetch('https://cors-anywhere.herokuapp.com/https://api.example.com/data', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ key: 'value' })
});

Важно: Это подходит только для разработки и тестирования. В продакшене так делать нельзя.

3. JSONP (устаревший метод)

Работает только для GET-запросов. Сервер оборачивает ответ в функцию JavaScript. Используется редко.

Почему Postman работает, а браузер — нет?

  • Postman: не проверяет CORS. Он просто показывает ответ сервера.
  • Браузер: проверяет CORS и блокирует ответ, если сервер не разрешил доступ. Ошибка возникает на стороне браузера, а не сервера. Сервер скорее всего обработал ваш запрос, но браузер "выбросил" ответ.

Проверить это можно в инструментах разработчика (F12) -> вкладка Network. Вы увидите два запроса: OPTIONS (заблокирован) и POST (часто тоже с ошибкой).

Вывод: Если сервер не ваш — без изменения его настроек CORS, вы не сможете сделать прямой запрос из браузера. Используйте бэкенд-прокси (свой сервер) или попросите владельца API добавить ваш домен в разрешённые.

<<>>

Альтернативный взгляд: режим no-cors и работа с ошибками

Если вам не нужно читать ответ от сервера (например, вы просто отправляете аналитику или логи), вы можете использовать режим no-cors в fetch. Это не решит проблему получения данных, но позволит избежать ошибки в консоли:

fetch('https://api.example.com/data', {
  method: 'POST',
  mode: 'no-cors', // Важно!
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ key: 'value' })
})
.then(response => {
  // response.type будет 'opaque'
  // Вы НЕ сможете прочитать тело ответа
  console.log('Запрос отправлен, но ответ недоступен');
});

Минусы:

  • Вы не получите данные от сервера.
  • Вы не увидите HTTP-статус ответа (всегда 0).
  • Некоторые заголовки (например, Content-Type) могут быть проигнорированы браузером.

Этот подход часто используется для трекеров или пинг-запросов, где важен только факт отправки. Для полноценной работы с API он не подходит.

Лучшая практика: Если API стороннее и не поддерживает CORS, создайте простой бэкенд-прокси на Node.js или PHP. Ваш фронтенд будет обращаться к своему бэкенду (CORS не нужен, так как домен один), а бэкенд уже будет делать запрос к стороннему API. Это безопасно и надёжно.