Дата публикации: 12.11.2022 в 11:46

Laravel API. Все что необходимо! Json Resource, Resource Collection, Cache, Rate Limit

0 комментария

В этом материале мы затронем тему web api. Сделаем публичные api, чтобы понять все базовые особенности реализации на laravel, тем самым мы дадим доступ остальным пользователям к данным которые мы предоставляем, а api будет интерфейсом для получения этих данных.


В наше время web api крайне важен – практически все приложения делаются по концепции api first (это отдельный проект), а уже далее веб-сайт, мобильные приложения и так далее, которые взаимодействуют с api и получают нужные данные и работают с ними. Так устроен современный мир, такие приложения получаются более быстрыми, гибкими и их проще поддерживать. Либо api даёт пользователям в открытом либо в закрытом доступе получить данные о курсе валют, погоде, состояние своих данных в том или ином сервисе. А уже сами решайте как их использовать.


Данная статья является текстовой версией видео урока на моём канале youtube:


У нас будет простой api, с фантазией у меня не так всё хорошо – мы будем хранить историю апдейтов версий laravel и давать возможность получить текущую последнюю версию. То есть дадим остальным пользователям используя наш api получить данные по версионности laravel.


Что нас ждёт?

  • Сделаем api с разбивкой на версии
  • Разберёмся с ресурсами и с ресурс-коллекциями
  • Добавим кэширование запросов и observer для контроля кэша
  • Разберёмся с рейт лимитом, ограничением на запросы к api

Для тестирования api буду использовать программу postman и вам также рекомендую. Здесь удобно менять http методы, задавать заголовки, устанавливать параметры, добавлять токен и также отслеживать ответ. Смотреть сколько времени потребовалось на получение ответа и сколько байт он занимает. По-этому рекомендую использовать postman, либо какой-либо другой аналог.

Далее вернёмся к нашему laravel приложению и посмотрим что у нас есть на старте. Я уже создал таблицу version, она простая, здесь по факту два необходимых поля – это title, тег обновления, и relise date (дата обновления). И наполнил эту таблицу тестовыми данными исходя из последних апдейтов laravel. Ну и само собой добавил модель для этой таблицы.

Друзья, прежде чем начать, на что необходимо обратить внимание – на то, что в laravel по-умолчанию есть api роуты. В директории routes есть файл api.php и здесь задам уже задан один route пример.

Мы его пока что удалим, он нам не потребуется. Далее откроем RouteServiceProvaider.php и увидим что этот route зарегистрирован и он у нас имеет несколько отличий. Во-первых есть префикс api, то есть запросы на URL с api будут попадать в RouteApi.php и также есть middleware группа api, давайте также на неё взглянем.

Http > Kernel.php, группа middleware api, здесь всего два middleware:

  1. throttler, не обращайте внимание что он в виде строки, это у нас alias, точно такой же middleware класс, который отвечает за rate лимит, за ограничение запросов.
  2. middleware такой же как и в web группе, он у нас отвечает за RouteBinding, за то, чтобы в роутах мы могли передавать параметры либо биндить модели.

Друзья, давайте вернёмся к RouteServiceProvider и немного подкорректируем api route, сразу будем привыкать работать правильно и качественно, и реализуем наш api с разбивкой на версии. Добавим в URL префикс версию. Начнём с версии один, само собой. И также будем разбивать всё файлы на версии. Зачем это нужно? Со временем ваш сервис обрастётся клиентами и если вы решите изменить реализацию вашего api, улучшить его, изменить структуру ответов и методов, то все ваши клиенты получают несовместимые проекты с ошибками и так делать нельзя. Апгрейды мы будем выполнять в новых версиях, а старые будем продолжать поддерживать и работать до тех пор пока совсем не устареют и заранее не проинформируете о прекращении поддержки на какой-либо версии и переходе на следующую, минимальную. Давайте добавим префикс и также api версии один, а также изменим файл со всеми route, api.php на api версии один, с изменениями версии у нас также может меняться и набор route, поэтому файл с route мы также будем делить на версии. Ну и, соответственно, переименуем сам файл, «api_v1.php». Далее перейдём в сам route и здесь у нас будет твориться вся магия, я ещё раз повторюсь, что route в laravel важны, это у нас входная точка для запросов и в принципе здесь может всё и закончится, скажем, добавим route, метод get, URL будет от слэша, то есть от префикса api_v1.php, далее передаём function и здесь вернём ответ, сейчас просто по-умолчанию просто massive. Далее откроем postman, отправим запрос и увидим в ответе massive, то есть по большому счёту мы уже реализовали api с использованием laravel, давайте изменим massive на модель со всеми версиями, метод all, чтобы вывести все записи из таблицы и также пульнём запрос в postman и уже увидем JSON, со всеми записями и таблицами версий.

Кстати, ещё один момент, который важно знать при работе с api на laravel, также не забывайте про заголовок accept application/json, чтобы laravel приложение понимало что вы в ответе от него хотите именно json, в противном случае, если у вас возникнут какие-либо ошибки, то laravel вернёт html страницу, но если будет присутствовать этот заголовок, то в ответе будет json с параметром message и с сообщением об ошибке, возвращаемся назад. Да друзья, несмотря на то, что мы реализовали api отходя от route, так мы оставлять само собой не будем и для начала добавим контроллер, будем придерживаться важных принципов при разработке и разделять ответственности и контроллер нам необходим, чтобы не оставлять всю логику в route, которая при таком подходе со временем превратится в огромный нечитаемый файл, контроллер наш мост между route и json ответом. Как видите, по большему счёту api отличается от стандартного web приложения тем, что в ответе у нас json, а не html.

Окей, давайте добавим контроллер:

api % php artisan make:controller

и контроллеры мы собой также будем разделять на версии. Все контроллеры у нас будут располагаться в директории api. Далее, директория с версией: «Api/V1/IndexController». Всё, контроллер добавлен, давайте сразу его добавим в route вместо функции, метод будет index, наименование route: «home». Перейдём к контроллеру и добавим метод index. И здесь сейчас вернём тоже самое, все записи из таблицы версий. Давайте перейдем в postman, посмотрим что всё также работает, видим всё тот же ответ, с json, со всеми записями из таблицы версий.

Давайте добавим ещё один route, который у нас будет отображать последнюю актуальную версию laravel, перейдём в route, текущий метод в контроллере мы переименуем в all, добавим в индекс метод, где будем выводить один момент с последней актуальной версией laravel, отсортируем по дате релиза и метод first вернёт один объект. Вернёмся к postman, пульнём запрос, увидем в json последний объект с последней версии laravel, а по URL'у all у нас будут отображаться все версии laravel. Далее, возвращаемся к контроллеру, и с какой проблемой мы можем здесь столкнуться, в нашем примере у нас простая таблица, всего два поля.

Но как быть если у нас огромная таблица с большим набором полей, какие-то поля нам необходимо скрывать в json, какие-то поля трансформировать, скажем, даты переводить к определенному формату, либо есть в базе данных поле с изображением где хранится просто, наименование файла, а нам в json нужно вернуть полный путь до изображения, конечно же мы можем работать и в рамках модели, здесь добавить метод select, выбрать определённое количество полей внутри модели, в свойстве hidan какие-либо поля скрыть, скажем если скроем id, то далее мы в json ответе его не увидим, также добавить мутаторы, для приведения некоторых полей в определенный формат, те же даты, либо изображения, но зачем мы будем работать так, если в laravel есть необходимые для этого инструменты, а именно json ресурсы, и json ресурс коллекции.

Когда и что нужно использовать? Когда у нас один объект eloquent модели, то мы используем json ресурс, когда коллекция объектов, то resource collection. Давайте начнём с json resources, для начала его создадим:

php % artisan make:resource VersionResource


Ресурс создан, давайте посмотрим на него, располагается в директории

«http» > «resources» > «VersionResource.php»,


пока что мы в нём ничего менять не будем, посмотрим как он работает по-умолчанию. Немного изменим ответ, здесь добавим экземпляр класса VersionResource, и в констракт методе передадим текущую eloquent модель с последней записью. И давайте откроем postman и посмотрим что у нас получилось. В объекте у нас тот же ответ json, только уже обернутый в дату, давайте для начала поговорим об этом. То есть по-умолчанию у нас ресурсы и коллекции имеют обертку, по-умолчанию data. На эту обертку можно менять наименование, воспользуюсь свойством $wrop и передать другое значение, скажем пускай это будет test. Теперь, если мы пульнём запрос, у нас будет обертка test, но если эта обертка вообще не нужна вам, то вы можете открыть AppServiceProvider.php и здесь добавить следующую строку, обратиться к классу json resources, и добавить WithoutWropping, после чего у нас уже не будет обертки. Либо таким же образом можно глобально с помощью метода $wrop задать наименование этой обертки. И в последующем уже все ресурсы и коллекции будут с оберткой, которая будет указана здесь. Давайте уберём, пускай будет у нас обертка, по-умолчанию.

Теперь перейдём в resource, здесь изменим этот момент, вернём massive и давайте возвращать только title и обратимся к атрибуту $this => title. Далее посмотрим, что у нас получается в json, мы видим что у нас приходит именно title. Давайте ещё немного изменим, как раз работаем с трансформацией, возьмём дату релиза и здесь уже вернём release.date в определённом формате. Посмотрим что у нас получилось, мы видим уже дату в формате которую её задали. Также мы можем добавлять здесь какие-либо условия, скажем, новое поле, meta, и далее воспользоваться методом ahem, метод с условием, первым параметром мы передаём условие, скажем, title у нас версии 8.61, тогда, второй параметр у нас function, мы вернём единицу. В противном случае, вернём двойку, ну просто как пример. Давайте посмотрим что у нас получилось, у нас в ответе единица.


Здесь можно вывести отношения, также использовать ресурс для отношений, ещё много дополнительных инструментов. Далее давайте создадим json resource коллекцию точно также, как мы создавали resource:

«api % php artisan make:resource VersionCollection»


тем самым мы дадим понять laravel, что мы от него здесь хотим resource коллекцию, а не resource. Если бы мы назвали как-то иначе, то нам необходимо было бы ещё добавить параметр collection, чтобы так же сказать laravel, что мы от него хотим, а именно коллекцию, но мы будем использовать наименование в рамках концепции laravel, тем самым при создании коллекции нам здесь не потребуется добавлять свойство $collect, чтобы указать также, что это коллекция ссылается на наш resource, VersionResource. То же самое как это работает в eloquent моделях, если мы называем модель в рамках концепции laravel, то нам не приходится добавлять свойство dabble и указывать к какой таблице у нас относится эта модель. То же самое здесь. Такая мы добавили коллекцию, она у нас по-умолчанию ссылается на наш resource. Далее в IndexController.php мы немножко изменим код, точно также как и с resource, здесь отдадим экземпляр VersionCollection, в констракт методе у нас будет уже коллекция объектов, далее откроем postman, отправим запрос в all, и увидим то же самое, также обертка data и здесь у нас объекты наших версий, видим, что meta версии 8.61 равна единице, а у всех остальных двойка, то есть также сработало условие. И согласитесь, в таком ключе работать гораздо удобнее.

Ещё интересный момент, если мы здесь возьмём пагинацию, вызовем метод paginate, скажем, по одному объекту, опять пульнём запрос, мы, помимо объекта версий, также увидим и дополнительные параметры, а именно ссылки на следующие страницы в пагинации, а также полная информация по каждому элементу пагинации. Если вдруг мы строим приложение, скажем, с vue.js, или с реактом, то мы сможем в таком ключе удобно отрендерить всю пагинацию, с каждым элементом, так работать будет гораздо удобнее. Это тоже очередной плюс в работе с json resources, а также с resource коллекциями.


Далее друзья, давайте немного оптимизируем наш api, не будем каждый раз делать запрос в базе данных, новые версии laravel выходят не каждую секунду, по-этому запрос к базе мы «закэшируем», будем использовать файловый кэш. Кэш настраивается «config» > «cache.php», здесь по-умолчанию используется как раз файловый кэш, который у нас будет храниться в директории storage. Давайте здесь немного изменим код, воспользуемся фасадом cache, далее метод remember, то есть будем сохранять кэш, наименование кэша, далее, сколько этот кэш будет жить, пусть будет жить сутки, шестьдесят умножить на шестьдесят минут и умножить на двадцать четыре часа, будет равняться суткам.

Далее у нас параметр function, где мы вернём то, что будем сохранять в нашем кэше, а именно в eloquent object, с запросом в базе, с последней версией laravel, актуальной. После чего у нас при первом запросе будет сохраняться кэш, который будет жить двадцать четыре часа и в последующем запросов в базе уже не будет. Как быть только в той ситуации, если у нас на протяжении этих двадцати четырёх часов, пока живёт кэш, вдруг пришла новая версия и нам нужно этот кэш сбросить, иначе пользователи эту новую версию не будут видеть. Для этого мы будем использовать event, события, сейчас мы создадим observer, для нашей модели version:

api % php artisan make:observer VersionObserver


Далее, вывод в EventServiceProvider.php мы его зарегистрируем, укажем, что version модель у нас наблюдает эти event с этого observer'а, а в самом observer мы добавим лишь один метод на создание новой записи, created, если у нас добавилась новая запись в моделе version, то мы обращаемся опять к фасаду cache и удаляем кэш version. После чего, у нас добавилась запись по laravel версии, у нас кэш будет обнулён, в последующем он опять создастся когда он отобразится и у нас всегда будет работать этот момент. То же самое и здесь, но здесь, я думаю, вы сделаете сами, то есть ничего меняться не будет, единственное что, поменяется ключ от кэша и в observer надо будет добавить ещё сброс и второго ключа.

И напоследок друзья, давайте добавим rate limit – ограничение запросов к нашему api, чтобы исключить последствия от DDOS атак. Давайте вернёмся к файлу kernel, здесь я напомню, у нас все запросы к api routebу нас уже есть middleware throttle, который как раз отвечает за rate limit, а настроен он в RouteServiceProvider.php, здесь по-умолчанию уже был этот ConfigurateLimit и вот он у нас здесь для api, мы задаём, что минуту, максимальное допустимое количество запросов шестьдесят и всё это привязано либо к текущему авторизованному пользователю, к его id, либо если его нет, тогда к текущему ip-адресу.

Давайте для тестов изменим, поставим всего один запрос в минуту, вернёмся к postman, пульнём запрос, первый запрос у нас проходит, а второй в эту же минуту у нас выдаёт ошибку, слишком много попыток, то есть в данном случае у нас сработал RateLimit. Конечно здесь значение нужно будет поставить повыше, пусть это будет как и прежде, шестьдесят минут, но просто чтобы вы знали, что такой функционал есть и как с ним работать и запросы к api конечно же нужно ограничить.

Друзья, на этом всё.

ОбщайсяРазвивайсяУчисьРаботай
ОбщайсяРазвивайсяУчисьРаботай
ОбщайсяРазвивайсяУчисьРаботай
ОбщайсяРазвивайсяУчисьРаботай