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

Что такое API

Как разговаривать с инопланетянами? А с программой, написанной на другом языке? Просто добавь API.

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

Кратко

Скопировано

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

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

Именно для того, чтобы подружить разные модули, системы, языки, программы — и существуют 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, при котором такое использование доставляет меньше всего проблем — наиболее бесшовный.

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

Скопировано

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

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

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

Понятность

Скопировано

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

Полнота

Скопировано

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

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

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

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

Скопировано

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

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

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 и 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 (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 — браузерные :–) Всё, что по умолчанию есть в 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

Скопировано

У Твитера, в принципе, не самое плохое 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"
    }
  ]
}