О насБлогКонтакты
Backend разработка18 апреля 2012 г. 4 мин 155Обновлено: 18 мая 2026 г.

Первый REST API для мобильного приложения: как мы учились проектировать API в 2012 году

AunimedaAunimeda
📋 Содержание

Первый REST API для мобильного приложения: как мы учились проектировать API в 2012 году

Весной 2012 года мы делали первый самостоятельный REST API для Android-приложения. До этого у нас были только веб-сайты - форм-сабмит, серверный рендеринг, сессии в куках. API для мобильного клиента был другим миром.

Мы прочли Roy Fielding's диссертацию (ну, её реферат). Прочли несколько статей на Хабре. И начали делать. Вот что вышло.


Ошибка №1: URL как имена процедур

Первая версия нашего API выглядела так:

GET  /api/getUser?id=123
POST /api/createOrder
GET  /api/getUserOrders?userId=123&status=active
POST /api/cancelOrder?orderId=456
GET  /api/getProductList?categoryId=2&page=1

Технически работало. Но это RPC через HTTP, не REST. Проблема проявилась через месяц: у нас было 40 endpoint'ов, все начинались с глагола, никакой структуры. Новый разработчик не мог предсказать URL - только смотреть в документацию.

Переписали по принципу «ресурсы, а не действия»:

GET    /api/v1/users/123              → получить пользователя
GET    /api/v1/users/123/orders       → заказы пользователя
GET    /api/v1/orders?status=active   → список заказов с фильтром
POST   /api/v1/orders                 → создать заказ
DELETE /api/v1/orders/456             → отменить заказ (не GET!)
GET    /api/v1/products?category=2    → список продуктов

Глаголы - HTTP-методы. Существительные - в URL. Новый разработчик мог угадать структуру.


Ошибка №2: API без версионирования

Первый месяц мы жили без версий в URL. Когда Android-клиент уже был у тестеров, нам потребовалось изменить структуру ответа /api/orders - добавить поле и переименовать существующее.

Мы изменили. Старый клиент сломался у тестеров. Нам повезло - приложение ещё не было в Play Store.

Версионирование через URL (/api/v1/) - самый простой вариант:

// routes.php - маршрутизация по версии
$version = $router->getVersion(); // Извлекает 'v1' из URL

switch ($version) {
    case 'v1':
        require 'controllers/v1/' . $controller . '.php';
        break;
    case 'v2':
        require 'controllers/v2/' . $controller . '.php';
        break;
    default:
        jsonResponse(400, ['error' => 'Unknown API version']);
}

Правило, которое мы приняли: v1 никогда не ломается. Новые фичи - в v2. Deprecated endpoint'ы в v1 живут ещё 6 месяцев, потом удаляются с предупреждением в заголовке.

// Deprecated endpoint: предупреждаем, но не ломаем
header('X-Deprecated: This endpoint will be removed on 2013-01-01. Use /v2/orders instead.');
// Возвращаем старую структуру ответа как обычно

Ошибка №3: токены в GET-параметрах

Первая аутентификация выглядела так:

GET /api/v1/orders?user_id=123&token=abc123def456

Проблема: токен в URL попадает в:

  • Логи сервера (access.log)
  • Историю браузера
  • Referrer-заголовок при переходе на другой сайт
  • Кэши прокси-серверов

Мы читали о HTTPS и думали, что это защищает. HTTPS шифрует передачу, но не мешает попаданию URL в логи на сервере.

Правильно: токен в заголовке Authorization:

GET /api/v1/orders
Authorization: Bearer abc123def456
// Извлечение токена из заголовка
function getAuthToken(): ?string {
    $header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
    if (preg_match('/^Bearer\s+(.+)$/i', $header, $m)) {
        return $m[1];
    }
    return null;
}

function authenticate(): ?array {
    $token = getAuthToken();
    if (!$token) {
        jsonResponse(401, ['error' => 'Authorization required']);
        exit;
    }
    
    $user = User::findByToken($token);
    if (!$user || $user->token_expires_at < time()) {
        jsonResponse(401, ['error' => 'Invalid or expired token']);
        exit;
    }
    
    return $user;
}

Ошибка №4: HTTP 200 на всё подряд

Первое время мы возвращали HTTP 200 с полем success: false при ошибках. Это заставляло клиент парсить тело ответа, чтобы понять - успех это или ошибка:

// Плохо: 200 OK с признаком ошибки внутри
HTTP/1.1 200 OK
{"success": false, "error": "User not found"}

Правильно использовать семантику HTTP-статусов:

function jsonResponse(int $status, array $data): void {
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data, JSON_UNESCAPED_UNICODE);
    exit;
}

// Использование
if (!$user) {
    jsonResponse(404, ['error' => 'User not found', 'code' => 'USER_NOT_FOUND']);
}
if (!$user->canAccessOrder($orderId)) {
    jsonResponse(403, ['error' => 'Access denied', 'code' => 'FORBIDDEN']);
}
if (!$validator->passes()) {
    jsonResponse(422, ['error' => 'Validation failed', 'fields' => $validator->errors()]);
}

// Успех
jsonResponse(200, ['data' => $order]);
// Создание ресурса
jsonResponse(201, ['data' => $newOrder, 'id' => $newOrder->id]);

Клиент на Android мог теперь проверить response.code() и обрабатывать ошибки без парсинга JSON:

if (!response.isSuccessful()) {
    // 4xx - ошибка клиента, показать пользователю
    // 5xx - ошибка сервера, показать "попробуйте позже"
    handleError(response.code());
    return;
}
// 2xx - обработать успешный ответ

Что получилось в итоге

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

  • URL-структура на основе ресурсов
  • Версионирование с /v1/ в пути
  • JWT-подобные токены в заголовке Authorization
  • Корректные HTTP-статусы
  • Единый формат ошибки {"error": "...", "code": "..."}
  • Документация в Markdown - по одной странице на endpoint

Первая версия API - это всегда обучение в бою. Важно не то, что вы сделали ошибки с версионированием и токенами. Важно что вы их заметили, пока приложение было у тестеров, а не у 50,000 пользователей с разными версиями клиента, которые все сломались одновременно.

Читайте также

Как перейти с монолита на микросервисы на PHP: реальный опыт 2016 годаaunimeda
Backend разработка

Как перейти с монолита на микросервисы на PHP: реальный опыт 2016 года

В 2016 году микросервисы стали модным словом. Мы разделили монолитный Laravel-проект на три сервиса: auth, catalog, orders. Не всё прошло гладко. Вот что работало, что не работало, и когда микросервисы помогают, а когда это преждевременная оптимизация.

Чистая архитектура в Node.js: практическое руководство без академизмаaunimeda
Backend разработка

Чистая архитектура в Node.js: практическое руководство без академизма

Чистая архитектура звучит хорошо в теории. На практике большинство реализаций добавляют сложность без пользы. Показываем паттерн, который реально работает в production Node.js TypeScript проектах - инверсия зависимостей, use cases, repository pattern с рабочим кодом.

Node.js vs Bun vs Deno 2026: какой JavaScript runtime выбратьaunimeda
Backend разработка

Node.js vs Bun vs Deno 2026: какой JavaScript runtime выбрать

Bun 1.x стабилен в production. Deno 2.0 поддерживает npm. Node.js 22 запускает TypeScript нативно. Реальные бенчмарки, сравнение экосистем и конкретные рекомендации - для новых и существующих проектов.

Нужна IT-разработка для вашего бизнеса?

Разрабатываем сайты, мобильные приложения и AI-решения для бизнеса в России. Бесплатная консультация.

Разработка Telegram-ботов

Получить консультацию Все статьи