← Назад к вопросам

Является ли POST-запрос идемпотентным?

2.0 Middle🔥 111 комментариев
#API и сетевые протоколы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Нет, POST-запрос НЕ является идемпотентным. Каждый POST-запрос должен создавать новый ресурс или иметь побочные эффекты, поэтому несколько идентичных POST-запросов приводят к разным результатам.

Почему POST не идемпотентен

POST предназначен для создания новых ресурсов или выполнения операций со статефулом эффектом. Каждый вызов:

  • Создает новый ресурс с уникальным ID
  • Изменяет состояние сервера
  • Может иметь побочные эффекты
POST /api/users → создает пользователя #1
POST /api/users → создает пользователя #2 (новый, с другим ID!)
POST /api/users → создает пользователя #3 (еще один новый)

Практический пример

app.post('/api/users', (req, res) => {
  const newUser = {
    id: Date.now(), // Уникальный ID
    name: req.body.name,
    email: req.body.email
  };
  users.push(newUser);
  res.status(201).json(newUser);
});

// Три одинаковых запроса создают РАЗНЫЕ ресурсы
POST /api/users {name: 'Иван'} → {id: 1000, name: 'Иван'}
POST /api/users {name: 'Иван'} → {id: 1001, name: 'Иван'} (другой ID!)
POST /api/users {name: 'Иван'} → {id: 1002, name: 'Иван'} (еще другой)

Случаи, где POST явно не идемпотентен

1. Создание заказов

app.post('/api/orders', (req, res) => {
  const order = {
    id: generateId(),
    items: req.body.items,
    total: calculateTotal(req.body.items),
    createdAt: new Date()
  };
  
  orders.push(order);
  res.status(201).json(order);
});

// Три одинаковых POST создадут ТРИ заказа
POST /api/orders {items: [...]}
POST /api/orders {items: [...]} (новый заказ, новый ID)
POST /api/orders {items: [...]} (еще один заказ)

2. Платежи

app.post('/api/payments', (req, res) => {
  const payment = {
    id: uuid(),
    amount: req.body.amount,
    cardId: req.body.cardId,
    status: 'pending'
  };
  
  payments.push(payment);
  processPayment(payment); // ПОБОЧНЫЙ ЭФФЕКТ
  res.status(201).json(payment);
});

// Каждый POST обработает платеж!
POST /api/payments → платеж обработан #1
POST /api/payments → платеж обработан #2 (ПРОБЛЕМА!)
POST /api/payments → платеж обработан #3 (ЕЩЕ ПРОБЛЕМА!)

3. Отправка писем

app.post('/api/send-email', (req, res) => {
  const email = {
    to: req.body.to,
    subject: req.body.subject,
    body: req.body.body
  };
  
  sendEmail(email); // ПОБОЧНЫЙ ЭФФЕКТ - письмо отправляется
  res.json({sent: true});
});

// Три POST отправят ТРИ письма
POST /api/send-email → письмо отправлено
POST /api/send-email → письмо отправлено (еще раз!)
POST /api/send-email → письмо отправлено (и еще раз!)

Сравнение методов

МетодИдемпотентенОписаниеРезультат повтора
GETДаПолучение данныхОдин результат
POSTНетСозданиеНесколько ресурсов
PUTДаОбновление/заменаОдин ресурс (обновлён)
DELETEДаУдалениеОдин результат
PATCHМожет бытьЧастичное обновлениеЗависит от реализации

Проблема дублирования запросов

По сетевым причинам POST может отправиться дважды:

// Клиент отправляет POST
fetch('/api/orders', {method: 'POST', body: {...}})
  .then(r => r.json())
  .catch(() => {
    // Timeout! Переотправляем
    fetch('/api/orders', {method: 'POST', body: {...}})
  });

// Результат: два заказа вместо одного!

Решения для безопасности

1. Идемпотентные ключи (Idempotency Key)

app.post('/api/orders', (req, res) => {
  const idempotencyKey = req.headers['idempotency-key'];
  
  // Проверяем, есть ли уже заказ с таким ключом
  const existing = orders.find(o => o.idempotencyKey === idempotencyKey);
  if (existing) return res.json(existing);
  
  // Создаем новый
  const order = {
    id: uuid(),
    idempotencyKey,
    items: req.body.items
  };
  orders.push(order);
  res.status(201).json(order);
});

// Клиент
const idempotencyKey = uuid();
fetch('/api/orders', {
  method: 'POST',
  headers: {'Idempotency-Key': idempotencyKey},
  body: JSON.stringify({items: [...]})
});

2. Кэширование результатов

const requestCache = new Map();

app.post('/api/payment', (req, res) => {
  const cacheKey = JSON.stringify(req.body);
  
  if (requestCache.has(cacheKey)) {
    return res.json(requestCache.get(cacheKey));
  }
  
  const result = processPayment(req.body);
  requestCache.set(cacheKey, result);
  res.json(result);
});

3. Использование PUT для идемпотентности

// ПЛОХО - POST для обновления
POST /api/user/settings {theme: 'dark'}

// ХОРОШО - PUT для идемпотентного обновления
PUT /api/user/settings {theme: 'dark'}

Вывод

POST не идемпотентен по определению. Каждый POST создает новый ресурс или имеет побочные эффекты. Для защиты от дублирования используй:

  • Idempotency-Key (Stripe, Paypal)
  • Кэширование результатов
  • Проверку дубликатов в БД
  • Для обновлений используй PUT/PATCH вместо POST