Иконка подарка

Весенняя распродажа! Скидка 15% по промокоду

до 01.04.2026

Работа с API - api-integration пошаговое руководство с примерами

27 марта 2026
Автор

Олег Марков

Введение

Работа с API сегодня — одна из ключевых задач для разработчика. Вы постоянно взаимодействуете с внешними сервисами: платежные шлюзы, сервисы отправки писем и SMS, картография, аналитика, собственные микросервисы. Все это — интеграции по API (api-integration).

Смотрите, я покажу вам, как подойти к интеграции системно: от понимания протокола HTTP и форматов данных до проектирования структуры клиентского кода, обработки ошибок, ретраев и тестирования.

В этой статье вы разберетесь:

  • какие бывают типы API и чем они отличаются;
  • как читать документацию к API и по каким ключевым точкам ее проверять;
  • как строится HTTP запрос и ответ;
  • как работать с аутентификацией и авторизацией при интеграции;
  • как организовать код клиентской библиотеки к API;
  • как обрабатывать ошибки, таймауты и повторы запросов;
  • как тестировать интеграции и изолировать внешние вызовы.

Я буду показывать примеры в основном на JavaScript (Node.js) и немного на cURL, чтобы вы могли воспроизвести их в консоли. Подходы при этом универсальны и подходят для любых языков.

Что такое API и зачем оно нужно

Основная идея API

API (Application Programming Interface) — это соглашение о том, как одна программа может обращаться к другой: какие есть операции, какие данные принимаются и возвращаются, какие ошибки возможны.

Когда вы работаете с HTTP API (REST API, JSON API и т.п.), обычно речь идет о:

  • URL (endpoint), по которому вы делаете запрос;
  • HTTP методе (GET, POST, PUT, PATCH, DELETE и т.д.);
  • заголовках (headers), например, авторизации или формата данных;
  • теле запроса (body) — чаще всего JSON;
  • ответе сервера — также с кодом состояния, заголовками и телом.

Виды API с которыми вы столкнетесь

Кратко разберем основные типы, чтобы в документации вы сразу понимали, о чем речь.

REST API

Наиболее распространенный стиль. Основные признаки:

  • взаимодействие по HTTP;
  • ресурсы (users, orders, payments) представлены в URL;
  • методы HTTP обозначают действие:
    • GET — получить ресурс;
    • POST — создать;
    • PUT/PATCH — изменить;
    • DELETE — удалить;
  • данные обычно в формате JSON.

Пример типичного REST запроса:

GraphQL

Здесь вы отправляете один endpoint, но сами описываете, какие поля и какие сущности хотите получить в ответе.

Особенности:

  • один URL (часто /graphql);
  • запросы и мутации описаны в специальном языке (GraphQL schema);
  • вы четко указываете, какие поля нужны — это удобно для оптимизации.

Webhook

Это не совсем тип API, а паттерн интеграции. Вместо того чтобы вы постоянно опрашивали внешний сервис, он сам делает запрос в вашу систему, когда что-то произошло.

Пример:

  • вы интегрировались с платежной системой;
  • после успешной оплаты она делает POST запрос к вашему endpoint /payments/webhook;
  • в теле запроса — данные о платеже.

Webhook-и требуют:

  • публично доступного URL;
  • проверки подписи (подлинности) запроса;
  • аккуратной обработки повторных запросов.

Как читать документацию API

Прежде чем писать код, важно понять, с чем вы имеете дело. Давайте разберемся, на что смотреть в документации.

Обязательные разделы хорошей документации

  1. Базовый URL (Base URL)

    • Например, https://api.example.com/v1.
    • Вы будете добавлять к нему конкретные пути (endpoints).
  2. Аутентификация и авторизация

    • API ключи (API Key);
    • Bearer токены (OAuth 2.0);
    • Basic auth;
    • подписи запросов (HMAC).
  3. Описание ресурсов и методов

    • список endpoint-ов;
    • какие параметры принимает каждый метод;
    • структура запроса и ответа.
  4. Ограничения (Rate limits)

    • сколько запросов в секунду/минуту/час можно сделать;
    • как API сообщает, что лимит превышен;
    • как правильно обрабатывать такие ошибки.
  5. Ошибки

    • коды ответов (400, 401, 403, 404, 500, 503…);
    • формат ошибки в теле ответа;
    • примеры.
  6. SDK и примеры

    • есть ли готовые клиентские библиотеки;
    • примеры на разных языках.

Когда вы настроите эту базу, API-интеграция пойдет заметно проще.

Основы HTTP запросов при интеграции

Теперь давайте разберемся, как выглядит реальный HTTP запрос к внешнему API.

Пример базового запроса с cURL

Смотрите, я покажу вам пример простого GET запроса:

curl -X GET "https://api.example.com/v1/users/123" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"

Комментарии к этому примеру:

  • -X GET — указываем HTTP метод.
  • Authorization — здесь передаем токен, который выдали при регистрации приложения.
  • Accept — говорим, что хотим получить ответ в формате JSON.

Ответ сервера может выглядеть так:

{
  "id": 123,
  "email": "user@example.com",
  "name": "Alex Smith"
}

Если что-то пошло не так (например, неверный токен), сервер вернет ошибку:

{
  "error": "unauthorized",
  "message": "Invalid access token"
}

Пример POST запроса с телом JSON

Давайте разберемся на примере запроса на создание пользователя:

curl -X POST "https://api.example.com/v1/users" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "newuser@example.com",
    "name": "New User"
  }'

Что здесь важно:

  • Content-Type: application/json — говорим серверу, в каком формате отправляем данные;
  • -d '...' — тело запроса (body), сериализованное в JSON.

Ответ (201 Created):

{
  "id": 124,
  "email": "newuser@example.com",
  "name": "New User",
  "created_at": "2026-01-16T12:34:56Z"
}

Обратите внимание, многие API в случае успешного создания ресурса возвращают код 201 и объект ресурса.

Форматы данных в API интеграциях

JSON — де-факто стандарт

Подавляющее большинство современных API используют JSON. Он:

  • легко читается;
  • поддерживается всеми языками;
  • имеет простую структуру.

С точки зрения интеграции вам важно:

  • уметь сериализовать свои данные в JSON;
  • уметь безопасно распарсить JSON из ответа;
  • проверять наличие обязательных полей в ответе.

Пример обработки JSON в Node.js:

// Импортируем стандартный модуль для HTTP запросов (в примере используем fetch)
import fetch from "node-fetch";

async function getUser(userId) {
  // Формируем URL запроса
  const url = `https://api.example.com/v1/users/${userId}`;

  // Делаем HTTP запрос
  const response = await fetch(url, {
    method: "GET",
    headers: {
      // Передаем токен авторизации
      Authorization: `Bearer ${process.env.API_TOKEN}`,
      // Просим ответ в виде JSON
      Accept: "application/json",
    },
  });

  // Проверяем, что код ответа в диапазоне 200-299
  if (!response.ok) {
    // Если нет — бросаем ошибку с кодом и текстом
    throw new Error(
      `Request failed with status ${response.status} ${response.statusText}`
    );
  }

  // Парсим JSON из тела ответа
  const data = await response.json();

  // Возвращаем полученные данные
  return data;
}

Комментарии:

  • response.ok — удобное свойство, которое говорит, успешен ли ответ;
  • response.json() — парсит тело ответа; если там невалидный JSON, будет ошибка;
  • всегда оборачивайте такие вызовы в try/catch в вызывающем коде.

Другие форматы: XML, form-data, x-www-form-urlencoded

Вы можете встретить и другие форматы:

  • XML — часто в старых API;
  • multipart/form-data — при загрузке файлов;
  • application/x-www-form-urlencoded — в старых формах или OAuth запросах.

Если API работает не с JSON, обязательно смотрите в документации:

  • пример тела запроса;
  • обязательные заголовки Content-Type;
  • возможные нюансы кодировки.

Аутентификация и авторизация в API

Теперь давайте перейдем к одному из ключевых вопросов интеграции — как правильно передавать доступ.

API Key

Самый простой вариант: вам выдают строку, которую нужно передавать в заголовке или параметрах запроса.

Пример:

curl "https://api.example.com/v1/data" \
  -H "X-API-Key: YOUR_API_KEY"

Рекомендации по безопасности:

  • не храните ключи в коде (особенно в публичных репозиториях);
  • используйте переменные окружения или менеджеры секретов;
  • при логировании никогда не выводите ключ в лог полностью.

Bearer токены и OAuth 2.0

Здесь вы сначала получаете токен доступа, а затем используете его в заголовке Authorization.

Пример запроса с Bearer токеном:

curl "https://api.example.com/v1/profile" \
  -H "Authorization: Bearer ACCESS_TOKEN_VALUE"

Пример получения токена (очень упрощенный, granttype clientcredentials):

curl -X POST "https://auth.example.com/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET"

Ответ:

{
  "access_token": "ACCESS_TOKEN_VALUE",
  "token_type": "Bearer",
  "expires_in": 3600
}

Важно:

  • токен живет ограниченное время (expires_in);
  • нужно заранее продумывать механизм автообновления токена;
  • храните время истечения и обновляйте токен при необходимости.

Подписи запросов (HMAC, custom схемы)

Некоторые API требуют подписывать каждый запрос, чтобы защититься от подделки. Обычно схема такая:

  • у вас есть secret key;
  • вы формируете строку из метода, пути, тела запроса и timestamp;
  • вычисляете HMAC (например, SHA256);
  • отправляете подпись в заголовке.

Покажу вам, как это может выглядеть в коде на Node.js:

import crypto from "crypto";
import fetch from "node-fetch";

function signRequest({ method, path, body, secret, timestamp }) {
  // Собираем строку, которая будет подписана
  const payload = `${method}\n${path}\n${timestamp}\n${body || ""}`;

  // Вычисляем HMAC-SHA256 от этой строки
  return crypto.createHmac("sha256", secret).update(payload).digest("hex");
}

async function callSignedApi(path, bodyObj) {
  const method = "POST";
  const timestamp = Math.floor(Date.now() / 1000).toString();

  // Сериализуем тело запроса в JSON
  const body = JSON.stringify(bodyObj);

  // Вычисляем подпись
  const signature = signRequest({
    method,
    path,
    body,
    secret: process.env.API_SECRET,
    timestamp,
  });

  // Делаем запрос с подписью
  const response = await fetch(`https://api.example.com${path}`, {
    method,
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": process.env.API_KEY,
      "X-Signature": signature,
      "X-Timestamp": timestamp,
    },
    body,
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(
      `Signed request failed ${response.status} ${errorText.slice(0, 200)}`
    );
  }

  return response.json();
}

Здесь я размещаю пример, чтобы вам было проще понять идею: сервер может повторно вычислить подпись по тем же правилам и сравнить с полученной.

Проектирование клиентской библиотеки к API

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

Базовая структура клиента

Давайте посмотрим, как может выглядеть простой клиент.

// apiClient.js

import fetch from "node-fetch";

export class ApiClient {
  constructor({ baseUrl, apiKey, timeoutMs = 5000 }) {
    // Базовый URL API
    this.baseUrl = baseUrl;
    // Секретный ключ для авторизации
    this.apiKey = apiKey;
    // Таймаут для HTTP запросов
    this.timeoutMs = timeoutMs;
  }

  // Внутренний метод, который делает HTTP запрос
  async request(path, { method = "GET", query, body, headers = {} } = {}) {
    // Формируем полный URL
    const url = new URL(path, this.baseUrl);

    // Добавляем query-параметры к URL
    if (query) {
      Object.entries(query).forEach(([key, value]) => {
        if (value !== undefined && value !== null) {
          url.searchParams.set(key, String(value));
        }
      });
    }

    // Формируем объект опций для fetch
    const options = {
      method,
      headers: {
        "Content-Type": "application/json",
        "X-API-Key": this.apiKey,
        ...headers,
      },
    };

    // Если есть тело запроса — сериализуем его в JSON
    if (body !== undefined) {
      options.body = JSON.stringify(body);
    }

    // Запускаем таймаут и запрос параллельно
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
    options.signal = controller.signal;

    try {
      const response = await fetch(url.toString(), options);

      // Снимаем таймаут, если ответ получен вовремя
      clearTimeout(timeout);

      // Если код ответа не 2xx — выбрасываем ошибку
      if (!response.ok) {
        const errorText = await response.text();
        const err = new Error(
          `API request failed ${response.status} ${response.statusText}`
        );
        // Сохраняем дополнительную информацию в объекте ошибки
        err.status = response.status;
        err.body = errorText;
        throw err;
      }

      // Если тело пустое — возвращаем null
      if (response.status === 204) {
        return null;
      }

      // Пытаемся распарсить JSON
      return await response.json();
    } catch (err) {
      // Если запрос был прерван по таймауту
      if (err.name === "AbortError") {
        throw new Error(`API request timeout after ${this.timeoutMs} ms`);
      }
      // Пробрасываем остальные ошибки дальше
      throw err;
    }
  }

  // Публичный метод для получения пользователя
  getUser(userId) {
    // Используем общий метод request
    return this.request(`/users/${userId}`, { method: "GET" });
  }

  // Публичный метод для создания пользователя
  createUser({ email, name }) {
    return this.request("/users", {
      method: "POST",
      body: { email, name },
    });
  }
}

Обратите внимание, как этот фрагмент кода решает сразу несколько задач:

  • централизует авторизацию и таймауты;
  • позволяет добавлять новые методы (getUser, createUser) очень просто;
  • дает вам единый формат обработки ошибок.

Теперь вы увидите, как это выглядит в коде при использовании:

// main.js
import { ApiClient } from "./apiClient.js";

async function main() {
  // Инициализируем клиент один раз
  const client = new ApiClient({
    baseUrl: "https://api.example.com/v1",
    apiKey: process.env.API_KEY,
    timeoutMs: 7000,
  });

  try {
    // Получаем пользователя
    const user = await client.getUser(123);
    console.log("User", user);

    // Создаем пользователя
    const newUser = await client.createUser({
      email: "newuser@example.com",
      name: "New User",
    });
    console.log("Created user", newUser);
  } catch (err) {
    // Логируем ошибку
    console.error("API error", err.message);
  }
}

main();

Разделение на уровни

Хорошая практика — разделять:

  • слой чистого HTTP клиента (ответственный за запросы, таймауты, ретраи);
  • слой бизнес-логики (где вы решаете, когда и какие запросы делать).

Это упрощает тестирование и позволяет переиспользовать базовый клиент для разных частей системы.

Обработка ошибок при интеграции с API

Интеграции почти всегда ломаются там, где нет аккуратной обработки ошибок. Давайте разберем основные категории проблем.

Классы HTTP кодов

  • 2xx — успех (обычно 200, 201, 204);
  • 3xx — редирект (редко используется в API, но лучше быть готовым);
  • 4xx — ошибка на стороне клиента:
    • 400 — некорректный запрос;
    • 401 — неавторизован;
    • 403 — запрещено;
    • 404 — не найден ресурс;
    • 429 — превышен лимит запросов;
  • 5xx — ошибка на стороне сервера (временно недоступно, внутренняя ошибка и т.д.).

Что нужно делать при разных ошибках

  1. 400 Bad Request

    • проверяйте формат запроса;
    • убедитесь, что отправляете все обязательные поля;
    • используйте сообщения из тела ответа, если они есть.
  2. 401 Unauthorized, 403 Forbidden

    • проверьте токен или ключ;
    • для OAuth — возможно, токен устарел, нужно обновить;
    • не делайте бесконечных ретраев с тем же токеном.
  3. 404 Not Found

    • ресурс реально может не существовать (например, удален);
    • сохраните факт того, что ресурс отсутствует, чтобы не запрашивать его снова без необходимости.
  4. 429 Too Many Requests

    • реализуйте ожидание и повтор запроса после паузы;
    • читайте заголовки Retry-After, если API их отдает;
    • продумайте стратегию плавного уменьшения нагрузки.
  5. 5xx ошибки

    • используйте стратегию повторных попыток (retry) с экспоненциальной задержкой;
    • ограничивайте количество попыток.

Пример простой логики ретраев

Давайте посмотрим, как реализовать ретраи в Node.js.

// retryingApiClient.js

import fetch from "node-fetch";

async function fetchWithRetry(url, options, { retries = 3, backoffMs = 500 } = {}) {
  let attempt = 0;

  // Запускаем цикл попыток
  while (true) {
    try {
      const response = await fetch(url, options);

      // Если ошибка сервера или превышен лимит — решаем, делать ли повтор
      if (!response.ok) {
        if (
          response.status >= 500 || // Ошибка на стороне сервера
          response.status === 429 // Превышение лимита запросов
        ) {
          if (attempt < retries) {
            attempt += 1;
            const delay = backoffMs * attempt;
            // Ждем перед повторной попыткой
            await new Promise((resolve) => setTimeout(resolve, delay));
            continue;
          }
        }

        // Если ретраи не помогли — выбрасываем ошибку
        const errorBody = await response.text();
        const err = new Error(
          `Request failed after retries status=${response.status}`
        );
        err.status = response.status;
        err.body = errorBody;
        throw err;
      }

      // Если все хорошо — возвращаем ответ
      return response;
    } catch (err) {
      // Если это сетевая ошибка, а не HTTP (например, нет соединения)
      if (attempt < retries) {
        attempt += 1;
        const delay = backoffMs * attempt;
        await new Promise((resolve) => setTimeout(resolve, delay));
        continue;
      }
      // Если все попытки исчерпаны — пробрасываем ошибку
      throw err;
    }
  }
}

Теперь вы можете использовать эту функцию внутри своего клиента.

Важно понимать: ретраи стоит использовать осознанно. Не повторяйте запросы, которые создают ресурсы, если API не идемпотентно обрабатывает такие запросы.

Идемпотентность и безопасность повторных запросов

Что такое идемпотентность

Идемпотентная операция — это такая операция, которую можно выполнить несколько раз подряд, и результат будет таким же, как при одном выполнении.

Примеры:

  • GET /users/123 — безопасно вызывать много раз, данные только читаются;
  • PUT /users/123/email — выставить email в конкретное значение;
  • DELETE /users/123 — часто делается идемпотентным (повторный DELETE может вернуть 404 или 204, но не создаст нового пользователя).

Неидемпотентные операции:

  • POST /orders — каждый POST создает новый заказ;
  • POST /payments/charge — может списать деньги повторно.

Зачем это важно для интеграций

Когда вы добавляете ретраи или сталкиваетесь с сетевыми ошибками, запрос может:

  • быть выполнен на сервере, но ответ к вам не дошел;
  • не дойти до сервера вообще.

Если вы не уверены, что операция идемпотентна, повтор может привести к дублям (двойные списания, дубли заказов etc).

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

Многие платежные и критичные API вводят специальный заголовок (например, Idempotency-Key). Суть:

  • вы генерируете уникальный идентификатор операции;
  • передаете его в заголовке запроса;
  • сервер сохраняет результат первого успешного выполнения по этому ключу;
  • повторные запросы с тем же ключом возвращают тот же результат.

Пример:

curl -X POST "https://api.payments.com/v1/charges" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{
    "amount": 1000,
    "currency": "USD",
    "source": "card_123"
  }'

В своей интеграции:

  • генерируйте ключи для критичных операций;
  • храните их в базе данных вместе с состоянием операции;
  • при повторе используйте тот же ключ.

Пагинация и выборка больших объемов данных

Когда данных много, API редко отдает все сразу. Вместо этого используется пагинация.

Основные подходы к пагинации

  1. Пагинация по страницам:

    • параметры page и per_page;
    • пример: GET /users?page=2&per_page=50.
  2. Пагинация по смещению (offset, limit):

    • параметры offset и limit;
    • пример: GET /users?offset=100&limit=50.
  3. Cursor-based пагинация:

    • возвращается специальный токен (cursor, nextpagetoken);
    • вы используете его в следующем запросе.

Пример обхода пагинации

Давайте разберемся на примере пагинации по страницам.

// paginationExample.js

import fetch from "node-fetch";

async function fetchAllUsers() {
  const baseUrl = "https://api.example.com/v1/users";
  const perPage = 100;
  let page = 1;
  const allUsers = [];

  while (true) {
    const url = `${baseUrl}?page=${page}&per_page=${perPage}`;

    const response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${process.env.API_TOKEN}`,
        Accept: "application/json",
      },
    });

    if (!response.ok) {
      throw new Error(`Failed to fetch users page=${page}`);
    }

    const data = await response.json();

    // Предположим, что API возвращает массив пользователей
    if (!Array.isArray(data) || data.length === 0) {
      // Больше данных нет — выходим из цикла
      break;
    }

    // Добавляем текущую страницу к общему списку
    allUsers.push(...data);

    // Переходим к следующей странице
    page += 1;
  }

  return allUsers;
}

Как видите, этот код выполняет последовательные запросы, пока не дойдет до пустой страницы.

При работе с пагинацией важно:

  • не держать такие выборки в очень долгих транзакциях;
  • учитывать лимиты API (не делать слишком частые запросы);
  • по возможности обрабатывать данные постранично, а не собирать все в память.

Логирование и мониторинг интеграций

При работе с API ключевое значение имеет наблюдаемость. Вы должны понимать:

  • какие запросы вы делаете;
  • какие ошибки возникают;
  • где узкие места.

Что логировать

Минимальный набор:

  • метод и URL (без чувствительных данных);
  • статус ответа;
  • время выполнения;
  • коды ошибок и текст (частично, без секретов);
  • идентификаторы операций (request id, correlation id).

Важно: никогда не логируйте в явном виде токены, секреты, полные номера карт и другие чувствительные данные.

Пример простого логирования

// loggingWrapper.js

import fetch from "node-fetch";

async function loggedFetch(url, options = {}) {
  const start = Date.now();

  try {
    const response = await fetch(url, options);
    const duration = Date.now() - start;

    // Логируем информацию о запросе
    console.log(
      JSON.stringify({
        type: "api_request",
        method: options.method || "GET",
        url,
        status: response.status,
        duration_ms: duration,
      })
    );

    return response;
  } catch (err) {
    const duration = Date.now() - start;

    // Логируем ошибку запроса
    console.error(
      JSON.stringify({
        type: "api_error",
        method: options.method || "GET",
        url,
        duration_ms: duration,
        error: err.message,
      })
    );

    throw err;
  }
}

Здесь я показываю упрощенный вариант, но даже он уже дает базовое понимание, что происходит с вашими интеграциями.

Тестирование API интеграций

Интеграции нужно тестировать на нескольких уровнях:

  • модульные тесты — мокают HTTP слой и проверяют ваш клиентский код;
  • интеграционные тесты — работают с тестовым окружением внешнего сервиса;
  • end-to-end — проверяют полный сценарий (например, создание заказа и списание платежа).

Мокирование HTTP запросов

Для модульных тестов удобно подменять реальный HTTP клиент на фейковый.

Идея:

  • вы абстрагируете HTTP слой в отдельный интерфейс;
  • в тестах подставляете реализацию, которая возвращает заранее описанные ответы.

Пример простой подмены (на псевдокоде):

// userService.js

export class UserService {
  constructor({ apiClient }) {
    // Сохраняем абстрактный клиент (настоящий или тестовый)
    this.apiClient = apiClient;
  }

  async getUserEmail(userId) {
    // Вызываем метод клиента, не зная, реальный он или нет
    const user = await this.apiClient.getUser(userId);
    return user.email;
  }
}

В тесте:

// userService.test.js

class FakeApiClient {
  async getUser(userId) {
    // Возвращаем предсказуемый ответ для теста
    return { id: userId, email: "test@example.com" };
  }
}

async function testGetUserEmail() {
  const fakeClient = new FakeApiClient();
  const service = new UserService({ apiClient: fakeClient });

  const email = await service.getUserEmail(42);

  if (email !== "test@example.com") {
    throw new Error("Email does not match expected");
  }
}

testGetUserEmail();

Так вы тестируете свою логику независимо от реального API.

Интеграционные тесты с реальным API

Для критичных интеграций создают:

  • отдельный тестовый аккаунт в внешнем сервисе;
  • тестовый набор данных (например, тестовые карты в платежной системе);
  • набор сценариев, которые можно безопасно выполнять.

Рекомендации:

  • не запускайте такие тесты слишком часто, чтобы не упираться в лимиты;
  • отделяйте их от быстрых модульных тестов;
  • очищайте тестовые данные или используйте окружение, которое само сбрасывается.

Практический чек-лист для API интеграции

Чтобы вам было проще подходить к новой интеграции, соберу ключевые шаги в одном месте:

  1. Изучить документацию:

    • базовый URL;
    • способы аутентификации;
    • лимиты и ошибки;
    • форматы данных.
  2. Определить критичные сценарии:

    • какие операции действительно нужны вашему приложению;
    • какие из них чувствительны к повтору (неидемпотентны).
  3. Спроектировать клиентский модуль:

    • отдельный класс/пакет для этого API;
    • базовый метод request с таймаутами и логированием;
    • высокоуровневые методы для бизнес-операций.
  4. Добавить обработку ошибок:

    • разбор кодов 4xx и 5xx;
    • стратегия ретраев для подходящих ошибок;
    • разумное логирование.
  5. Позаботиться о безопасности:

    • хранить ключи в переменных окружения или менеджерах секретов;
    • не логировать чувствительную информацию;
    • учитывать требования по подписи запросов, если есть.
  6. Настроить тестирование:

    • модульные тесты с моками HTTP;
    • интеграционные тесты с тестовым окружением.
  7. Наблюдаемость:

    • логирование основных метрик;
    • мониторинг ошибок и таймаутов.

Если вы будете придерживаться этой последовательности, работа с API станет заметно предсказуемее и стабильнее.

Частозадаваемые технические вопросы по теме и ответы

Как аккуратно прокидывать токены и ключи через переменные окружения

Создайте конфигурационный модуль, который читает значения из окружения и проводит базовую валидацию.

Пример на Node.js:

// config.js

function getEnvVar(name) {
  const value = process.env[name];
  if (!value) {
    // Если переменная не задана — сразу бросаем ошибку при старте
    throw new Error(`Environment variable ${name} is required`);
  }
  return value;
}

export const config = {
  apiKey: getEnvVar("API_KEY"),
  apiBaseUrl: getEnvVar("API_BASE_URL"),
};

Так вы не позволите приложению незаметно работать без нужных секретов.

Как ограничить параллельное количество запросов к API

Используйте семафор или очередь. Идея — одновременно открыто не более N запросов. В Node.js можно взять готовые пакеты (например, p-limit) или реализовать простую очередь вручную.

Псевдокод:

  • создаете счетчик активных запросов;
  • если он меньше лимита — запускаете следующий запрос;
  • если достигнут лимит — ставите запрос в очередь;
  • по завершении запроса берете следующий из очереди.

Это помогает не превышать rate limit и не создавать всплески нагрузки.

Как реализовать кэширование ответов внешнего API

Простейший вариант:

  1. Считайте key = метод + URL + сериализованные параметры.
  2. Перед отправкой запроса проверяйте кэш (например, Redis или in-memory).
  3. Если запись есть и не протухла — возвращайте ее сразу.
  4. Если нет — делайте запрос, сохраняйте ответ с TTL и отдавайте дальше.

Важно: кэшируйте только те запросы, которые действительно безопасно кэшировать (обычно GET и явно документированные как кэшируемые).

Как организовать версионирование собственного API для других сервисов

Используйте версию в URL (например, /api/v1) или в заголовке. При изменениях:

  • не ломайте поведение старой версии;
  • добавляйте новую (/api/v2);
  • планируйте срок поддержки старой версии и сообщайте клиентам.

Внутри кода удобно держать отдельные контроллеры/хендлеры для разных версий, чтобы не запутаться.

Как отлаживать интеграцию если API недоступно локально

Используйте:

  • mock-серверы (например, WireMock, MockServer, json-server);
  • инструменты типа Postman Mock Server или Swagger Codegen Mock;
  • запись и воспроизведение реальных ответов (record-replay).

Подход:

  • сначала моделируете ответы API по документации;
  • строите свою логику на них;
  • затем проверяете ее на настоящем API и при необходимости корректируете моделированные ответы.
Стрелочка влевоМокирование данных - базовые и продвинутые техники для надежных тестов

Все гайды по Feature-sliced_design

Открыть базу знаний

Отправить комментарий