Чем мобильный 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-токеном.
Схема такая:
- Пользователь логинится — получает
access_token (живёт 15–60 минут) и refresh_token (живёт 30–90 дней).
- Все запросы идут с
Authorization: Bearer <access_token>.
- Когда 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 — это инвестиция, которая окупается месяцами спокойной жизни. Неправильный — бесконечные патчи, злые мобильные разработчики и пользователи, которые видят белый экран вместо вашего продукта.