Всем привет! Меня зовут Александр, я фронтенд-разработчик в 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

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

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

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

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

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

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

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

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

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

yarn develop

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

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

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

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

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

Режим Content-Type Builder

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

Режим 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 «Студент»

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

  • 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 «Специальность»

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

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

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

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

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

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

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

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

Переиспользование компонента «Ссылка» внутри 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 у коллекции «Студент»

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

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

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

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

yarn strapi install documentation

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

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

Swagger проекта

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 «Специальность»

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

Заполнение поля «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

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

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

Заключение

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

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

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

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

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

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

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

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

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