Чем мобильный API отличается от обычного

Когда проектируешь API для веб-приложения, можно позволить себе расслабиться. Браузер обновляется автоматически, клиентский код всегда свежий, а если что-то сломалось — просто перезагрузи страницу. С мобилками всё иначе.

Пользователь может годами сидеть на старой версии приложения. Он не обновляется, потому что «всё и так работает» или потому что у него старое устройство и новая версия уже не поддерживается. Ваш сервер обязан корректно отвечать этому пользователю.

Плюс мобильный трафик — это часто слабый 4G в метро или вообще Edge в деревне. Каждый лишний килобайт в ответе — это время загрузки, расход батареи и раздражённый пользователь. Поэтому проектирование API для мобильных приложений — отдельная дисциплина со своими правилами.

REST или GraphQL — что выбрать

Большинство мобильных API строятся на REST. Это понятно, предсказуемо, хорошо документируется и работает из коробки в любом HTTP-клиенте. Если команда небольшая — берите REST.

GraphQL даёт клиенту контроль над тем, какие поля запрашивать. Это звучит как идеальное решение для мобилки: запросил только нужные поля — получил меньше данных. На практике всё чуть сложнее. GraphQL добавляет сложность на сервере, требует нормального инструментария для кеширования (стандартный HTTP-кеш тут работает иначе), и его нужно уметь готовить. Если у вас сложные вложенные данные и несколько типов клиентов с разными потребностями — GraphQL оправдан. Если у вас условный интернет-магазин и iOS-приложение — REST справится.

Есть ещё gRPC — бинарный протокол от Google, очень быстрый и компактный. Хорошо подходит для внутреннего общения между микросервисами, но для мобильного API используется редко: отлаживать сложнее, инструментарий меньше.

Версионирование — не опция, а необходимость

Это одно из немногих правил, которое нарушать нельзя. Если вы не версионируете API с самого начала, рано или поздно сломаете старые клиенты.

Есть два популярных способа:

URL-версионирование:

GET /api/v1/products
GET /api/v2/products

Header-версионирование:

GET /api/products
Accept: application/vnd.myapp.v2+json

URL-версионирование нагляднее: видно прямо в логах и браузере. Header-версионирование считается «более RESTful», но менее очевидно. Для большинства проектов URL-версионирование — прагматичный выбор.

Когда выпускаете v2, не удаляйте v1 сразу. Дайте пользователям время обновиться. Хороший срок — 6–12 месяцев после анонса устаревания. Присылайте заголовок Deprecation в ответах старой версии — мобильные разработчики это замечают в логах.

Структура ответов

Договоритесь о единой структуре ответа и не отступайте от неё. Типичный рабочий вариант:

{
  "data": { ... },
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 143
  },
  "error": null
}

При ошибке:

{
  "data": null,
  "error": {
    "code": "PRODUCT_NOT_FOUND",
    "message": "Товар не найден",
    "details": {}
  }
}

Несколько важных моментов:

  • Не меняйте типы полей. Если id был строкой — пусть остаётся строкой. Переедете с int на string — старые клиенты упадут.
  • Добавляйте поля без страха. Добавление новых полей — не breaking change. Удаление или переименование — breaking change.
  • Используйте null явно. Не пропускайте поле в ответе, если значения нет — верните null. Отсутствующее поле и null — разные вещи для клиента.

Пагинация

Для мобилки чаще всего нужна курсорная пагинация, а не постраничная через offset/limit.

Постраничная пагинация проста:

GET /api/posts?page=3&per_page=20

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

Курсорная пагинация работает иначе. Клиент получает cursor — непрозрачный токен, указывающий на место в выборке:

{
  "data": [ ... ],
  "meta": {
    "next_cursor": "eyJpZCI6MTIzfQ==",
    "has_more": true
  }
}

Следующий запрос:

GET /api/posts?cursor=eyJpZCI6MTIzfQ==&per_page=20

Это надёжнее и хорошо масштабируется на больших объёмах данных. Instagram, Twitter и большинство современных мобильных API используют именно курсоры.

Оптимизация трафика

Мобильный трафик стоит денег и времени пользователя. Несколько обязательных вещей.

Сжатие. Включите gzip или brotli для всех текстовых ответов. JSON сжимается в 5–10 раз. Это бесплатные миллисекунды загрузки.

Минимальные поля в списках. Список из 20 товаров для каталога не должен возвращать полное описание каждого товара, все фотографии и технические характеристики. Возвращайте только то, что нужно для карточки: id, name, price, thumbnail. Полные данные — только при открытии конкретного товара.

Разница может быть кратной: полный объект товара — 2 КБ, карточка для списка — 200 байт. Умножьте на 20 элементов — получите 40 КБ против 4 КБ.

ETag и условные запросы. Сервер отдаёт заголовок ETag с хешем ответа. Клиент при следующем запросе шлёт If-None-Match. Если данные не изменились — сервер отвечает 304 Not Modified без тела. Экономия трафика при polling.

Аутентификация

Стандарт для мобильных API — JWT в паре с refresh-токеном.

Схема такая:

  1. Пользователь логинится — получает access_token (живёт 15–60 минут) и refresh_token (живёт 30–90 дней).
  2. Все запросы идут с Authorization: Bearer <access_token>.
  3. Когда access_token истекает, клиент автоматически делает запрос с refresh_token и получает новую пару.

Несколько правил:

  • Храните токены в Keychain (iOS) или EncryptedSharedPreferences (Android). Никогда в обычном UserDefaults или AsyncStorage без шифрования.
  • Refresh-токены должны быть одноразовыми (rotation). Использовали — получили новый. Если старый refresh-токен использован повторно — признак кражи, отзываете всю сессию.
  • Добавьте device_id при логине. Пусть пользователь видит список устройств в настройках и может завершить сессию удалённо.

Для OAuth (вход через Google, Apple) используйте PKCE — это стандарт для нативных приложений без серверного компонента.

Обработка ошибок

Плохая обработка ошибок — одна из самых частых причин, почему мобильное приложение кажется ненадёжным. Сервер вернул 500, клиент показал белый экран — пользователь удалил приложение.

HTTP-коды используйте правильно. 400 — плохой запрос клиента, 401 — не авторизован, 403 — нет прав, 404 — не найдено, 422 — данные не прошли валидацию, 429 — слишком много запросов, 500 — ошибка сервера. Не возвращайте 200 с {"success": false} — это анти-паттерн.

Machine-readable коды ошибок. Рядом с HTTP-кодом возвращайте строковый код:

{
  "error": {
    "code": "EMAIL_ALREADY_EXISTS",
    "message": "Этот email уже используется"
  }
}

Клиент реагирует на code, а message показывает пользователю. Это позволяет менять текст ошибки на сервере без обновления приложения.

Ошибки валидации с полями:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "fields": {
      "email": "Некорректный формат",
      "phone": "Обязательное поле"
    }
  }
}

Клиент подсвечивает конкретные поля в форме — пользователь понимает, что исправить.

Идемпотентность и потеря связи

Мобильные пользователи регулярно теряют соединение. Когда клиент не получил ответ из-за timeout, он не знает, выполнился ли запрос. Если операция не идемпотентна, повторный запрос создаст дубликат.

Решение — Idempotency-Key. Клиент генерирует UUID и шлёт в заголовке:

POST /api/orders
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

Сервер запоминает ключ и результат. При повторном запросе с тем же ключом — возвращает сохранённый результат. Stripe использует этот подход для всех платёжных операций.

Также не держите запрос подвешенным бесконечно. Для большинства эндпоинтов 5–10 секунд — максимум. Лучше вернуть 503 и дать клиенту сделать retry, чем держать соединение открытым.

Документация и контракт

API без документации — это не API, а угадайка.

Минимум — OpenAPI (Swagger) спецификация. Она генерируется из кода или пишется вручную, позволяет автоматически генерировать клиентский SDK для iOS и Android, даёт интерактивную документацию.

Контракт с мобильными разработчиками: любое изменение, ломающее обратную совместимость, должно быть согласовано заранее. Не «я задеплоил, теперь обновляйте», а «через две недели деплоим v2, вот что изменится».

Хороший способ держать контракт — contract testing. Pact — популярный инструмент: мобильный разработчик описывает, какие запросы делает и что ожидает получить, серверная сторона прогоняет эти сценарии в CI. Если сервер поломал контракт — CI падает до деплоя.

Мониторинг мобильного API

Несколько метрик, которые стоит отслеживать отдельно для мобильного трафика:

  • 95-й и 99-й перцентиль времени ответа. Медиана обманывает: если 1% запросов отвечает за 10 секунд, у вас проблема, которую медиана скрывает.
  • Распределение по версиям API. Видно, сколько пользователей ещё на старых клиентах — помогает решать, когда удалять устаревшую версию.
  • Ошибки по типу устройств. Иногда конкретная версия Android или iOS воспроизводит баг, которого нет у других.

User-Agent из мобильного приложения должен содержать версию приложения и платформу:

MyApp/2.3.1 (iOS 17.4; iPhone14,2)

Это позволяет фильтровать метрики по версиям клиента и быстро локализовать проблему.

Если вам нужен бэкенд с нормально спроектированным API для мобильного приложения, REEXY занимается этим в рамках интеграции сервисов и разработки — от 1 500 ₽. Подробности на r3xy.ru.

Правильно спроектированный API — это инвестиция, которая окупается месяцами спокойной жизни. Неправильный — бесконечные патчи, злые мобильные разработчики и пользователи, которые видят белый экран вместо вашего продукта.