Два робота, левый даёт кристалл, а правый одобряет с большим пальцем
Иллюстрация: Кира Кустова

Что такое API

Время чтения: 10 мин

Кратко

Секция статьи "Кратко"

Разные программы могут быть написаны на разных языках.

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

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

Давайте сразу рассмотрим пример: мы работаем в «Twitter» и делаем фичу для браузерного приложения на JavaScript.

Когда нам нужны какие-то данные, мы запрашиваем их у сервера. Однако сервер написан, скорее всего, не на JavaScript, а на каком-то другом языке: Python, C#, Java. Чтобы сервер понял, что мы от него хотим, нам нужно как-то объяснить наш запрос.

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

API (Application Programming Interface) — это набор фич, которые одна программа представляет всем остальным. Она как бы говорит: «Смотрите, со мной можно говорить вот так и вот так, можете меня спросить о том-то через эту часть, а попросить что-то сделать — через эту».

В случае c клиент-серверным общением (см. Как работают веб-приложения) API может выступать как набор ссылок, по которым клиент обращается на сервер:

  • POST /api/v1.0/users — для создания пользователя;
  • GET /api/v1.0/users — для получения списка пользователей.

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

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

Представьте, что вы написали модуль CreditCalculator, который считает проценты по кредитам какого-нибудь банка. Чтобы воспользоваться этим модулем в других частях программы, вы экспортируете его функции наружу. Эти функции — это API этого модуля.

Или, например, вы написали плагин для Gulp, который минифицирует HTML-код. Если вы пользовались функциями, которые Gulp предоставляет, вы пользовались Gulp API.

Или вы пишете программку для Arduino, которая автоматически включает свет, если в комнате стало темно. Работая с Arduino SDK, вы пользуетесь их API.

Идеальное API

Секция статьи "Идеальное API"

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

Бесшовность

Секция статьи "Бесшовность"

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

Невозможно написать «универсальный модуль», который бы подошёл к любому проекту. Но зато возможно написать модуль, который бы был достаточно абстрактным. В таком случае, чтобы добавить его в проект, нам потребуется какое-то количество дополнительного кода, но мы сможем использовать уже написанные функции.

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

Быстродействие

Секция статьи "Быстродействие"

Вернёмся к Twitter API. Если мы хотим создать нового пользователя, нам нужно передать на сервер данные об этом пользователе. Мы не можем сделать это с помощью JS-объекта, потому что сервер использует другой язык. Значит, нам надо «перевести» данные на какой-то промежуточный язык (чаще всего это JSON).

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

Всё это требует времени. Чем меньше времени тратится на общение и выполнение нужных действий, тем лучше спроектировано API.

Понятность

Секция статьи "Понятность"

Чем точнее названы функции, методы или ссылки в API, тем меньше заблуждений и ошибок будет возникать при работе с ним.

Полнота

Секция статьи "Полнота"

Как правило, разработчикам хочется производить как можно меньше операций, из-за чего «перевод» может оказаться неточным: в нём может не хватать данных или, наоборот, быть слишком много.

Чем грамотнее спроектировано API (а скорее даже вся программная система), тем более полным будет ответ на каждое конкретное действие.

В идеальном API все эти проблемы решены, но идеального API не существует 😃

Какие API бывают

Секция статьи "Какие API бывают"

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

Прочитайте статью «Как работают веб-приложения», чтобы глубже разобраться в клиент-серверной архитектуре.

REST

Секция статьи "REST"

REST (Representational State Transfer) — стиль общения компонентов, при котором все необходимые данные указываются в параметрах запроса.

REST сейчас — один из самых распространённых стилей API в интернете.

Отличительная особенность этого стиля — это стиль построения адресов и выбор метода. Всё взаимодействие между клиентом и сервером сводится к 4 операциям (CRUD):

  • созданию чего-либо, например, объекта пользователя (create, C);
  • чтению (read, R);
  • обновлению (update, U);
  • удалению (delete, D).

Для каждой из операций есть собственный HTTP-метод:

  • POST для создания;
  • GET для чтения;
  • PUT, PATCH для обновления;
  • DELETE для удаления.

Разница между PUT и PATCH в том, что PUT обновляет объект целиком, а PATCH — только указанное поле.

Адрес, как правило, остаётся почти одинаковым, а детали запроса указываются в HTTP-методе и параметрах или теле запроса.

Например

Если бы мы писали API для интернет-магазина, то CRUD для заказа мог бы выглядеть следующим образом:

  • POST /api/orders/ — создать новый заказ. Как правило, в ответ на POST-запрос сервер возвращает ID созданной сущности, в нашем случае — ID заказа. Пусть будет 42.
  • GET /api/orders/42 — получить заказ с номером 42. В ответ мы получим JSON, XML, HTML с данными о заказе (сейчас чаще всего — JSON).
  • PUT /api/orders/42 — обновить заказ с номером 42. Вместе с запросом мы отправляем данные, которыми надо обновить этот заказ. В ответ сервер ответит или статусом 204 (всё хорошо, но контента в ответе нет), или ID обновлённой сущности.
  • DELETE /api/orders/42 — удалить заказ с номером 42. Как правило, в ответ присылается или 204, или ID удалённой сущности.

Чаще всего при работе с API веб-сервисов вам будет попадаться именно REST или что-то похожее на него.

Плюсы:

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

Минусы:

  • если спроектирован плохо, может отправлять или слишком много информации, либо слишком мало. (Но для обхода этой проблемы можно использовать backend for frontend).

SOAP

Секция статьи "SOAP"

Вообще, не очень корректно сравнивать SOAP и REST, потому что REST — это архитектурный стиль, а SOAP — формат обмена данными. Поэтому мы не будем их сравнивать, а просто расскажем, как работает SOAP.

SOAP (Simple Object Access Protocol) — формат обмена данными.

Это структурированный формат обмена данными, то есть, каждое сообщение следует определённой структуре. Чаще всего вместе с SOAP используется XML для отражения этой структуры.

Сама структура выглядит так:

Схема структуры SOAP пакета
  • Envelope — корневой элемент, который определяет само сообщение.
  • Header содержит атрибуты сообщения, например: информацию о безопасности.
  • Body содержит сообщение, которым обмениваются приложения.
  • Fault необязательный элемент с ошибками обработки, если они были.

Сообщение-запрос к интернет-магазину может выглядеть так:

        
          
          <?xml version="1.0" encoding="utf-8"?><soap:Envelope  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:xsd="http://www.w3.org/2001/XMLSchema"  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">  <soap:Body>    <getOrderDetails xmlns="https://example-store.com/orders">      <orderID>42</orderID>    </getOrderDetails>  </soap:Body></soap:Envelope>
          <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <getOrderDetails xmlns="https://example-store.com/orders">
      <orderID>42</orderID>
    </getOrderDetails>
  </soap:Body>
</soap:Envelope>

        
        
          
        
      

А ответ:

        
          
          <?xml version="1.0" encoding="utf-8"?><soap:Envelope  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:xsd="http://www.w3.org/2001/XMLSchema"  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">  <soap:Body>    <getOrderDetailsResponse xmlns="https://example-store.com/orders">      <getOrderDetailsResult>        <orderID>42</orderID>        <userID>43</userID>        <dateTime>2020-10-10T12:00:00</dateTime>        <products>            <productID>1</productID>            <productID>23</productID>            <productID>45</productID>        </products>      </getOrderDetailsResult>    </getOrderDetailsResponse>  </soap:Body></soap:Envelope>
          <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <getOrderDetailsResponse xmlns="https://example-store.com/orders">
      <getOrderDetailsResult>
        <orderID>42</orderID>
        <userID>43</userID>
        <dateTime>2020-10-10T12:00:00</dateTime>
        <products>
            <productID>1</productID>
            <productID>23</productID>
            <productID>45</productID>
        </products>
      </getOrderDetailsResult>
    </getOrderDetailsResponse>
  </soap:Body>
</soap:Envelope>

        
        
          
        
      

При этом в SOAP неважно, каким методом передавать сообщения, в отличие от REST.

SOAP не очень прижился, потому что достаточно многословен и неудобен для работы на клиенте: XML проигрывает JSON, а SOAP, построенный на JSON — это довольно редкий случай.

Плюсы:

  • не зависит от методов передачи;
  • есть структура сообщения.

Минусы

  • многословен;
  • проигрывает REST в простоте.

RPC

Секция статьи "RPC"

RPC (Remote Procedure Call) — это такой стиль, при котором в сообщении запроса хранится и действие, которое надо выполнить, и данные, которые для этого действия нужны.

Так как мы больше говорим о вебе, то можно грубо сказать, что RPC — это «вызов серверной функциональности из браузера».

В вебе более часто использовались XML-RPC и JSON-RPC. Мы будем рассматривать примеры на JSON-RPC, просто потому что JSON сейчас используется чаще, и его проще читать.

Сообщение-запрос по протоколу JSON-RPC должно иметь 3 обязательных поля:

  • method — строка с именем вызываемого метода.
  • params — массив данных, которые должны быть переданы методу, как параметры.
  • id — значение любого типа, которое используется для установки соответствия между запросом и ответом.

В ответ сервер должен прислать сообщение, содержащее:

  • result — данные, которые вернул метод. Если произошла ошибка во время выполнения метода, это свойство должно быть установлено в null.
  • error — код ошибки, если произошла ошибка во время выполнения метода, иначе null.
  • id — то же значение, что и в запросе, к которому относится этот ответ.

На примере всё с тем же магазином, получение заказа было бы реализовано примерно так:

Запрос:

        
          
          {  // Для последней спецификации следует указывать версию:  "jsonrpc": "2.0",  // Далее указываем метод:  "method": "orders.get",  // В параметрах указываем ID заказа,  // который нас интересует:  "params": [42],  // ID этого запроса.  // Он может понадобиться,  // когда система обрабатывает несколько запросов параллельно  "id": 1}
          {
  // Для последней спецификации следует указывать версию:
  "jsonrpc": "2.0",

  // Далее указываем метод:
  "method": "orders.get",

  // В параметрах указываем ID заказа,
  // который нас интересует:
  "params": [42],

  // ID этого запроса.
  // Он может понадобиться,
  // когда система обрабатывает несколько запросов параллельно
  "id": 1
}

        
        
          
        
      

Успешный ответ:

        
          
          {  "jsonrpc": "2.0",  // В result данные о заказе:  "result": {    "orderId": 42,    "userId": 43,    "dateTime": "2020-10-10T12:00:00",    "products": [      { "productID": 1 },      { "productID": 23 },      { "productID": 45 }    ]  },  "error": null,  "id": 1}
          {
  "jsonrpc": "2.0",

  // В result данные о заказе:
  "result": {
    "orderId": 42,
    "userId": 43,
    "dateTime": "2020-10-10T12:00:00",
    "products": [
      { "productID": 1 },
      { "productID": 23 },
      { "productID": 45 }
    ]
  },
  "error": null,
  "id": 1
}

        
        
          
        
      

Ответ с ошибкой:

        
          
          {  "jsonrpc": "2.0",  "result": null,  "error": "Order not found",  "id": 1}
          {
  "jsonrpc": "2.0",
  "result": null,
  "error": "Order not found",
  "id": 1
}

        
        
          
        
      

Плюсы:

  • есть структура сообщения;
  • использует JSON, что делает его проще для чтения и написания;
  • производителен, если нужны batch-запросы.

Минусы:

  • слишком много логики уходит на клиент;
  • HTTP-кэширование недоступно.

На практике

Секция статьи "На практике"

Саша Беспоясов

Секция статьи "Саша Беспоясов"

Иногда мы пользуемся какими-то API даже того не замечая, а иногда не можем разобраться, чего от нас хотят. Вот два разных примера:

Браузерное API

Секция статьи "Браузерное API"

Одни из самых знакомых фронтенд-разработчикам API — браузерные :–) Всё, что по умолчанию есть в window — это браузерное API.

Мы описали самые важные из них в статье «Браузерное окружение, BOM»

скриншот доступных браузерных API

Когда мы пользуемся консолью для отладки, мы тоже используем это API:

        
          
          console.log("Is the error here?")
          console.log("Is the error here?")

        
        
          
        
      

Или когда обращаемся к localStorage:

        
          
          localStorage.setItem("key", "value")
          localStorage.setItem("key", "value")

        
        
          
        
      

Twitter API

Секция статьи "Twitter API"

У Твитера, в принципе, не самое плохое API, которое можно встретить, хотя и дико запутанная документация.

Например, чтобы получить список твитов:

  1. Надо использовать метод GET.
  2. URL для запроса — https://api.twitter.com/2/tweets.
  3. Обязательный аргумент — ids, чтобы указать, какие именно твиты надо получить. Технически вот этот адрес: https://api.twitter.com/2/tweets?ids=1261326399320715264,1278347468690915330 запрошенный через GET должен сработать, но нужно использовать HTTP-заголовки для аутентификации
  4. Authorization: Bearer $BEARER_TOKEN

И только при выполнении всех эти условий, можно получить ответ на запрос:

        
          
          const response = await fetch(  "https://api.twitter.com/2/tweets?ids=1261326399320715264,1278347468690915330",  {    // можно не указывать, так как он по умолчанию    method: "GET",    headers: {      Authorization: `Bearer ${BEARER_TOKEN}`,    },  })await response.json()
          const response = await fetch(
  "https://api.twitter.com/2/tweets?ids=1261326399320715264,1278347468690915330",
  {
    // можно не указывать, так как он по умолчанию
    method: "GET",
    headers: {
      Authorization: `Bearer ${BEARER_TOKEN}`,
    },
  }
)

await response.json()

        
        
          
        
      

Ответ:

        
          
          {  "data": [    {      "id": "1261326399320715264",      "text": "Tune in to the @MongoDB @Twitch stream featuring our very own @suhemparack to learn about Twitter Developer Labs - starting now! https://t.co/fAWpYi3o5O"    },    {      "id": "1278347468690915330",      "text": "Good news and bad news: \n\n2020 is half over"    }  ]}
          {
  "data": [
    {
      "id": "1261326399320715264",
      "text": "Tune in to the @MongoDB @Twitch stream featuring our very own @suhemparack to learn about Twitter Developer Labs - starting now! https://t.co/fAWpYi3o5O"
    },
    {
      "id": "1278347468690915330",
      "text": "Good news and bad news: \n\n2020 is half over"
    }
  ]
}