Всем привет! Меня зовут Александр, я фронтенд-разработчик в KTS. Сегодня я расскажу о Strapi CMS, разберу сценарии ее использования на конкретных примерах и поделюсь способами упрощения работы в ней.



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

При использовании такой CMS-системы хочется, чтобы она отвечала исключительно за контент и не накладывала никаких ограничений на UI, поэтому при выборе мы ориентировались на Headless CMS. Headless CMS – это системы, которые предоставляют функционал администрирования контента (админка) и генерируют API, к которому можно подключить любой клиент (веб-приложение, мобильное приложение и т.д.). Поскольку Headless CMS – это просто API, они никак не ограничивают клиент и не влияют на реализацию других сервисов бэкенда.

Мы выбрали Strapi, поскольку это одно из самых популярных на сегодняшний день решений. Система является опенсорсной, и, несмотря на то, что у нее есть и платная версия, бесплатного функционала вполне хватает в работе. Мы используем версию Community Edition (62k+ звездочек и почти 7k форков на github). В этой статье я поделюсь нашим опытом работы со Strapi и наглядно опишу, как с его помощью можно решать практические задачи.

Оглавление:

Что умеет Strapi

С помощью Strapi разработчик может назначать коллекции данных, которые будут доступны с помощью API из CMS, а также задавать им конкретную структуру. Strapi автоматически генерирует весь бойлерплейт для совершения CRUD-операций с этими коллекциями данных, а именно:

  • создает коллекции в БД;

  • формирует REST API (или GraphQL) для этих коллекций;

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

  • дает возможность изменять логику на любом уровне с помощью программирования (кастомизировать или добавлять новые эндпоинты, изменять обращения к базе и т.д.).

После того, как разработчики настроят структуру данных, пользователи CMS (например, контент-менеджеры) смогут заполнять коллекции контентом. Затем эти данные можно будет запросить через API.

Последовательность работы со Strapi можно представить в виде следующей схемы:

Схема работы Strapi
Схема работы Strapi

Как создать проект на Strapi

Чтобы создать новый проект, нужно выполнить следующую команду:

yarn create strapi-app kts-strapi-project --quickstart

У вас получится проект со следующей структурой:

Cтруктура проекта после создания
Cтруктура проекта после создания

В следующих папках будет расположена ключевая логика для работы со Strapi и коллекциями данных:

  • src/api – здесь будет храниться структура коллекций в виде json-файлов (иначе говоря – схемы), а также сгенерированные js-файлы с кодом логики этих коллекций;

  • src/components – здесь хранятся схемы компонентов. Это утилитарные сущности, и для них, в отличие от коллекций, не генерируется API. Компонент можно подключить в качестве поля другого компонента или коллекции. Когда вы запросите у API коллекцию, вы получите связанные с ней компоненты;

  • src/extensions – здесь хранятся расширения, добавленные для удобства работы с CMS. Вы можете написать свое расширение или подключить его из магазина расширений Strapi.

Чтобы запустить проект, выполните следующую команду:

yarn develop

Система предложит зарегистрироваться. После регистрации вы сможете авторизоваться в ней под созданными учетными данными:

Регистрация в CMS
Регистрация в CMS

Режимы работы Strapi

В Strapi есть 2 режима: Content-Type Builder и Content Manager.

Content-Type Builder доступен только в dev-режиме. Dev-режим – это запущенный локально сервер Strapi. На нем разработчики настраивают структуру коллекций, определяют, какими свойствами и атрибутами (иначе говоря, полями) они обладают. После сохранения коллекции происходит кодогенерация json-схем, миграций для БД и бойлерплейта сервера.

Режим Content-Type Builder
Режим Content-Type Builder

Content Manager доступен и в dev-, и в prod-режимах. Prod-режим – это режим, в котором нельзя изменять структуру коллекций, он существует для заполнений коллекций данными.

Режим Content Manager
Режим Content Manager

Как создавать коллекции в Strapi

Для наглядности рассмотрим работу Strapi на примере проекта личного кабинета студента университета. Сначала мы будем создавать коллекции с минимальным набором свойств, а затем дополнять их в процессе знакомства с возможностями Strapi. Первым делом введем некоторые коллекции, которые будут использоваться на нашем сайте.

  1. Коллекция «Студент» со следующими полями:

    • имя;

    • фамилия;

    • дата рождения.

  2. Коллекция «Специальность» со следующими полями:

    • название специальности;

    • код специальности;

    • длительность обучения (в годах).

Виды коллекций данных

В Strapi есть три вида коллекций – Single Type, Collection Types и Component.
Каждый из них подходит для определенной цели.

Single Type подходит, если вы уверены, что данная коллекция хранится в единственном экземпляре. Например, вам наверняка понадобятся такие коллекции single type:

  • Documents – коллекция документов в приложении со следующими полями:

    • user_agreement;

    • privacy_policy;

    • other;

  • Main Page – коллекция для главной страницы приложения;

  • Header – коллекция для управления пунктами главного меню;

  • Footer.

Collection Types подойдет, если коллекция представляет собой список данных.
Например, это могут быть:

  • студенты;

  • специальности;

  • кафедры;

  • адреса институтов на карте.

Components – это вспомогательные сущности. Их нельзя запросить из API отдельным списком, но можно подключить в качестве поля для Single Type, Collection Type или в другой компонент. Примерами Components могут быть:

  • ссылка, которая состоит из текста для ссылки и ее url-адреса;

  • карточка, которая состоит из заголовка, изображения и описания;

  • координата, которая состоит из широты и долготы.

Типы данных

Создадим две коллекции вида Collections Types: «Студент» и «Специальность».

При создании нужно указать Display Name (отображаемое название коллекции в Strapi), а также API ID в единственном и множественном числах – они будут использоваться в дальнейшем для совершения CRUD-операций с коллекциями.

Создание Collection Type «Студент»
Создание Collection Type «Студент»

При создании коллекции необходимо присвоить тип каждому свойству:

  • Text – строка;

  • Rich text (blocks) – текст с форматированием (можно сделать полужирным, курсивным и т.д.);

  • Number – число;

  • Date – дата в формате date, datetime или time;

  • Media – изображение или видео в json-формате, хранит ссылку на файл в хранилище S3;

  • Relation – тип данных «связь». Нужен для того, чтобы задать связи между коллекциями (но не компонентами). Например, на каждой специальности учится много студентов, поэтому у студента будет поле Relation с ссылкой на Специальность с типом связи «один-ко-многим»;

  • Boolean – логический тип данных;

  • JSON – данные в формате JSON;

  • Email – соответствует строке, но валидируется на формат адреса электронной почты в рамках Strapi;

  • Password – нельзя запросить из API, но можно использовать, если кастомизировать запросы. Мы пока не нашли применение для этого типа данных, поскольку хранить пароли в БД – не лучшая идея;

  • Enumeration – выбор из ограниченного списка текстовых значений;

  • UID – на клиент приходит как строка, но на стороне Strapi происходит проверка на уникальность;

  • Component – переиспользуемый компонент из коллекции Components;

  • Dynamic Zone: предположим, у нас есть сущность «Статья», которая состоит из заголовка и контента. Контент, в свою очередь, является массивом произвольных компонентов из определенного набора (например, картинка, текст, видео и опрос). Dynamic Zone позволяет формировать такие динамические списки компонентов.

Типы данных у свойств при создании коллекции
Типы данных у свойств при создании коллекции

Пример создания коллекции

Создадим коллекцию «Студент» со структурой, которую мы спроектировали выше – добавим поля «имя», «фамилия» и «дата рождения»:

Структура Collection Type «Студент»
Структура Collection Type «Студент»

Далее по аналогии создадим коллекцию «Специальность»:

Структура Collection Type «Специальность»
Структура Collection Type «Специальность»

Далее необходимо добавить связь для коллекций «Студент» и «Специальность», чтобы из API можно было запросить всех студентов с конкретной специальности или узнать специальность, на которой обучается отдельный студент. Для этого для коллекции «Студент» добавим поле «speciality» с типом Relation. На каждой специальности учится много студентов, поэтому связь будет «один-ко-многим».

Сначала добавим поле «speciality» типа Relation:

Добавление поля «speciality»
Добавление поля «speciality»

Для коллекции «Студент» добавим поле «photo» с типом Media. Стоит отметить, что при добавлении поля типа Media можно настроить ограничения на форматы данных. По умолчанию там любые файлы. Можно ограничить: только изображения, только видео или только pdf:

Структура Collection Type «Студент» после добавления поля «speciality»
Структура Collection Type «Студент» после добавления поля «speciality»

Предположим, что у каждой специальности есть ссылка на сайт с её детальным описанием. Давайте добавим поле «link» в коллекцию специальность. Ссылка — это комбинация заголовка и адреса. Как и обсуждали ранее, ссылку нужно сделать именно компонентом, потому что она не существует сама по себе, и её не нужно получать из API. В коллекцию «Специальность» нам также необходимо добавить поле «speciality_link». Для этого мы заранее создадим соответствующий компонент «Ссылка», который будет состоять из названия и заголовка:

Создание компонента «Ссылка»
Создание компонента «Ссылка»
Структура компонента «Ссылка»
Структура компонента «Ссылка»

Далее переиспользуем созданный компонент в коллекции «Специальность»:

Переиспользование компонента «Ссылка» внутри Collection Type «Специальность»
Переиспользование компонента «Ссылка» внутри Collection Type «Специальность»

Кодогенерация

После сохранения каждой из коллекций происходит автоматическая кодогенерация схем. По сути, код генерируется согласно принципам архитектуры Model-Routes-Controllers-Service, только вместо Model директория называется content-types. Подробнее об этой архитектуре можно почитать здесь.

Также генерируется логика получения коллекций, и все это хранится в следующих папках:

  • content-types: здесь хранится схема коллекции, которая приходит с API;

  • controllers: здесь хранится логика, с которой обрабатывается HTTP-запрос при обращении к API: происходит валидация и парсинг параметров запроса, настраивается структура ответа с сервера. Controllers используют внутри себя services, чтобы обращаться к БД. Controllers, в отличие от services, не могут быть переиспользованы;

  • routes: здесь хранится список эндпоинтов данной коллекции, которые доступны для обращения;

  • services: здесь настраивается логика обращения к БД. Также именно в services должна быть написана какая-то специфичная бизнес-логика для приложения: например, отправка письма на почту пользователю (это легко сделать с помощью плагинов). Services используются внутри controllers, а также могут переиспользовать друг друга.

Автосгенерированные файлы у коллекции «Студент»
Автосгенерированные файлы у коллекции «Студент»

В результате получаются следующие файлы:

Стоит отметить, что после создания коллекции ее Singular ID и Plural ID нельзя поменять из интерфейса – их можно изменить только путем редактирования кода в файлах выше, но такое изменение часто приводит к ошибкам в миграциях. Если вам все же нужно поменять идентификаторы для коллекции, то самый надежный способ – удалить ее и создать заново.

Пример Singular Id и Plural ID у коллекции «Студент»
Пример Singular Id и Plural ID у коллекции «Студент»

Как работать с API

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

Плагин для автогенерации swagger

Strapi дает возможность устанавливать плагины, которые упрощают настройку и тестирование коллекций. Один из таких плагинов – strapi documentation – позволяет получить автогенерируемый swagger. Чтобы установить его, нужно выполнить следующую команду:

yarn strapi install documentation

После установки документация будет доступна по адресу:

http://localhost:{PORT}/documentation/v1.0.0.

Swagger проекта
Swagger проекта

Добавление данных в коллекцию

Через режим Content Manager создадим несколько экземпляров коллекции «Студент» и заполним их данными. Здесь важно оговориться, что по умолчанию экземпляр коллекции создается в состоянии Draft – это значит, что он не будет доступен с помощью API. Для того, чтобы открыть доступ к созданному экземпляру, нужно перейти в режим его редактирования и нажать Publish.

Добавление данных в коллекцию «Студент»
Добавление данных в коллекцию «Студент»

Настройка доступов

По умолчанию Strapi делает все коллекции приватными – получить их можно только с помощью отправки сгенерированного токена в заголовке. Чтобы сделать коллекцию публичной, нужно открыть раздел «Роли» в настройках. Он будет находиться по адресу:

http://localhost:1338/admin/settings/users-permissions/roles.

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

Настройки приватности для HTTP-запросов
Настройки приватности для HTTP-запросов

Публичные запросы

Укажем, какие типы операций неавторизованный пользователь сможет осуществлять с коллекцией Student. Нам достаточно чтения коллекции и отдельного экземпляра, поэтому отметим флажки find и findOne.

Настройка типов операций у HTTP-запросов
Настройка типов операций у HTTP-запросов

Приватные запросы

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

http://localhost:1338/admin/settings/api-tokens/create.

Создание токена авторизации
Создание токена авторизации

Значение сгенерированного токена нужно пробрасывать при каждом запросе в заголовке Authorization, в swagger это делается через нажатие на кнопку Authorize):

Отправка http-заголовка Authorization внутри swagger
Отправка http-заголовка Authorization внутри swagger

Параметры запроса

Рассмотрим query-параметры для получения коллекции Student. Они делятся на две категории.

Первая категория – те параметры, которые связаны исключительно с пагинацией.

query-параметры запроса у Collection Type «Студент», связанные с пагинацией
query-параметры запроса у Collection Type «Студент», связанные с пагинацией

Вторая категория – параметры, которые помогают настраивать, какие из полей вы хотите запросить у экземпляров коллекций и их порядок.

query-параметры запроса у Collection Type «Студент», связанные с сортировкой и фильтрами
query-параметры запроса у Collection Type «Студент», связанные с сортировкой и фильтрами

С параметрами этой категории я предлагаю познакомиться поближе:

  • sort – значение, по которому нужно отсортировать коллекцию. Принимает на вход название поля и направление сортировки (asc/desc). Есть возможность множественной сортировки;

  • fields – список примитивных полей, которые нужно получить с API. По умолчанию отдаются все примитивные поля, к которым относятся text, enumeration, rich_text, email, password, date, number, boolean и JSON. Fields позволяет описать только те поля, которые нужно получить;

  • populate: Relation, Media, Dynamic Zone и Component не являются примитивными полями, поскольку для их запроса необходимо выполнить дополнительные запросы в БД или использовать join. Поэтому Strapi по умолчанию вместо связи отправляет id этой связи. Чтобы запросить поля вложенных сущностей, эти поля нужно перечислить в populate;

  • filters – правила для фильтрации коллекции. Может принимать значения $gte, $equal и другие.

Подробнее о параметрах можно почитать в документации Strapi.

Пример запроса

Сделаем запрос на список студентов:

http://localhost:1338/api/students

И получим следующий ответ:

Скрытый текст
{
  "data": [
    {
      "id": 1,
      "attributes": {
        "name": "Арсений",
        "surname": "Ковалев",
        "birthday_date": "2004-07-14T20:00:00.000Z",
        "createdAt": "2024-07-17T09:16:31.088Z",
        "updatedAt": "2024-07-21T15:41:59.989Z",
        "publishedAt": "2024-07-17T15:43:49.701Z"
      }
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 1
    }
  }
}

Обратите внимание: экземпляр нашей коллекции обернут в структуру вида { id, attributes }. Strapi делает так с каждым экземпляром коллекции, а также со вложенными сущностями, о которых мы говорили выше.

Также можно заметить, что в ответе на запрос мы не получили поля speciality и image. Произошло это потому, что они являются вложенными сущностями. Как я уже отметил ранее, по умолчанию Strapi не добавляет их в ответ.

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

Добавим в запрос информацию о полях, которые нам нужны, и получим запрос следующего вида:

http://localhost:1338/api/students?populate=speciality,photo

С параметром populate ответ изменился: теперь в него включены поля speciality и photo.

Скрытый текст
{
  "data": [
    {
      "id": 1,
      "attributes": {
        "name": "Арсений",
        "surname": "Ковалев",
        "birthday_date": "2004-07-14T20:00:00.000Z",
        "createdAt": "2024-07-17T09:16:31.088Z",
        "updatedAt": "2024-07-21T20:27:58.544Z",
        "publishedAt": "2024-07-17T15:43:49.701Z",
        "speciality": {
          "data": {
            "id": 1,
            "attributes": {
              "createdAt": "2024-07-17T09:17:03.596Z",
              "updatedAt": "2024-07-17T15:17:34.613Z",
              "publishedAt": "2024-07-17T14:59:42.231Z",
              "name": "Прикладная математика и информатика",
              "code": "01.03.02",
              "duration": 4
            }
          }
        },
        "photo": {
          "data": {
            "id": 1,
            "attributes": {
              "name": "2024-06-30 12.45.19.jpg",
              "alternativeText": null,
              "caption": null,
              "width": 960,
              "height": 1280,
              "formats": {
                "thumbnail": {
                  "name": "thumbnail_2024-06-30 12.45.19.jpg",
                  "hash": "thumbnail_2024_06_30_12_45_19_9e691ba632",
                  "ext": ".jpg",
                  "mime": "image/jpeg",
                  "path": null,
                  "width": 117,
                  "height": 156,
                  "size": 4.82,
                  "sizeInBytes": 4815,
                  "url": "/uploads/thumbnail_2024_06_30_12_45_19_9e691ba632.jpg"
                },
                "small": {
                  "name": "small_2024-06-30 12.45.19.jpg",
                  "hash": "small_2024_06_30_12_45_19_9e691ba632",
                  "ext": ".jpg",
                  "mime": "image/jpeg",
                  "path": null,
                  "width": 375,
                  "height": 500,
                  "size": 32.88,
                  "sizeInBytes": 32882,
                  "url": "/uploads/small_2024_06_30_12_45_19_9e691ba632.jpg"
                },
                "medium": {
                  "name": "medium_2024-06-30 12.45.19.jpg",
                  "hash": "medium_2024_06_30_12_45_19_9e691ba632",
                  "ext": ".jpg",
                  "mime": "image/jpeg",
                  "path": null,
                  "width": 563,
                  "height": 750,
                  "size": 68.55,
                  "sizeInBytes": 68547,
                  "url": "/uploads/medium_2024_06_30_12_45_19_9e691ba632.jpg"
                },
                "large": {
                  "name": "large_2024-06-30 12.45.19.jpg",
                  "hash": "large_2024_06_30_12_45_19_9e691ba632",
                  "ext": ".jpg",
                  "mime": "image/jpeg",
                  "path": null,
                  "width": 750,
                  "height": 1000,
                  "size": 112.06,
                  "sizeInBytes": 112055,
                  "url": "/uploads/large_2024_06_30_12_45_19_9e691ba632.jpg"
                }
              },
              "hash": "2024_06_30_12_45_19_9e691ba632",
              "ext": ".jpg",
              "mime": "image/jpeg",
              "size": 156.6,
              "url": "/uploads/2024_06_30_12_45_19_9e691ba632.jpg",
              "previewUrl": null,
              "provider": "local",
              "provider_metadata": null,
              "createdAt": "2024-07-21T20:27:55.626Z",
              "updatedAt": "2024-07-21T20:27:55.626Z"
            }
          }
        }
      }
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 1
    }
  }
}

Как можно кастомизировать Strapi

Рассмотрим следующую ситуацию. У каждой специальности есть детальная страница на сайте университета, имеющая примерно следующий url:

https://project-example.ru/speciality/{serial_id}

serial_id – это идентификатор, который автоматически присваивается каждому экземпляру коллекции при его создании. С его помощью Strapi позволяет получать информацию об отдельном экземпляре коллекции. К примеру, получить детальную информацию о специальности можно с помощью следующей команды:

GET https://strapi-example.ru/api/speciality/<id>

При этом для улучшения SEO нужно, чтобы каждая детальная страница специальности имела slug (читабельный url) приблизительно следующего формата:

  • https://project-example.ru/speciality/lingvistika

  • https://project-example.ru/speciality/prikladnaya_matematika

Для этого Strapi позволяет переписать логику контроллера и роутов, которые отвечают за то, по каким правилам будет выполняться запрос к API. Чтобы сделать это, в режиме Content-Type Builder добавим в структуру коллекции «Специальность» поле slug типа UID. Теперь slug каждой специальности будет уникален:

Добавление поля «slug» для Collection «Специальность»
Добавление поля «slug» для Collection «Специальность»

Затем в режиме Content Manager присвоим каждому экземпляру коллекции свой slug и опубликуем всю коллекцию:

Заполнение поля «slug» для Collection «Специальность»
Заполнение поля «slug» для Collection «Специальность»

Теперь нужно переписать логику контроллера, чтобы можно было получать нужный экземпляр коллекции по его slug. Для этого придется выполнить следующие преобразования для коллекции «Специальность»:

  1. Добавить кастомный роут /api/specialties/get-by-slug/{slug}.

  2. Добавить в файл services функцию findOneBySlug, чтобы на уровне Strapi корректно обрабатывался sql-запрос.

  3. Доработать логику контроллера, чтобы он корректно доставал из query-параметров slug и populate и пробрасывал их в функцию findOneBySlug.

Сначала добавим утилиту kts-strapi-project/src/utils/extendCoreRouter.js, чтобы расширять логику дефолтного роутера:

const extendCoreRouter = (innerRouter, extraRoutes = []) => {
 let routes;
 return {
   get prefix() {
     return innerRouter.prefix;
   },
   get routes() {
     if (!routes) routes = [...extraRoutes, ...innerRouter.routes];
     return routes;
   },
 };
};


module.exports =  { extendCoreRouter };

Затем доработаем файл kts-strapi-project/src/api/speciality/services/speciality.js с запросом к БД:

Было
'use strict';


/**
* speciality service
*/


const { createCoreService } = require('@strapi/strapi').factories;


module.exports = createCoreService('api::speciality.speciality');

Стало
"use strict";


const { createCoreService } = require("@strapi/strapi").factories;


module.exports = createCoreService(
 "api::speciality.speciality",
 ({ strapi }) => ({
   async findOneBySlug(slug, { populate }) {
       
     return strapi.db.query("api::speciality.speciality").findOne({
       where: {
         slug: slug,
       },
       populate,
     });
   },
 })
);

Аналогичным образом поступим и с другими файлами.

kts-strapi-project/src/api/speciality/controllers/speciality.js:

Было
'use strict';


/**
* speciality controller
*/


const { createCoreController } = require('@strapi/strapi').factories;


module.exports = createCoreController('api::speciality.speciality');

Стало
"use strict";


const { createCoreController } = require("@strapi/strapi").factories;


module.exports = createCoreController(
 "api::speciality.speciality",
 ({ strapi }) => ({
   async findOneBySlug(ctx) {
     const { slug } = ctx.params;


     const sanitizedQuery = await this.sanitizeQuery(ctx);


     const result = await strapi
       .service("api::speciality.speciality")
       .findOneBySlug(slug, {
         populate: sanitizedQuery.populate,
       });


     const sanitizedResults = await this.sanitizeOutput(result, ctx);


     return this.transformResponse(sanitizedResults);
   },
 })
);

kts-strapi-project/src/api/speciality/routes/speciality.js:

Было
'use strict';


/**
* speciality router
*/


const { createCoreRouter } = require('@strapi/strapi').factories;


module.exports = createCoreRouter('api::speciality.speciality');

Стало
"use strict";


const { extendCoreRouter } = require("../../../utils/extendCoreRouter");


const { createCoreRouter } = require("@strapi/strapi").factories;


const defaultRouter = createCoreRouter("api::speciality.speciality");


module.exports = extendCoreRouter(defaultRouter, [
 {
   method: "GET",
   path: "/specialties/get-by-slug/:slug",
   handler: "speciality.findOneBySlug",
   config: {
     auth: false,
   },
 },
]);

Таким образом, мы добавили кастомный эндпоинт /api/specialties/get-by-slug/{slug}.

В примерах выше используется функция sanitizeQuery, она используется чтобы очистить параметры запроса от ошибок и небезопасных значений. Подробнее об sanitize можно почитать здесь.

Теперь запросим с помощью slug какой-нибудь экземпляр из коллекции «Специальность». Сделаем следующий запрос:

http://localhost:1338/api/specialties/get-by-slug/prikladnaya-matematika-i-informatika

И получим следующий ответ:

{
  "data": {
    "id": 1,
    "attributes": {
      "createdAt": "2024-07-17T09:17:03.596Z",
      "updatedAt": "2024-07-23T17:38:09.042Z",
      "publishedAt": "2024-07-17T14:59:42.231Z",
      "name": "Прикладная математика и информатика",
      "code": "01.03.02",
      "duration": 4,
      "slug": "prikladnaya-matematika-i-informatika"
    }
  },
  "meta": {

  }
}

Теперь мы можем легко получить экземпляры коллекции не по serial_id, а по slug.

Однако важно отметить, что Swagger не сможет подтянуть типы принимаемых параметров, несмотря на изменение логики получения. Следовательно, сделать запрос с помощью slug через Swagger будет невозможно:

Пример ошибки валидации в swagger
Пример ошибки валидации в swagger

При этом сделать запрос, к примеру, через postman, будет возможно:

Пример получения специальности с помощью «slug» через Postman
Пример получения специальности с помощью «slug» через Postman

Заключение

В рамках этой статьи мы рассмотрели Strapi в общих чертах и определили, для каких целей он нужен. Создали несколько коллекций и получили их с помощью API и добавили кастомный роут для получения экземпляров коллекции с помощью slug. Однако этим функционал системы, разумеется, не заканчивается.

Если вам не терпится углубиться в подробности, следите за нашими публикациями. В следующей статье мы изучим Strapi более детально и поговорим о том:

  • как обрабатывать сложные типы данных (rich text и dynamic zone);

  • как сортировать коллекции по популярности;

  • как мы типизируем и валидируем коллекции на клиенте с использованием Typescript и zod;

  • как подключать postgres, s3, minio; 

  • какие данные стоит хранить в Strapi, а какие – в админке;

  • что ожидается в новой версии Strapi 5.

А чтобы скрасить ожидание, предлагаю вам почитать другие материалы в нашем блоге, не менее полезные для фронтенд-разработчиков:

Комментарии (8)


  1. xaver
    31.08.2024 10:04

    Не рассматривали directus в качестве аналога?


    1. kava13 Автор
      31.08.2024 10:04

      Мы решили использовать Strapi из-за его популярности и более развитого комьюнити

      Но обязательно обратим внимание на Directus, спасибо за предложенную альтернативу!


  1. Swordsman
    31.08.2024 10:04

    Куда катится Хабр. 20-30 строк кода на пыхе + правильно настроенный апач порвет всякие гламурные штуки. Работа ради работы. Ну бред же.


    1. kava13 Автор
      31.08.2024 10:04
      +3

      CMS – это все-таки более широкий функционал, чем 20-30 строк кода на пыхе

      Проект на Strapi может успешно функционировать вообще без написания кода разработчиками — весь код генерируется автоматически на основе коллекций, создаваемых через UI

      Strapi же предоставляет возможность гибко кастомизировать функционал по мере необходимости


    1. jeserg
      31.08.2024 10:04

      А в чем гламурность этого инструмента, просто интересно на чем строятся такие оценки?


    1. LexterWayne
      31.08.2024 10:04

      Вот благодаря таким программистам (20-30 строк кода) у меня есть работа)) Потом сидишь переписываешь проекты с нуля потому что кто-то решил свой велосипед на PHP соорудить вместо использования фреймворка хотя бы)


  1. PavelKuptsov
    31.08.2024 10:04

    "Media – изображение или видео в json-формате, хранит ссылку на файл в хранилище S3;"

    Вот это напрягает. Зачем нам еще тут S3?
    Чтобы однажды сайт превратился в тыкву?


    1. uwriter
      31.08.2024 10:04
      +1

      Каким образом хранение файлов в S3 уменьшает его отказоустойчивость?