{"id":21748419,"url":"https://github.com/surfstudio/api-design-best-practices","last_synced_at":"2025-04-13T07:13:38.601Z","repository":{"id":47479236,"uuid":"381582310","full_name":"surfstudio/api-design-best-practices","owner":"surfstudio","description":null,"archived":false,"fork":false,"pushed_at":"2021-08-30T07:37:19.000Z","size":1408,"stargazers_count":8,"open_issues_count":0,"forks_count":5,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-13T07:13:31.666Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/surfstudio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-06-30T05:10:28.000Z","updated_at":"2023-12-05T12:02:32.000Z","dependencies_parsed_at":"2022-08-20T00:40:56.995Z","dependency_job_id":null,"html_url":"https://github.com/surfstudio/api-design-best-practices","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/surfstudio%2Fapi-design-best-practices","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/surfstudio%2Fapi-design-best-practices/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/surfstudio%2Fapi-design-best-practices/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/surfstudio%2Fapi-design-best-practices/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/surfstudio","download_url":"https://codeload.github.com/surfstudio/api-design-best-practices/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248675422,"owners_count":21143768,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-26T08:13:26.354Z","updated_at":"2025-04-13T07:13:38.564Z","avatar_url":"https://github.com/surfstudio.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Лучшии практики ведения API спецификации (OpenAPI) в Surf\nВ этом репозитории находится пример спецификации (папка example) и описаны правила работы с ней, а также распространенные кейсы (файл cases). \n\n# Содержание\n- [Лучшии практики ведения API спецификации (OpenAPI) в Surf](#лучшии-практики-ведения-api-спецификации-openapi-в-surf)\n- [Содержание](#содержание)\n- [Общие принципы](#общие-принципы)\n- [Организация файлов и папок](#организация-файлов-и-папок)\n- [Проектирование спецификации API](#проектирование-спецификации-api)\n- [Инструменты](#инструменты)\n- [Генерация папок для фичи](#генерация-папок-для-фичи)\n- [Базовая структура](#базовая-структура)\n- [Ресурсы и методы](#ресурсы-и-методы)\n  - [Правила именования пути](#правила-именования-пути)\n  - [Методы](#методы)\n  - [Параметры пути и запроса](#параметры-пути-и-запроса)\n  - [Параметры пути](#параметры-пути)\n  - [Параметры запроса](#параметры-запроса)\n  - [Тело запроса](#тело-запроса)\n  - [Ответ](#ответ)\n- [Структура и проектирование моделей](#структура-и-проектирование-моделей)\n  - [Типы параметров](#типы-параметров)\n  - [Ошибки, хэширование, кэширование и тд](#ошибки-хэширование-кэширование-и-тд)\n- [Быстрые и полезные комбинации](#быстрые-и-полезные-комбинации)\n- [Проверка спецификации API на фичу](#проверка-спецификации-api-на-фичу)\n- [Проверка всей спецификации API через линтер SurfGen](#проверка-всей-спецификации-api-через-линтер-surfgen)\n- [Дополнительная информация для ознакомления и советы](#дополнительная-информация-для-ознакомления-и-советы)\n  - [Хэдеры](#хэдеры)\n  - [Советы](#советы)\n\n---\n# Общие принципы\n\n- Спецификация должна быть побита на разные файлы. Это **необходимо** для того, чтобы ее можно было легко и приятно поддерживать (дополнять, изменять) и вообще читать. \n- Корневой README.md должен содержать информацию о том, как работать с источниками в репозитории. Потому что кто-то может быть не в курсе. \n- Хорошо иметь скрипт для автогенерации шаблона. Чтоб не копипастить каждый раз самим. \n- Старайтесь использовать текстовый формат (не используйте GUI), потому что так проще навигировать. И так можно пользоваться поиском. \n- Относитесь к спецификации как к программному коду. Иначе в конечном итоге она превратится в мусор. \n\n# Организация файлов и папок\n\nХорошо придерживаться следующего паттерна:\n```\n- root_folder \u003c- корень вашего репозитория\n  -- README.md\n  -- feature_or_api_controller \u003c- папка, имеющая название фичи\n    --- api.yaml        \u003c- Файл исключительно для описание сервисов (path в Swagger)\n    --- models.yaml     \u003c- Файл исключительно для описания моделей (components/schemas в Swagger)\n    --- parameters.yaml \u003c- Файл исключительно для описания параметров api-методов (components/parameters в Swagger)\n    --- errors.yaml     \u003c- Файл исключительно для описания ошибок которые могут вернуть методы (components/schemas в Swagger)  \n```\n\n**ВАЖНО**\n\n- `api.yaml` и `models.yaml` обязательны! Потому что в противном случае люди будут бояться лезть менять ваши файлы. \n- `parameters.yaml` и `errors.yaml` не обязательны! Они могут быть использованы в тех случаях, когда сервисы содержат очень много повторяющихся параметров и/или ошибок. \n- Не стоит выносить отдельно components/requestBodies , потому что тогда, чтобы добраться от метода до модели, потребуется 2 перехода. Это уже неудобно. \n\n# Проектирование спецификации API\nПеред проектированием спецификации API подразумевается, что вы: \n- Клонировали репозиторий и наполнили его файлами для генерации и readme.\n- Создали ветку фичи, которую будете проектировать.\n- Установили необходимые расширения в VSCode.\n\n# Инструменты\n- [VSCode](https://code.visualstudio.com/download  ) скачать и установить, если еще не установлен.\n- Расширения для VSCode - для установки переходим в раздел Расширения в левом боковом меню и ищем по названию необходимое расширение:\n    - [Swagger Viewer](https://marketplace.visualstudio.com/items?itemName=Arjun.swagger-viewer) - Предварительный просмотр спецификации API, происходит в режиме реального времени по мере ввода.\n    - [GitHub Pull Requests and Issues](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github) (опционально) - можно смотреть и комментировать PR-ы прямо из VSCode. Необходимо авторизоваться через VSCode в GitHub, появится иконка GitHub-а в боковом меню, где можно просматривать и комментировать PR-ы. \n    - [Git Blame](https://marketplace.visualstudio.com/items?itemName=waderyan.gitblame) (опционально). Расширение Git Blame предоставляет возможность просматривать информацию состояния для текущей выбранной строки. Оно позволяет выяснить, кто писал определенные фрагменты кода.\n    - [Git History](https://marketplace.visualstudio.com/items?itemName=donjayamanne.githistory) (опционально). Расширение Git History предоставляет возможность тщательно изучить историю файла, автора, ветки. Чтобы активировать окно Git History, нужно кликнуть на файл правой кнопкой мыши и выбрать Git: View File History. Кроме того, вы можете сравнивать ветки и коммиты, создавать ветки из коммитов и многое другое.\n\n# Генерация папок для фичи\nХорошо иметь какой-то инструмент, который позволит сгенерить нужные файлы с предзаполненной мета информацией - чтоб люди не писали по 100 раз одно и то же. \n\nВ этом репозитории это сделано с помощью шелл-скрипта [gen.sh](./gen.sh) и двух файлов с шаблонами [.api_template](./.api_template) и [.models_template](./.models_template)\n\nСам скрипт позволяет сгенерировать \"контроллер\" с отдельным файлом для моделек и отдельным файлом для методов API. \nТ.к. мы проектируем уже фичу, то все махинации должны производиться в соответствующей ветке.\n\nПошаговая инструкция генерации папки для фичи: \n\n| №       | Шаг                |\n| ------------- |:------------------|\n| 1     | Проверяем, что мы в правильной ветке. Если работаем над авторизацией, то в ветке auth, которую мы создали.    |\n| 2     | Открываем консоль, перемещаемся в корень репозитория: \u003cbr/\u003e 1. Идем в папку где располагается репозиторий (к примеру, `E:\\work\\my-project-swagger`). \u003cbr/\u003e 2. Shift+клик по правой кнопка мыши → “Открыть окно PowerShell здесь”, на macOS соответственно терминал открываем или же через терминал VSCode.    |\n| 3  | Вводим в консоль `./gen.sh ./{название фичи (оно же название папки)}`. Пример: `./gen.sh ./auth` или `sh gen.sh catalog`         |\n| 4  | Скрипт создаст папку `catalog` и добавит в нее два файла `api.yaml` и `models.yaml` с уже заполненной шапкой. Если папка уже есть, то скрипт просто положит в нее два файла. \u003cbr/\u003e Всё, папки появились. Если нет, жмем кнопку обновления проводника (Explorer) в VSCode. Созданы два файла в папке `auth` с нужными шапками/шаблонами: api.yaml - для описания эндпоинтов, models.yaml - для описания моделей к ним|\n| 5     | Можем начинать проектирование фичи, для которой создали папку.    |\n\n# Базовая структура\nСтруктура файла `api.yaml` или что нагенерил мне скрипт:\n\n\u003e Все поля, кроме `path`, заполняются при генерации папок. Но понимать, что они означают будет полезно.\n\n\u003ctable border=\"1\" width=\"100%\" cellpadding=\"5\"\u003e\n  \u003ctr\u003e\n    \u003cth\u003eНазвание поля\u003c/th\u003e\n    \u003cth\u003eОписание\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003eopenapi\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eОбязательное для заполнения. \u003cbr/\u003e Номер версии спецификации OpenAPI. Последняя версия на данный момент 3.0.2. \u003cbr/\u003e Это поле не связано со строкой API - \u003ccode\u003einfo.version\u003c/code\u003e.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003einfo\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003e Обязательное для заполнения. \u003cbr/\u003e Содержит основную информацию о вашем API: название (\u003ccode\u003etitle\u003c/code\u003e), описание вашего API (\u003ccode\u003edescription\u003c/code\u003e), версия вашего API (\u003ccode\u003eversion\u003c/code\u003e), контакты разработчика спецификации (\u003ccode\u003econtact.name\u003c/code\u003e, \u003ccode\u003econtact.email\u003c/code\u003e). В данной инструкции указан не весь набор элементов, которые могут содержаться в info-блоке, но для нашего использования этого достаточно. \u003cbr/\u003e Пример: \u003cbr/\u003e \n\n  ```yaml\n    info:\n    title: \"API\"\n    version: \"1.0.0\"\n    contact:\n      name: Юльская Виктория\n      email: yulskaya@surfstudio.ru\n  ```\n  \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003eservers\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eСодержит информацию об используемых серверах: url и описание сервера (description). Их может быть несколько. \u003cbr/\u003e Пример: \u003cbr/\u003e\n  \n  ```yaml\n    servers:\n      - url: https://dev3.myproject.ru/api/surf/v1/\n        description: Test server dev3\n      - url: https://surf.myproject.ru/api/surf/v1/\n        description: Test server surf\n      - url: https://myproject.ru/api/surf/v1/\n        description: Production Server\n  ```  \n  \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003ecomponents\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eВ объекте components можно хранить множество различных переиспользуемых объектов. Объект components может содержать следующее: \u003cli\u003e схемы \u003cli\u003e ответы \u003cli\u003e параметры \u003cli\u003e примеры \u003cli\u003e тело запроса \u003cli\u003e хэдеры \u003cli\u003e схемы безопасности \u003cli\u003e и тд \u003cbr/\u003e \n    \u003cblockquote\u003e При делении папки на два файла api.yaml и models.yaml нам данный блок нужен только для определения схемы безопасности, все остальное уходит в файл models.yaml. \u003cbr/\u003e Если спецификация заказчика вся ведется в одном файле, то все вышеперечисленное будет в данном блоке. \u003c/blockquote\u003e \u003cbr/\u003e \u003cblockquote\u003e Лучше сразу понять как будет проходить авторизация и дальнейшая проверка наличия прав пользователя при запросе на ресурс - через токены / куки / что-то другое (разработчикам надо закладывать это в самом начале, во время первого спринта, а то и во время инициализации). \u003c/blockquote\u003e \u003cbr/\u003e Пример: \u003cbr/\u003e\n\n  ```yaml\n    components:\n      securitySchemes:\n        bearerAuth:\n          type: http\n          scheme: bearer\n          bearerFormat: JWT \n  ```   \n  \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003esecurity\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eДля отправки запросов, авторизованных нашим сервером API, спецификация должна содержать информацию о безопасности, которая авторизует запрос. \u003cbr/\u003e\n\n  ```yaml\n    security:\n      - bearerAuth: []\n  ```  \n  \u003cblockquote\u003e Объявленные поля components.securitySchemes и security свидетельствуют о том, что у любого метода в этом файле должен быть установлен хедер Authorization с JWT токеном. \u003cbr/\u003e При этом у каждого метода можно определить секцию security:[] , оставив ее пустой, которая будет свидетельствовать о том, что для данного метода авторизация не нужна. \u003c/blockquote\u003e  \n  \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003epaths\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eОбязательное для заполнения. \u003cbr/\u003e Содержит доступные пути (конечные точки) и операции (методы) для API. \u003cbr/\u003e Подробнее о заполнении данного блока рассмотрим чуть ниже.\u003c/td\u003e\n  \u003c/tr\u003e\n \u003c/table\u003e\n\n# Ресурсы и методы\nБлок path состоит из: \n- пути (конечной точки) - все пути в блоке `path` задаются относительно URL, определенных в блоке \"Серверы”, то есть полный URL запроса будет выглядеть так `\u003cserver-url\u003e/path`. \n- операций (методов `GET`, `POST` и тд), который в свою очередь включает: \n    -  `summary` - название метода.\n    -  `description` - описание работы метода. Описывайте там задачу которую решает метод или свойство.\n    -  `security: []` -  указывается если для запроса НЕ нужна авторизация.\n    -  `parameters` - параметры запроса\n    -  `requestBody` - тело запроса\n    -  `responses` - описание ответа\n\nЕсть и другие элементы, которыми нам особо не нужно пользоваться пока что.\n\u003cbr/\u003e![Состав метода](/src/images/изображение-20210621-133155.png)\n\n## Правила именования пути\nЕсть 3 типа ресурсов: \n- Документ - один объект. К примеру, одно сообщение в списке (`api/messages/{id}` - документ обычно вложен в коллекцию, но есть исключения).\n    -  в пути используются в таком случае только существительные.\n    -  последнее существительное в единственном числе.\n- Коллекция - множество объектов. К примеру, список сообщений (`api/messages`).\n    - в пути используются в таком случае только существительные.\n    - последнее существительное во множественном числе.\n- Контроллер - действие. К примеру, оформление заказа (`api/cart/checkout`).\n    -  можно использовать глаголы.\n    -  последнее слово всегда глагол.\n    -  действие всегда должно относится к чему то (`api/cart/checkout` - checkout относится к корзине, не может быть просто `api/checkout`).\n\n\u003e Стараемся делать как можно больше документов и коллекций, и как можно меньше контроллеров\n\n\u003e В названии пути НЕ ПИШЕМ ДЕЙСТВИЕ, о котором говорит HTTP method!!! (create, update, delete и тд)\n\u003e - \u003cspan style=\"color:green\"\u003e POST /courses - Создать новый курс \u003c/span\u003e\n\u003e - \u003cspan style=\"color:red\"\u003e POST /courses/create - Создать новый курс \u003c/span\u003e\n\n## Методы\n| Метод       | Описание               | Комментарий|\n| ------------- |:------------------|:------------------|\n|`GET` |Возвращает представление ресурса по указанному универсальному коду ресурса (URI). Текст ответного сообщения содержит сведения о запрашиваемом ресурсе. |\u003cli\u003eНЕ СОДЕРЖИТ ТЕЛА \u003cli\u003eПрокси могут просто отбрасывать тело GET запроса. \u003cli\u003e OS может самостоятельно повторить GET запрос. \u003cli\u003e GET запросы по умолчанию кешируются через URI. Если параметры передаются в теле, то кеши работать не будут.|\n|`POST` |Создает новый ресурс по указанному URI. Текст запроса содержит сведения о новом ресурсе. \u003cbr/\u003e Метод POST также можно использовать для запуска операций, не относящихся непосредственно к созданию ресурсов (для операции контроллера). | |\n|`PUT` |Создает или заменяет ресурсы по указанному URI. \u003cbr/\u003e В тексте сообщения запроса указан создаваемый или обновляемый ресурс. \u003cbr/\u003e \u003cspan style=\"color:green\"\u003e Лучше все таки разделять создание (делать POST) и изменение (PUT / PATCH) \u003c/span\u003e|\u003cli\u003eПолностью перезаписывает ресурс \u003cbr/\u003e ![image](/src/images/изображение-20210621-115407.png)|\n|`PATCH` |Выполняет частичное обновление ресурса. Текст запроса определяет набор изменений, применяемых к ресурсу |\u003cli\u003eПерезаписывает только определенную часть \u003cbr/\u003e ![image](/src/images/изображение-20210621-115433.png)|\n|`DELETE` |Удаляет ресурс по указанному URI|\u003cli\u003eНЕ СОДЕРЖИТ ТЕЛА |\n|| …есть еще множество методов, нас они не интересуют| |\n\n![Пример построения пути](/src/images/изображение-20210621-115651.png)\n\n## Параметры пути и запроса\nПараметры пути и запрос состоят из:\n- `name`: имя параметра.\n- `in`: место параметра. Возможные значения: \n    - `header` - параметры, включенные в заголовок запроса, обычно относятся к авторизации.\n    - `path` - параметры в пределах path конечной точки перед строкой запроса. Обычно эти параметры выделяются фигурными скобками.\n    - `query` - параметры в строке запроса конечной точки, располагаются после знака ?.\n    - `cookie` - параметры в заголовке Cookie. \n- `description`: описание параметра.\n- `required`: требуется ли параметр.\n- `schema`: схема или модель для параметра. Схема определяет структуру входных или выходных данных. \n- `example`: пример типа носителя. Если объект example содержит примеры, эти примеры появляются в Swagger UI, а не в содержимом объекта example.\n\nТакже, параметры запроса можно выносить в `models.yaml` и ссылаться (`$ref`) на параметры из моделей. Пример:\n\n```yaml\nparameters:\n  - $ref: \"models.yaml#/components/parameters/Param1\"\n```\n\nДля добавления в компоненты параметров необходимо на уровне с элементом `schema` добавить элемент parameters и описать там все необходимые параметры. \n\nДля ограничения возможных значений параметра запроса необходимо использовать ключевое слово `enum`.\n\n```yaml\n# Описание параметра запроса в models.yaml для того, чтобы ссылаться на него и переиспользовать без дублирующего описания.\nparameters:\n    filter_type: \n      name: filter_type \n      in: query\n      description: | \n        Тип фильтра заказов пользователя: \n        - all - все заказы\n        - current - текущие\n        - done - выполненные\n      schema:\n        enum: [\"all\", \"current\", \"done\"] \n        type: string\n```\n\n## Параметры пути\nДля того чтобы добавить параметр в путь запроса необходимо использовать фигурный скобки `{}`. Обычно это используется для указания определенного элемента в коллекции. Путь может иметь несколько параметров:\n\n```yaml\nGET /users/{id}:\nGET /cars/{carId}/drivers/{driverId}:\n```\n\n\nКаждый параметр пути должен быть заменен фактическим значением при вызове. \n\nДля определения параметров пути нужно использовать следующую конструкцию `in: path`. Необходимо также добавить `required: true`, чтобы указать обязательность данного параметра. \n\n```yaml\npaths:\n  /users/{id}:\n    get:\n      parameters:\n        - name: id   # имя можно использовать такое же как и в пути\n          in: path\n          description: Идентификатор пользователя\n          required: true # обязательный параметр\n          schema:\n            type: integer\n            minimum: 1\n```\n\n## Параметры запроса\nПараметры запроса отображаются в конце URL-адреса после знака вопроса (`?`). Несколько значений должны разделяться амперсандом (`\u0026`). \n\n```\nGET /pets/findByStatus?status=available\nGET /notes?offset=100\u0026limit=50\n```\n\nДля определения таких параметров нужно использовать следующую конструкцию in: query.\n\n```yaml\npaths:\n  /notes:\n    get:\n      parameters:\n        - name: offset\n          in: query\n          description: The number of items to skip before starting to collect the result set\n          schema:\n            type: integer\n        - name: limit\n          in: query\n          description: The numbers of items to return\n          schema:\n            type: integer\n```\n\n\u003e\u003cspan style=\"color:red\"\u003eНе декларируйте здесь объекты!!!\u003c/span\u003e\n\u003cdetails\u003e\n    \u003csummary\u003eПримеры оформления параметров запроса\u003c/summary\u003e\n\n| Не надо так       | Надо вот так               | Так тоже можно|\n| :------------------ |:------------------|:------------------|\n|![nope](/src/images/изображение-20210621-135049.png)|![norm1](/src/images/изображение-20210621-135031.png)|Если все же необходим объект, то объявите его в моделях и ссылайтесь на данный объект \u003cbr/\u003e ![norm2](/src/images/изображение-20210621-135119.png) \u003cbr/\u003eМожно вынести отдельно параметры для дальнейшего переиспользования и ссылаться на объявленные параметры \u003cbr/\u003e ![norm3](/src/images/изображение-20210621-135244.png) |\n\n\u003c/details\u003e\n\nПро параметры заголовка и куки подробнее можно прочитать в [соответствующих разделах](https://swagger.io/docs/specification/describing-parameters/#path-parameters).\n\n## Тело запроса\nPOST, PUT и PATCH запросы могут иметь тело запроса. \n\n\u003e\u003cspan style=\"color:orange\"\u003e 1. Мы всегда должны ставить ссылки на модели!!! Не нужно засорять нашу спецификацию перечислением того, что должно быть в моделях. У нас и так огромные методы, а если еще модели писать, то будет много дублирования и иных проблем. Делаем ссылки для своего удобства и для удобства всей команды. \u003cbr/\u003e 2. Есть исключения в виде массивов или групп, в таком случае мы прописываем массив и как тип элементов, которые там лежат мы используем ссылку на модель элемента.\n\nПример:\n```yaml\nrequestBody: \n  required: true\n  content:\n    application/json: \n      schema:\n        $ref: \"models.yaml#/components/schemas/RecalculateOrderRequest\"\n```\n\u003cdetails\u003e\n    \u003csummary\u003eПримеры оформления тела запроса\u003c/summary\u003e\n\n| Плохо       | Хорошо    |\n| :------------------ |:------------------|\n|![image](/src/images/изображение-20210622-155150.png)|![image](/src/images/изображение-20210622-155049.png)|\n\n\u003c/details\u003e\n\n## Ответ\nОписание REST-запроса обязательно должно содержать описание ответа (`responses`). У каждого метода должен быть определен хотя бы один ответ (успешный). Response задается HTTP-кодом ответа и данными, которые возвращаются в теле ответа и / или заголовке. \n\u003e\u003cspan style=\"color:orange\"\u003e 1. Мы всегда должны ставить ссылки на модели!!! Не нужно засорять нашу спецификацию перечислением того, что должно быть в моделях. У нас и так огромные методы, а если еще модели писать, то будет много дублирования и иных проблем. Делаем ссылки для своего удобства и для удобства всей команды. \u003cbr/\u003e 2. Есть исключения в виде массивов или групп, в таком случае мы прописываем массив и как тип элементов, которые там лежат мы используем ссылку на модель элемента.\n\nОписание ответа начинается с кода, такого как `200` или `404`. Методы обычно возвращают один успешный код и один и более кодов ошибок. Каждый код требуется описания (`description`) - условие, при которых код срабатывает. Если вы не можете задать определенный код, то его можно задать следующим видом: `1XX`, `2XX`, `3XX`, `4XX`, `5XX`. Но таким образом, в случае если был задан код `404` и `4XX`, приоритет у первого будет выше. \n\n```yaml\nresponses:\n        '201':\n          description: Бонусы успешно списаны.\n        '500':\n          description: |\n            Возможные ошибки\n            * `101` - UserBlocked, пользователь был заблокирован\n            * `104` - OTPCodeInvalid, неверный OTP-код\n          content:\n            application/json:\n              schema:\n                $ref: \"../common/models.yaml#/components/schemas/ErrorResponse\"\n        '426':\n          description: Необходимо обновить приложение\n```\n\n\u003eНе стоит описывать все возможные коды ответов, тем более что о некоторых из них можно и не знать в момент проектирования запроса. Важно при описании кодов покрыть случай успешного выполнения запроса и коды ошибок, известных на момент написания REST-запроса. \u003cbr/\u003e Также, в Surf обычно описываются кастомные ошибки и заворачиваются в `400` или `500` статус код. \u003cbr/\u003e Кастомные ошибки описываются в отдельной папке common - где в `api.yaml` описывается справочник ошибок, а в `models.yaml` описывается модель ошибки, к примеру, `ErrorResponse`, который состоит из кода кастомной ошибки, сообщения в человекочитаемом виде, и при необходимости вспомогательная информация об ошибке\n\nДля передачи файлов в запросе или ответе в OpenAPI 3.0 используется type: string и format: binary или format: base64.\n\n```yaml\npaths:\n  /report:\n    get:\n      summary: Returns the report in the PDF format\n      responses:\n        '200':\n          description: A PDF file\n          content:\n            application/pdf:\n              schema:\n                type: string\n                format: binary\n```\n\n\u003cdetails\u003e\n    \u003csummary\u003eПримеры оформления ответа\u003c/summary\u003e\n\n| Плохо       | Хорошо    |\n| ------------- |:------------------|\n|![image](/src/images/изображение-20210622-155138.png) \u003cbr/\u003e ![image](/src/images/изображение-20210622-155150.png)| ![image](/src/images/изображение-20210622-160105.png)|\n\n\u003c/details\u003e\n\n**Статус коды**\n| Код       | Описание    | Часто используемые коды |\n| ------------- |:------------------|:------------------|\n|2xx |Операция завершилась успешно| \u003cli\u003e `200` - Все ок! \u003cbr/\u003e Всегда содержит тело ответа. Может использоваться в GET запросах. \u003cli\u003e `201` - Запись создана. \u003cbr/\u003e Используется в методах POST и имеет тело ответа, чтобы сказать клиенту, что мы создали в итоге - как минимум получить идентификатор записи). \u003cli\u003e `202` - Принято. \u003cbr/\u003e Не содержит тело ответа. \u003cbr/\u003e Говорит о том, что клиенту не обязательно ждать завершения операции (но она еще не завершилась). Пример, оплата. \u003cli\u003e `204` - Операция прошла успешно, но ответ пустой. \u003cbr/\u003e Если знаем что мы не ждем ответа, то ставим всегда данный код.|\n|3xx |Редирект или можем пойти читать из кэша| \u003cli\u003e `304` - Данные не изменились. \u003cbr/\u003e Можно читать данные из кеша. Обычно работает с E-Tag или Cache-Control заголовками. \u003cbr/\u003e **Работает только с GET запросами**|\n|4xx |Операция завершилась с ошибкой по вине клиента|Из тех, что стоит фиксировать в спецификации: \u003cbr/\u003e \u003cli\u003e `401` - Пользователь не авторизован для доступа. \u003cli\u003e `403` - Пользователь не имеет права просматривать контент. \u003cli\u003e `426` - Указывает, что сервер отказывается выполнять запрос с использованием текущего протокола, но может захотеть сделать это после того, как клиент обновится до другого протокола (используется когда версия приложения уже не поддерживается и пользователю предлагается обновить приложение при получении данной ошибки). \u003cbr/\u003e Также, на `400` или `409` можно повесить кастомные ошибки и описать их в справочнике|\n|5xx |Операция завершилась с ошибкой по вине сервера (или не смог сразу определить что по вине клиента)|Конкретные 5xx ошибки не фиксируем обычно в спецификации API, но если необходима необычная обработка, то фиксируйте (к примеру определенная заглушка на ошибку временной неработоспособности сервера - `503`)|\n\n# Структура и проектирование моделей\nФайл models.yaml состоит из \n- `components`, который в свою очередь включает:\n  - `schemas` - модели\n  - `parameters` - параметры\n\nПример структуры файла:\n```yaml\n    components:\n    schemas:\n      UpdatedOrderResponse:\n        type: object\n        description: Модель для обновленных полей заказа после выполнения действия над ним.\n        properties:\n          status:\n            $ref: \"#/components/schemas/ExtendedOrderStatus\"\n          actions:\n            type: array\n            description: |\n              Список действий, доступных над заказом.\n              Список пуст, если нет доступных действий.\n            items:\n              $ref: \"#/components/schemas/OrderAction\"\n        required:\n          - status\n          - actions\n\n      ReceiverType: # модель, которая содержит ограничения возможных значений по типу плательщика\n        type: string\n        enum: [individual, entity]\n        description: |\n          Тип плательщика:\n          - individual - физическое лицо\n          - entity - юридическое лицо\n    \n    parameters: # параметры, которые можно переиспользовать в параметрах запроса\n      filter_type: \n        name: filter_type \n        in: query\n        description: | \n          Тип фильтра заказов пользователя: \n          - all - все заказы\n          - current - текущие\n          - done - выполненные\n        schema:\n          enum: [\"all\", \"current\", \"done\"] \n          type: string\n```\n\n\u003eКомментарий (`description`) очень важная часть спецификации. Применимо как к методам, так и к моделям. \u003cbr/\u003e Уделяйте большое внимание этому полю и описывайте как можно более понятнее, вкладывайте контекст, логику, примеры - пишем как можно больше (в пределах разумного, конечно, описывать супер подробно `user.name` не стоит).\n\n## Типы параметров\nС помощь ключевого слова type задается тип данных. Типы могут быть следующими:\n- `string` - Строка текста. \n- `number` - включает в себя и целые числа, и числа с плавающей точкой.\n- `integer` - только целые числа. \n- `boolean` - в логическом типе boolean представлено два возможных значения: `true` и `false`.\n- `array` - массив. \n- `object` - объекты - коллекция пар элемент и значение. \n\n**Строка**\n- Длину строки можно ограничить, используя для этого minLength и maxLength. \n- Ключевое слово `pattern` позволяет определить шаблон регулярного выражения для строки - значения, которые могут быть использованы в строке. Для задания `pattern` используется синтаксис регулярного выражения из JavaScript (`pattern: '^\\d{3}-\\d{2}-\\d{4}$'`).  `\"^\"` используется для обозначения начала строки, `\"$\"` - конца строки. Без `^… $` шаблон соответствует любой строке, содержащей указанное регулярное выражение. \n- Ключевое слово `format` используется для того чтобы задать формат строки, например один из них: `date` (2017-07-21), `date-time` (2017-07-21T17:32:28Z), `password`, `byte`, `binary`\n\nК примеру, для передачи файла используется:\n```yaml\navatar:          # изображение, встроенное в JSON\n  description: Base64-encoded contents of the avatar \n  type: string\n  format: byte\n```\n**Числа**\n- Чтобы указать диапазон возможных значений, можно использовать ключевая слова `minimum` и `maximum` (minimum ≤value≤ maximum).\n- Чтобы исключить граничные значения, укажите `exclusiveMinimum: true` и `exclusiveMaximum: true`\n```yaml\ncount:          \n  description: Суммарное количество товаров в заказе \n  type: integer\n  example: 6\n  maximum: 25\n```\n\n**Массивы**\n- С помощью `minItems` и `maxItems` можно задавать минимальную и максимальную длину массива. Если не использовать `minItems`, то массив может быть пустым.\n- Элементы массива описываем отдельным элементом, если они представляют собой коллекцию.\n\n```yaml\n# Элементы массива отдельным элементом\nactions:          \n  description: |\n    Список действий, доступных над заказом. \n    Список пуст, если нет доступных действий. \n  type: array\n  items:\n    $ref: \"#/components/schemas/OrderAction\"\n\n# Массив строк\ncategories:\n  description: |\n    id категорий товаров первого уровня, в которые входят товары данной акции \n  type: array\n  items:\n    type: string\n```\n\n**Объекты**\n- По умолчанию все элементы коллекции необязательные. Можно указать список обязательных элементов с помощью слова `required` (можно отказаться от `required` и прийти к тому, чтобы явно прописывать параметрам `nullable: false`, если поле не может быть пустым).\n- Коллекция также может быть вложенной и включать в себя коллекцию. В таком случае коллекцию оформляем отдельным объектом и даем на него ссылку для удобства всех членов команды.\n![image](/src/images/изображение-20210622-161754.png)\n\n## Ошибки, хэширование, кэширование и тд\nДля описания данной информации следует создать папку common, где фиксировать общие договоренности, которые в самих методах никак не отразить. Также, можно выносить общие методы, к примеру, загрузка изображения (профиля, отзыва и тд).\n\u003cdetails\u003e\n    \u003csummary\u003eПример оформления файла common/api.yaml\u003c/summary\u003e\n\n```yaml\nopenapi: 3.0.2\ninfo:\n  title: \"API\"\n  version: \"1.0.0\"\n  contact:\n    name: Виктория Юльская\n    email: yulskaya@surfstudio.ru\n  description: |\n    # Headers\n    \u003cdetails\u003e\n    Для определения текущего пользователя, во всех запросах может как приходить, так и передаваться кастомный хедер `UserID`.\n    \u003c/details\u003e\n\n    # Авторизация \n    \u003cdetails\u003e\n\n    Схема авторизации онована на механизме access/refresh токенов.\n\n    При логине - сервер возвращает пару access/refresh токен. \n    В дальнейшей работе с сервером во всех запросах необходимо передавать в хедере `Authorization`\n    полученный access токен.\n    При его протухании - необходимо произвести обновление токенов с помощью соответствующего метода,\n    используя refresh токен.\n\n    При получении ошибки во время обновления токенов - необходимо закончить текущую сесию работы с сервером\n    и разлогинить пользователя.\n    \u003c/details\u003e\n\n    # Пагинация\n    \u003cdetails\u003e\n\n    Проект поддерживает страничную пагинацию.\n    Каждый пагинируемый запрос должен url-параметрами принимать `page` и `size`,\n    а в теле ответа возвращать данные с информацией о порции пагинируемых данных. \n    \n    - `page` отвечает за размер пачки пагинации, \n    - `size` отвечает за размер порции.\n    \u003c/details\u003e\n\n    # Ошибки \n    \u003cdetails\u003e\n    \u003csummary\u003eОписание\u003c/summary\u003e\n\n    Сервер возвращает ответ со статус кодом 400 и ошибкой в формате:\n\n        { \n          \"code\": 100,\n          \"errorMessage\": \"Пользователь не найден\",\n          \"data\": \"85\"\n        }\n\n    Ошибки состоят из\n    - специфического кода `code` для идентификации ошибки\n    - текста `errorMessage`, который будет отображаться на клиенте\n    (в силу этого вся обязанность за формирование текста ошибок ложиться на серверную часть,\n    что позволит обеспечить гибкость механики в целом)\n    - и опционального поля `data`, в котором может располагаться строка с какими-то данными, \n    которые клиенту следует отобразить \n    (Например, это может быть количество секунд, через которые можно повторно отправить смс)\n\n    \u003c/details\u003e\n    \n    \u003cdetails\u003e\n    \u003csummary\u003eСпецифические коды ошибок\u003c/summary\u003e\n\n    * `100` - BadJson, неверный формат JSON от клиента\n    * `103` - OTPCodeRequestTooOften, слишком частые запросы на генерацию OTP-кода, в **data** - количество секунд до разблокировки\n    * `104` - OTPCodeInvalid, неверный OTP-код\n    * `105` - SendingCodeFailed, не удалось послать OTP-код\n    * `106` - FileUploadingError - Ошибка при загрузке файла\n    * `107` - NotEnoughPoints - Недостаточно баллов для списания\n    * `108` - NotEnoughProducts - Товар закончился\n    * `120` - NameNotUnique - Товар с таким именем уже существует\n    * `119` - RefreshTokenFailed - Не удалось обновить токен\n    * `121` - OTPCodeExpired - срок жизни кода истек\n    * `122` - BonusesWriteoffFailed - Нельзя списать больше бонусов, чем есть у пользователя\nservers:\n  - url: https://someaddress.com\n    description: TODO заглушка, требуется поправить в будущем на реальный адрес. \n\ncomponents:\n  securitySchemes:\n    bearerAuth:\n      type: http\n      scheme: bearer\n      bearerFormat: JWT \n\nsecurity:\n  - bearerAuth: []\n\npaths:\n  /file:\n    post:\n      summary: Загрузка файла\n      description: Запрос на загрузку файла на сервер, используется, когда пользователь прикрепляет файлы\n      requestBody:\n        required: true\n        content: \n          multipart/form-data:\n            schema:\n              $ref: \"models.yaml#/components/schemas/BinaryFile\"\n      responses:\n        '200':\n          description: Успешный ответ с данными.\n          content:\n            application/json:\n              schema:\n                $ref: \"models.yaml#/components/schemas/FileURL\"\n        '400':\n          description: |\n            Возможные ошибки\n             * `106` - FileUploadingError - Ошибка при загрузке файла\n          content:\n            application/json:\n              schema:\n                $ref: \"../common/models.yaml#/components/schemas/ErrorResponse\"\n    \n  /file/{fileUrl}:\n    delete:\n      summary: Удаление файла\n      parameters:\n        - name: fileUrl\n          in: path\n          description: Ссылка на файл\n          required: true\n          schema:\n            type: string\n      responses:\n        '204':\n          description: Файл удален\n```\n\u003c/details\u003e\n\n# Быстрые и полезные комбинации\n1. Ctrl (или CMD) - Можем посмотреть что за ref у нас в методе / моделе. Наводим на ref и зажимаем комбинацию, получаем информацию о нашей ссылке. Если кликнуть по ней, то быстро перейдем по ссылке к нашей модели, на которую ссылаемся.\n2. Ctrl (или CMD) + Shift + O - Позволяет найти и перейти к конкретной вкладке, либо конкретной модели / свойству.\n3. Ctrl (или CMD) + Shift + P - Открывает панель команд.\n4. Ctrl (или CMD) + P - Поиск файлов по имени\n\n# Проверка спецификации API на фичу\nМинимальная проверка спецификации API на фичу может быть проведена путем визуализации спецификации API при помощи комбинации `Alt+Shift+P`. Комбинацию вызывать находясь в файле `api.yaml`.\n\nПроверить что спецификация визуализируется, все параметры отверстаны, прописаны обязательные и `nullable` поля. Запросы и ответы также отрендерены и не отображаются ошибки. \n\nЕсли не рендерится сваггер, на что обратить внимание: \n\nТабуляция. Проверьте, что все находится на своем уровне\n\nСсылки. Проверьте, что все ссылки корректные и они ссылаются на существующие модели.\n\n| Проблема с табуляцией       | Все ок    |\n| ------------- |:------------------|\n| ![image](/src/images/изображение-20210708-105725.png) | ![image](/src/images/изображение-20210708-105749.png) |\n\n# Проверка всей спецификации API через линтер SurfGen\nДля проверки должен быть подключен `SurfGen` и подключен линтинг через `GitHub Actions`.\n\nДля корректной работы ветки называть `feature_branchname`, к примеру, чтобы у всех веток (кроме мастера) был единый префикс.\n\nДалее при открытии PR-а, при пуше изменений и тд будет запускать проверка спеки на соответствие всего вышеописанного.\n- Если проставилась зеленая галка, то все с вашей спекой ок.\n- Если видим красный индикатор, то есть проблемы и их нужно исправить.\n\nИдем на вкладку Checks и читаем логи (build). Начинаем читать все, начиная с третьей строки. Что мы видим?\n- Линтер пошел парсить схему `/common/models.yaml`\n- Зашел в объект `ErrorResponse`\n- В параметре `data` нашел ошибку- Выдает нам описание ошибки.\n\nЧтобы исправить ошибку мы идем в папку common -\u003e файл models.yaml -\u003e находим модель `ErrorResponse`, а в ней параметр `data`. Исправляем описанную линтером ошибку.\n\n![image](/src/images/image%20(27).png)\n\nПосле того, как мы запушим изменения с исправлением линтер снова запустится и продолжит проверку. Исправляем до тех пор, пока не получим зеленую галку)\n\n# Дополнительная информация для ознакомления и советы\n## Хэдеры\nОсновные дефолтные хэдеры:\n- `Accept-Charset` - способ клиента сказать в какой кодировке нужны данные (`UTF-8`, `ASCII`, `whatever`). Обычно всегда используется `UTF-8` и менять не нужно.\n- `Accept-Encoding` (аналог с сервера - `Content-Encoding`) - то, как данные от сервера закодированы, обычно речь про алгоритм сжатия. Например, `gzip`.\n- `Accept-Language` (аналог с сервера - `Content-Language`) - то, какой язык хочет получить клиент. Использовать можно для мультиязычных сервисах.\n- `Accept` (аналог с сервера - `Content-Type`) - Формат данных которые клиент поддерживает, эти форматы называются MIME-типами. Например, `application/json`. Такое часто бывает при передаче файлов или когда хотим открыть файл в вебе, здесь нужно правильно установить MIME-тип.\n- `Cookies` - это способ хранить состояние. Как это работает:\n  - Сначала сервер просит клиента установить `cookies` (`Set-Cookie`).\n  - Клиент их отправляет серверу при обращении в хэдерах с ключом `Cookie`.\n\n\u003e`Cookies` могут использоваться для передачи токена. Не самый лучший способ, но такое может быть. \u003cbr/\u003e В таком случае обязательные параметры для таких `cookies`: \u003cbr/\u003e \u003cli\u003e `secure=true` \u003cli\u003e `httponly=true` \u003cli\u003e `samesite=strict`\n\n## Советы\n\u003ctable border=\"1\" width=\"100%\" cellpadding=\"5\"\u003e\n  \u003ctr\u003e\n    \u003cth\u003eСовет\u003c/th\u003e\n    \u003cth\u003eОписание\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eИспользуйте kebab-case для URL\u003c/td\u003e\n    \u003ctd\u003eВот пример для списка заказов.\n      \u003cbr/\u003eПлохо: \u003ccode\u003e/systemOrders\u003c/code\u003e или \u003ccode\u003e/system_orders\u003c/code\u003e\n      \u003cbr/\u003eХорошо: \u003ccode\u003e/system-orders\u003c/code\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eИспользуйте camelCase для параметров\u003c/td\u003e\n    \u003ctd\u003eВот пример получения списка продуктов в магазине.\n      \u003cbr/\u003eПлохо: \u003ccode\u003e/system-orders/{order_id}\u003c/code\u003e или \u003ccode\u003e/system-orders/{OrderId}\u003c/code\u003e\n      \u003cbr/\u003eХорошо: \u003ccode\u003e/system-orders/{orderId}\u003c/code\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eИспользуйте множественное число для коллекций\u003c/td\u003e\n    \u003ctd\u003eЕсли вы хотите получить всех пользователей.\n      \u003cbr/\u003eПлохо: \u003ccode\u003eGET /user\u003c/code\u003e или \u003ccode\u003eGET /User\u003c/code\u003e\n      \u003cbr/\u003eХорошо: \u003ccode\u003eGET /users\u003c/code\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eНе используйте глаголы в URL ресурсов\u003c/td\u003e\n    \u003ctd\u003eВместо этого пользуйтесь HTTP методами для описания операций.\n      \u003cbr/\u003eПлохо: \u003ccode\u003ePOST /updateuser/{userId}\u003c/code\u003e или \u003ccode\u003eGET /getusers\u003c/code\u003e\n      \u003cbr/\u003eХорошо: \u003ccode\u003ePUT /user/{userId}\u003c/code\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eПользуйтесь глаголами в URL операций\u003c/td\u003e\n    \u003ctd\u003eНапример, если вы хотите переслать уведомление пользователю.\n      \u003cbr/\u003eХорошо: \u003ccode\u003ePOST /alerts/245743/resend\u003c/code\u003e\n      \u003cbr/\u003eПомните, что resend не является [CRUD](https://ru.wikipedia.org/wiki/CRUD) операцией. Наоборот, это функция, которая выполняет определённое действие на сервере.\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eИспользуйте camelCase для JSON свойств\u003c/td\u003e\n    \u003ctd\u003eВместо этого пользуйтесь HTTP методами для описания операций.\n      \u003cbr/\u003eПлохо: \n\n```json\n{\n   user_name: \"Ванька Петров\",\n   user_id: \"1\"\n}\n```\nХорошо: \n\n```json\n{\n   userName: \"Ванька Петров\",\n   userId: \"1\"\n}\n```\n  \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eИспользуйте простой порядковый номер для версий\u003c/td\u003e\n    \u003ctd\u003eИ всегда указывайте его на самом верхнем уровне.\n      \u003cbr/\u003eХорошо: \u003ccode\u003ehttp://api.domain.com/v1/shops/3/products\u003c/code\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eУказывайте количество ресурсов в ответе на запрос\u003c/td\u003e\n    \u003ctd\u003eЭто свойство можно назвать total.\n      \u003cbr/\u003eПлохо: \n\n```json\n{\n  users: [ \n     ...\n  ], offset: 0\n}\n```\nХорошо: \n\n```json\n{\n  users: [ \n     ...\n  ] offset: 0,\n    total: 34\n}\n```\n  \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eИспользуйте параметры \u003ccode\u003elimit\u003c/code\u003e и \u003ccode\u003eoffset\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eИ всегда указывайте его на самом верхнем уровне.\n      \u003cbr/\u003eХорошо: \u003ccode\u003eGET /shops?offset=5\u0026limit=5\u003c/code\u003e\n      \u003cbr/\u003eПотому что на фронтенде часто требуется пагинация\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eНе передавайте аутентификационные токены в URL\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eИ всегда указывайте его на самом верхнем уровне.\n      \u003cbr/\u003eЭто очень плохая практика, потому что часто URL логгируются, и токен также сохранится.\n      \u003cbr/\u003eПлохо: \u003ccode\u003eGET /shops/123?token=some_kind_of_authenticaiton_token\u003c/code\u003e\n      \u003cbr/\u003eХорошо: Вместо этого пользуйтесь заголовками. \u003ccode\u003eAuthorization: Bearer xxxxxx, Extra yyyyy\u003c/code\u003e\n      \u003cbr/\u003eПомните — время жизни токена нужно ограничивать\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eИспользуйте HTTP методы для CRUD операций\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eВ этом и есть их смысл.\n      \u003cli\u003e\u003ccode\u003eGET\u003c/code\u003e: получение данных о ресурсах.\n      \u003cli\u003e\u003ccode\u003ePOST\u003c/code\u003e: создание новых ресурсов и подресурсов.\n      \u003cli\u003e\u003ccode\u003ePUT\u003c/code\u003e: обновление существующих ресурсов.\n      \u003cli\u003e\u003ccode\u003ePATCH\u003c/code\u003e: обновляет только определённые поля существующих ресурсов.\n      \u003cli\u003e\u003ccode\u003eDELETE\u003c/code\u003e: удаляет ресурсы.\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eURL должен отражать структуру вложенных ресурсов\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eПримеры:\n      \u003cli\u003e\u003ccode\u003eGET /shops/2/products\u003c/code\u003e: получить список продуктов из магазина 2.\n      \u003cli\u003e\u003ccode\u003eGET /shops/2/products/31\u003c/code\u003e: получить детали продукта 31 из магазина 2.\n      \u003cli\u003e\u003ccode\u003eDELETE /shops/2/products/31\u003c/code\u003e: удалить продукт 31 из магазина 2.\n      \u003cli\u003e\u003ccode\u003ePUT /shops/2/products/31\u003c/code\u003e: обновить данные о продукте 31. Используйте PUT на URL ресурса, а не коллекции.\n      \u003cli\u003e\u003ccode\u003ePOST /shops\u003c/code\u003e: создать новый магазин и вернуть данные о нём. Используйте  POST на URL коллекции.\n    \u003c/td\u003e\n  \u003c/tr\u003e\n \u003c/table\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsurfstudio%2Fapi-design-best-practices","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsurfstudio%2Fapi-design-best-practices","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsurfstudio%2Fapi-design-best-practices/lists"}