Как написать удобный API — 10 рекомендаций
Я разработчик и большую часть моей карьеры я строю API различных сервисов. Рекомендации для этой статьи были собраны на основе наиболее часто встречающихся проблем при проектировании своего сервиса в команде или использовании сторонних API.
Скорее всего, вы сталкивались с провайдерами ужасного API. Работа с ними, как правило, сопряжена c повышенной эмоциональностью и недопониманием. Большую часть таких проблем можно избежать, проектируя интерфейс приложения, используя советы ниже.
1. Не используйте глаголы в URL *
* — если это одна из CRUD-операций.
За действие с ресурсом отвечают CRUD-методы запроса: POST — создать (create), GET — получить (read), PUT/PATH — обновить (update), DELETE — удалить (ну вы поняли). Плохо:
POST /users//delete - удаление пользователя POST /bookings//update - обновление бронировки
Хорошо:
DELETE /users/ PUT /bookings/
2. Используйте глаголы в URL
Плохо:
POST /users//books//create - добавить книгу пользователю
Хорошо:
POST /users//books//attach POST /users//notifications/send - отправить уведомление пользователю
3. Выделяйте новые сущности
Выше есть пример добавления книги пользователю, возможно, логика вашего приложения подразумевает список избранного, тогда роут может быть и таким:
POST /wishlist//
4. Используйте один идентификатор ресурса *
* — если ваша структура данных это позволяет.
Это значит если у вас есть записи вида один ко многим, например
бронь -> путешественники (booking->travellers), вам будет достаточно передавать в запросе идентификатор путешественника.
Плохо:
# получение данных путешественника GET /bookings//travellers/
Хорошо:
GET /bookings/travellers/
Также замечу, что /bookings/travellers/ лучше, чем просто /travellers . Хорошо придерживаться иерархии данных в своем API.
5. Все ресурсы во множественном числе
Плохо:
GET /user/ - получение данных пользователя POST /ticket//book - бронирование билета
Хорошо:
GET /users/ POST /tickets//book
6. Используйте HTTP-статусы по максимуму
Самый простой способ обработки ошибок — это ответить соответствующим кодом состояния. В большинстве случает один этот статус может дать исчерпывающую информацию о результате обработки запроса. Одни из самых распространенных кодов ответов:
- 400 Bad Request — клиент отправил неверный запрос, например, отсутствует обязательный параметр запроса.
- 401 Unauthorized — клиенту не удалось пройти обязательную аутентификацию на сервере для обработки запроса.
- 403 Forbidden — клиент аутентифицирован, но не имеет разрешения на доступ к запрошенному ресурсу.
- 404 Not Found — запрошенный ресурс не существует.
- 409 Conflict — этот ответ отправляется, когда запрос конфликтует с текущим состоянием сервера.
- 500 Internal Server Error — на сервере произошла общая ошибка.
- 503 Service Unavailable — запрошенная услуга недоступна.
7. Модификаторы получения ресурса
Логика построения роутов может быть не связана с архитектурой проекта или структурой базы данных. Например, в бд есть викторины и пройденные викторины — две отдельные таблицы (quizzes и passed_quizzes). Но для апи это могут быть просто викторины, а пройденные викторины это модификатор.
Пример: /quizzes и /quizzes/passed . Здесь quizzes — ресурс (викторины), passed — модификатор (пройденные).
Плохо:
GET /passed-quizzes - получение пройденных викторин GET /booked-tickets - получение забронированных билетов POST /gold-users - создание премиум пользователя
Хорошо:
GET /tickets/booked POST /users/gold
8. Выберите одну структуру ответов
Когда на два запроса к API может быть получен совсем разный по структуре ответ — это грустно. Старайтесь сформировать одну четкую структуру, которой всегда будете придерживаться. Будет круто еще включить служебные поля, несущие дополнительную информацию.
Плохо:
GET /book/
Хорошо:
GET /book/ < "status": 0, "message": "ok", "data": >
В этом примере 3 поля универсальны и могут использоваться для любого ответа от апи. status , message — собственный статус и сообщение приложения по которому клиент сможет ориентироваться, эти поля сообщат ему дополнительную информацию о процессе обработки запроса, но не данные ресурса. Например, в нашем приложении в один момент времени, пользователь может проходить только одну викторину. Тогда запрос на начало новой может выдать 409-й статус, а в полях status и message — дополнительную информацию, почему была получена ошибка.
9. Все параметры и json в camelCase
9.1 В параметрах запросов
Плохо:
GET /users/ GET /users/ GET /users/
Хорошо:
GET /users/ POST /ticket//gold
9.2 В теле ответа или принимаемого запроса
Плохо:
Хорошо:
10. Пользуйтесь Content-Type
Плохо:
GET /tickets.json GET /tickets.xml
Хорошо:
GET /tickets // и в хедере Сontent-Type: application/json // или Сontent-Type: application/xml
Заключение
Перечисленные выше рекомендации это далеко не весь список способов сделать API лучше. Для дальнейшего изучения рекомендую разобрать спецификации REST API и список кодов http-статусов (вы удивитесь, насколько их много и какие ситуации они охватывают).
А в комментариях предлагаю написать свою рекомендацию по построению REST API, которую вы считаете важной.
CRUD — HTTP API
В программировании часто используется аббревиатура CRUD . Она обозначает четыре базовых операции над информацией:
- Создание
- Чтение
- Обновление
- Удаление
CRUD строят вокруг пользователя или какой-то другой сущности. Для этого создают либо интерфейс с формами, либо HTTP API эндпоинты. В этом уроке мы научимся проводить все эти операции на примере сервиса DummyJSON .
В целях безопасности этот сервис не выполняет реальных изменений на сервере. Все операции выглядят так, как будто они вносят изменения, но изменения на самом деле не происходят.
Посмотрим на пример данных:
| Метод | URL | Операция |
|---|---|---|
| GET | /users | Список пользователей |
| GET | /users/1 | Информация о пользователе |
| POST | /users/add | Добавление пользователя |
| PATCH | /users/1 | Обновление пользователя |
| DELETE | /users/1 | Удаление пользователя |
Как видите, в URL постоянно повторяется 1 — это идентификатор конкретного пользователя. Он будет меняться в зависимости от того, с каким пользователем мы работаем прямо сейчас.
Кстати, идентификатор необязательно должен быть числом. Здесь все зависит от того, что бэкенд считает идентификатором и как идентификаторы создаются в базе данных. Например, в MongoDB идентификатор состоит из чисел и букв.
Давайте вернемся к примеру выше и обратим внимание на использование методов HTTP. У методов есть определенный смысл:
- GET нужен для извлечения данных
- POST — для создания и отправки форм
- PATCH — для обновления
- DELETE — для удаления
При этом URL часто остается одним и тем же.
В API важно использовать подходящие методы. Любые HTTP-запросы обрабатываются веб-серверами и промежуточными прокси, которые могут находиться на пути к веб-серверу. И веб-сервер, и прокси знают про особенности HTTP. В зависимости от параметров запроса они могут делать различные оптимизации и кешировать результат.
Кеширование — это такая техника, которая позволяет веб-серверу или прокси сохранить ответ от сервера и отдавать его при следующих запросах без обращения к самому серверу.
Кеширование ускоряет доступ к ресурсам и разгружает серверы. Запрос GET можно кешировать для ускорения доступа, потому что GET никогда не меняет данные.
Методы POST, PATCH и DELETE кешировать нельзя — они должны постоянно приходить на сервер, так как они вносят изменения.
Добавление пользователя
Попробуем добавить пользователя. Для этого по документации Dummy JSON нам нужно отправить POST-запрос на эндпоинт https://dummyjson.com/users/add .
Данные можно отправлять в разных форматах, HTML-формой или JSON-файлом. Чтобы использовать JSON, во время подготовки запроса нужно выполнить два шага:
- Указать заголовок Content-Type со значением application/json
- Преобразовать данные в JSON
В итоге запрос будет выглядеть так:
Есть несколько возможных вариантов ответа от сервера:
- Код 201 — ресурс успешно создан
- Код 422 — ошибка валидации
- Код 406 — некорректные данные или неверный формат
Подробнее можно прочитать в гайде по HTTP-кодам . Большая часть из них может встречаться с любым HTTP-методом.
Обновление пользователя
Для обновления пользователя мы должны отправить PATCH-запрос на эндпоинт https://dummyjson.com/users/ . Обновлять можно любой набор параметров, не обязательно сразу все:
Если все прошло успешно, то возможны два варианта ответа:
- Код 200 с какими-то данными — например, JSON с обновленными данными ресурса
- Код 204 — нет тела ответа
Удаление пользователя
Для удаления пользователя мы должны отправить DELETE-запрос на эндпоинт https://dummyjson.com/users/ . Тела запроса в таком случае нет, потому что все понятно из адреса запроса:
Если все прошло успешно, то возможны два варианта ответа:
- Код 200 и какие-то данные
- Код 204 и пустое тело ответа
Идемпотентность
Когда мы работаем с API, очень важна идемпотентность запросов. Это свойство указывает, насколько безопасно выполнять HTTP-вызов повторно. Идемпотентный запрос приводит к одному и тому же результату независимо от количества сделанных вызовов.
Для примера возьмем эндпоинт /users. Он возвращает список пользователей и ничего не меняет на сервере. Каждый новый вызов этого эндпоинта возвращает идентичный список пользователей — значит, это идемпотентный запрос.
Список пользователей может поменяться, если его меняют где-то в другом месте. Но даже это не поменяет идемпотентность GET-запроса на /users, ведь он сам ничего не меняет.
А вот POST-запрос — точно не идемпотентный. Каждый вызов /users/add имеет два исхода:
- Добавляет нового пользователя, хотя и с теми же самыми данными
- Возвращает ошибку, если на сервере добавлена проверка на уникальность каких-то данных
Именно поэтому при отправке форм после обновления страницы браузер всегда спрашивает, точно ли мы хотим выполнить этот запрос повторно.
По стандарту PATCH тоже не идемпотентный, хотя на практике чаще его делают идемпотентным. Но на это уже не могут рассчитывать браузеры или веб-серверы, потому что они не знают про специфику конкретных приложений.
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Что такое REST, RESTFul и CRUD
REST — это концепция для взаимодействия компонентов основанный на протоколе HTTP. Не хочу останавливаться на скучной теории (вики в помощь), а дам простое объяснение применительно к сайтам.
В Сети обмен происходит по протоколу HTTP: запрос — ответ. Для того, чтобы передать какие-то данные, их нужно как-то идентифицировать, то есть указать их «признак», «действие» или что-то подобное. И поэтому раньше данные требовалось оформить в виде какого-то сложного формата, обычно это XML или Json.
Так вот, REST говорит что этого делать не нужно. Данные передаём как есть, только у HTTP-запроса указывается метод (параметр) для этих данных. Обычно мы оперируем GET и POST, поскольку они работают «из коробки» в HTML и их поддерживает любой браузер. Но на самом деле, метод у HTTP может быть абсолютно любым. Есть некие общепринятые: PUT, DELETE, OPTIONS, PATCH, но это совсем не стандарт.
То есть, например мы хотим отправить на сервер GET-запрос по адресу сайт/task и получить список задач (таски). Потом мы хотим добавить новую задачу: отправляем на тот же адрес сайт/task, но уже методом POST. Потом решили, что для редактирования задачи нужно отправить уже PUT и опять же на тот же адрес. И, уж коли пошла такая пьянка, то отправляем и метод DELETE для удаления какой-то задачи.
Формирование GET-запроса очень простое — достаточно набрать адрес в браузере. Для POST уже нужна html-форма с method=post. Эти же методы прекрасно работают и через AJAX. Но, что касается PUT и DELETE, то ни HTML, ни браузер о них ничего не знают. Формально мы всё-таки можем их указывать, но наткнёмся на последнюю стенку — сервер, который также как правило ничего не знает об этих методах и рубанётся с ошибкой 501 или 405.
Таким образом, хоть HTTP и поддерживает произвольные методы, но в реальности работают только два: GET и POST.
Чтобы обойти это ограничение и применить REST к нашим сайтам, придумали хитрость. Все «нестандартные» запросы в своём теле должны содержать отдельное поле, где и указывается «желаемый» метод.
Устоявшаяся практика — метод указывать в поле _method формы или Ajax-запроса.
То есть реальная отправка это POST, но приложение проверяет поле _method и по нему уже принимает решение что делать дальше.
Такой разбор выполняет роутер приложения. Если это так, то правила роутинга указываются примерно так:
[ 'method' => 'POST', // create new task (Create) 'pattern' => 'task', 'action' => '\Controller\Task\Task@create', ], [ 'method' => 'GET', // task show (Read) 'pattern' => 'task', 'action' => '\Controller\Task\Task@read', ], [ 'method' => 'PUT', // update/edit task (Update) 'pattern' => 'task', 'action' => '\Controller\Task\Task@update', ], [ 'method' => 'DELETE', // delete task (Delete) 'pattern' => 'task', 'action' => '\Controller\Task\Task@delete', ]
Обратите внимание, что во всех случаях используется один http-адрес: сайт/task — меняются только методы, которые указываются либо в html-форме, либо Ajax-запросе.
Здесь мы подходим к другой концепции — CRUD, которая не что иное, как аббревиатура от Create, Read, Update, Delete — Создать, Прочитать, Обновить, Удалить. Это не что иное, как основные операции для подавляющего большинства данных.
CRUD нужен для того, чтобы разделить программный код на отдельные части, каждая из которых отвечает за свои действия. В данном примере будет использован php-класс \Controller\Task\Task , где каждый http-метод будет выполнен своим php-методом:
- для POST будет выполнен метод create()
- для GET будет выполнен метод read()
- для PUT будет выполнен метод update()
- для DELETE будет выполнен метод delete()
- сайт/task/create
- сайт/task/update/7
- сайт/task/delete/5
- сайт/task?action=create
- сайт/task?action=update&id=7
- сайт/task?action=delete&id=5
$method = 'get'; // метод по умолчанию if ($_POST) < if (isset($_POST['_method'])) $method = $_POST['_method']; else $method = 'post'; >.
Дальше роутер смотрит правила и находит php-метод для запуска. REST — это только концепция, набор правил. Если же применять эти правила к приложению, то уже говорят о RESTFul. Например REST предполагает использование модели «клиент-сервер». Если мы предположим, что сервер — это реальный сервер хостинга, а клиент — пользователь сайта, то для RESTFul нужно уже настраивать реальный сервер, чтобы он понимал используемые http-методы. Вряд ли кто-то будет это делать, поэтому с этой точки зрения приложение не может считаться RESTFul. Но, с другой стороны, если REST-сервер — это просто абстракция, то какая нам разница как вообще работает реальный сервер, если его основная задача — просто отдавать и принимать что нам нужно? Даже если мы используем хитрость с _method , это работает как положено, а значит приложение соответствует RESTFul. В целом стоит отметить, что сейчас REST и RESTFul — фактически синонимы. Если php-приложение может принимать хоть каким-то способом http-метод, то считается, что это уже полноценный RESTFul. Нужно просто понимать, что архитектура REST позволяет нам использовать HTTP примерно так, как он изначально и задумывался.
CRUD (создание, чтение, обновление, удаление)
API администрирования IIS предоставляет прямой доступ к ресурсам в системе. Многие из этих ресурсов позволяют выполнять операции создания, чтения, обновления и удаления. REST API сопоставляет операции CRUD с методами HTTP. В следующей таблице указывается, какой метод HTTP сопоставляется с какой операцией.
| Операция CRUD | Метод HTTP |
|---|---|
| Создание | POST |
| Чтение | GET |
| Update | PATCH / PUT |
| DELETE | DELETE |
Создание (POST)
Ресурсы создаются путем отправки HTTP-запросов POST в API. Тип ресурса определяется URL-адресом запроса. Текст запроса должен содержать объект JSON, описывающий создаваемый ресурс. Объект в тексте запроса определяет начальное состояние ресурса при его создании. Некоторые ресурсы требуют предоставления определенных свойств при их создании, другие можно создать с пустым объектом JSON.
Создание ресурса при задании свойства name. POST
Создание ресурса, принадлежащее другому
Иногда создаются ресурсы, которые должны принадлежать другому ресурсу. Например, если приложения должны принадлежать веб-сайту и кто-то хочет создать новое приложение , то они будут указывать это отношение во время создания приложения, как показано ниже.
Создание приложения для веб-сайта. POST
Чтение (GET)
Ресурсы извлекаются путем выполнения HTTP-запросов GET. Существует два основных метода извлечения ресурсов. Первый метод включает запрос списка ресурсов, второй метод заключается в запросе одного ресурса. Запросы к одному ресурсу помечаются наличием идентификатора ресурса в URL-адресе запроса. Иногда в URL-адресе можно также указать ресурсы с помощью синтаксического анализа строки запроса. Это поведение зависит от отдельной конечной точки API.
Получение нескольких ресурсов
Чтение списков ресурсов выполняется путем запроса конечной точки ресурса без указания идентификатора отдельных ресурсов. Иногда ресурсам требуются параметры строки запроса или они не могут создавать допустимые списки. Например, приложения IIS живут в конечной точке /api, webserver/webapps , но запрос на использование этой конечной точки не приведет к получению сведений. Это связано с тем, что веб-сайт должен быть указан для указания API, какие приложения должны отображаться. Поэтому потребители будут запрашивать /api/webserver/webapps?website.id= для просмотра списка приложений.
Получение списка ресурсов. GET/api/websites
< "websites": [ < "name": "Default Web Site", "id": "", "status": "started", "_links": < "self": < "href": "/api/webserver/websites/" > > >, < "name": "My Site", "id": "", "status": "started", "_links": < "self": < "href": "/api/webserver/websites/" > > > < "name": "docs", "id": "", "status": "started", "_links": < "self": < "href": "/api/webserver/websites/" > > > ] >
Извлечение отдельных ресурсов
Ресурсы извлекаются отдельно, предоставляя идентификатор ресурса в URL-адресе конечной точки ресурса. Некоторые конечные точки API также позволяют указывать отдельные ресурсы, предоставляя уникальные параметры строки запроса. Например, можно получить файл, указав идентификатор файла в URL-адресе или указав physical_path файла.
Ресурс файла позволяет нескольким методам извлекать отдельные файлы:
- /api/files/
- /api/files?physical_path=
Конечная точка файлов обеспечивает такое поведение, так как для любого физического пути может существовать только один файл, поэтому он является уникальным идентифицируя параметр строки запроса.
Обновление (PATCH/ PUT)
Обновления выполняются путем выдачи HTTP-запросов PATCH на URL-адрес, по которому находится ресурс. При выполнении запроса PATCH свойства текста запроса считываются, а если ресурс имеет свойство с тем же именем, что и свойство ресурса, будет присвоено новое значение.
Пример ресурса до PATCH
Выполнение запроса PATCH
Запрос на исправление для обновления имени ресурса. PATCH/api/webserver/websites/12345
Ресурс после PATCH
Удаление (DELETE)
Ресурсы удаляются путем отправки HTTP-запроса DELETE на URL-адрес, по которому находится ресурс. Это URL-адрес, содержащий идентификатор ресурса.
Обратная связь
Были ли сведения на этой странице полезными?
Обратная связь
Отправить и просмотреть отзыв по