Привет, Хабр! Platform V DataSpace Community Edition — это open source инструмент для быстрой разработки бизнес-приложений. В этой статье мы познакомимся с созданием решений с DataSpace на практическом примере банковского приложения, разберёмся, как работает платформа и почему она может стать вашим секретным оружием для создания MVP.

Platform V DataSpace Community Edition

Platform V DataSpace Community Edition — это программный компонент, который помогает ускорить создание бизнес-приложений и существенно снизить затраты на их разработку. Платформа базируется на декларативном подходе: вы описываете модель предметной области в XML, а DataSpace автоматически генерирует GraphQL API для работы с данными.

Ключевые возможности:

  • Декларативное моделирование — описываете структуру данных в XML, без необходимости писать бойлерплейт-код.

  • Автоматическая генерация GraphQL API — полнофункциональный API создаётся на основе модели.

  • Гибкая система безопасности — встроенные механизмы Row-Level Security (RLS) и Role-Based Access Control (RBAC).

  • Интеграция с IAM (например, KeyCloak) — готовая поддержка аутентификации и авторизации через JWT.

  • Автоматическая кодогенерация frontend — TypeScript-типы, React-хуки и компоненты форм генерируются автоматически.

  • Open Source — платформа доступна для свободного использования

Аналоги и отличия

На международном рынке DataSpace CE можно сравнить с такими решениями как Firebase, Supabase или Hasura. Ключевое отличие DataSpace — акцент на предметно-ориентированном проектировании (DDD) и возможность описывать сложные бизнес-модели с агрегатами, которые автоматически превращаются в GraphQL API с поддержкой сложных правил доступа.

Описание банковского демо-приложения

Для демонстрации ключевых возможностей DataSpace CE мы разберём полнофункциональное демо-приложение для управления банковскими операциями. Это не игрушечный пример — в приложении реализована ролевая модель, транзакции, механизмы блокировок и правила доступа на уровне строк.

Банковское демо-приложение
Банковское демо-приложение

Ключевые технические особенности:

  • Многопользовательский доступ;

  • JWT, KeyCloak;

  • Row-Level Security;

  • Role-Based Access Control;

  • Бизнес-логика (строковые выражения);

  • Атомарность операций;

  • Типобезопасность, TypeScript;

  • Автоматическая кодогенерация.

Основная функциональность:

  • Управление клиентами, счетами и операциями (с поддержкой овердрафта).

  • Гибкое разграничение прав доступа: клиенты видят только свои данные, менеджеры — все данные.

  • Справочники валют, типов клиентов и операций.

Необходимые компоненты:

  • Git;

  • Open JDK 17+;

  • Docker & Docker Compose;

  • Node.js.

Быстрый старт

Запуск DataSpace CE

Клонируйте репозиторий DataSpace:

git clone https://gitverse.ru/sbertech/dataspace-ce.git

Перейдите в папку банковского демо-приложения:

cd dataspace-ce/examples/bank

Запустите DataSpace и IAM KeyCloak:

./bank.sh

В терминале будет выводиться диагностическая информация о запуске компонентов, по завершении успешного запуска будет выведена информация о DataSpace и KeyCloak:

Application is running!
Database connection parameters:
URL: jdbc:postgresql://localhost:5433/dspcdb?currentSchema=public
Username: dspc
Password: dspc

Useful links:
- Models: http://localhost:8083/actuator/models
- GraphiQL: http://localhost:8083/graphiql?path=/models/1/graphql

Keycloak is available at:
- Admin Console: http://localhost:8180/admin
- Realm: bank
- Username: admin
- Password: 12345

Запуск DataSpace

Создание демо-данных

Откройте GraphQL-редактор: http://localhost:8083/graphiql?path=/models/1/graphql

Последовательно (!) выполнить все пять запросов из файла demodata.graphql

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

Финальный запрос FinalIntegrityCheck выводит информацию о всех созданных элементах набора демо-данных.

После успешного создания набора демо-данных необходимо раскомментировать параметр:

dataspace.security.graphql.permissions.source=fileв файле dataspace-ce/examples/bank/files/context-child.properties

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

Для применения настроек перезапустите проект, выполнив в корне проекта (dataspace-ce/):

./bank.sh

Набор банковских демо-данных успешно создан!

Создание демо-пользователей IAM KeyCloak

Откройте панель управления IAM KeyCloak: http://localhost:8180/admin/master/console/#/bank/users

Авторизуйтесь под учетной записью администратора (создана предварительно): логин: admin, пароль: 12345

Создание пользователей
Создание пользователей

Перейдите на вкладку «Users» http://localhost:8180/admin/master/console/#/bank/users и нажмите кнопку «Add user». Создайте трех новых пользователей со следующими учетными данными:

  • user1 user1@bank.com Petrov Ivan

  • user2 user2@bank.com Sidorova Maria

  • user3 user3@bank.com Smirnov Egor

При создании пользователей для каждого установите параметр Email verified — ON.

Назначьте для каждого пользователя пароль на вкладке «Credentials»:

Параметр Temporary при создании пароля для каждого пользователя должен быть установлен в OFF (иначе при первом входе пользователя система будет запрашивать смену пароля).

Пароли
Пароли

Далее добавьте каждому пользователю Realm role:

  • user1 client

  • user2 client

  • user3 manager

Набор демо-пользователей успешно создан!

Пользователи
Пользователи

Запуск frontend-приложения

Фронтальная часть демо-приложения построена на основе React-приложения, которое использует Apollo Client для работы с GraphQL API. В качестве библиотеки визуальных компонентов используется AntDesign.

Для запуска приложения последовательно выполните в терминале в директории dataspace-ce/examples/bank/simple-ds-gql-generator:

npm install
npm run start

Перейдите в браузере по адресу: http://localhost:3000/, пройдите авторизацию под любым ранее созданным пользователем (user1, user2 или user3) и протестируйте работу приложения.

Поздравляем, развёртывание банковского демо-приложения успешно завершено!

Архитектура

Демо-приложение банковской системы построено на основе классической многоуровневой архитектуры.

Архитектура демо-приложения
Архитектура демо-приложения

Frontend Layer

Клиентская часть представляет собой современное React-приложение, которое использует:

  • React Application — SPA-приложение с компонентами для работы со счетами, клиентами и транзакциями.

  • keycloak-js library — JavaScript-библиотека для интеграции с KeyCloak (аутентификация).

  • Apollo Client + JWT — клиент для GraphQL-запросов с автоматической подстановкой JWT-токенов в заголовки.

Backend Layer

Ядро системы — это DataSpace Community Edition:

  • model.xml — декларативное описание предметной области: агрегаты, сущности, их атрибуты и связи.

  • graphql-permissions.json — конфигурация правил доступа к данным с поддержкой PathConditions (фильтрация строк) и CheckSelects (проверка прав на операции).

  • GraphQL API — автоматически генерируется на основе model.xml и предоставляет полную CRUD-функциональность.

Authentication Layer

За аутентификацию и авторизацию отвечает IAM KeyCloak:

  • Централизованное управление пользователями и ролями

  • Выдача JWT-токенов с информацией о пользователе (email, роли)

  • JWKS Public Keys — набор публичных ключей для проверки подлинности токенов.

Data Layer

В основе работы с данными лежит PostgreSQL — здесь хранятся все данные приложения: клиенты, счета, транзакции, справочники. При этом все ключевые компоненты DataSpace, в том числе PostgreSQL, развёртываются в Docker-контейнерах, что позволяет легко развернуть приложение на любой платформе и ускорить процесс обучения и разработки.

Процесс авторизации и работы с данными

  • Пользователь открывает приложение в браузере.

  • React-приложение через keycloak-js обращается к KeyCloak для аутентификации.

  • После успешной аутентификации пользователь получает JWT-токен.

  • Apollo Client использует этот токен для отправки GraphQL-запросов к DataSpace.

  • DataSpace проверяет токен через JWKS, применяет правила доступа из graphql-permissions.json и выполняет запрос к PostgreSQL.

  • Данные возвращаются пользователю с учётом его прав доступа.

Поток авторизации и выполнения запроса
Поток авторизации и выполнения запроса

Domain-Driven Design (DDD)

DataSpace CE базируется на принципах предметно-ориентированного проектирования (Domain-Driven Design). В центре этого подхода — концепция агрегатов.

Агрегат — это группа связанных сущностей, которые изменяются вместе как единое целое. Доступ к ним всегда контролируется через один главный объект (корень агрегата). Это помогает поддерживать консистентность данных и упрощает моделирование сложной логики.

Использование подхода DDD позволяет точнее отражать бизнес-логику в программном коде, облегчает коммуникацию между разработчиками и предметными экспертами, а также ускоряет внедрение изменений. DDD помогает строить гибкие и масштабируемые системы, где модель предметной области становится основой для автоматизации, тестирования и поддержки бизнес-процессов.

Модель предметной области

Ниже представлена модель предметной области банковской системы, познакомимся с ней подробнее.

Модель данных (ER диаграмма)
Модель данных (ER диаграмма)

Модель данных (ER диаграмма)

Агрегат «Клиент» (Client):

  • Информация о физическом или юридическом лице.

  • Атрибуты: имя, фамилия, email, телефон, адрес, ИНН, паспортные данные.

  • Связь с типом клиента (справочник).

Агрегат «Счёт» (Account):

  • Банковский счёт клиента.

  • Атрибуты: номер счёта, валюта, текущий баланс, тип овердрафта.

  • Связь с клиентом.

  • Включает дочернюю сущность Transaction — транзакции не могут существовать без счёта, поэтому они объединены в один агрегат.

Справочники:

  • Currency (валюты с кодами ISO 4217);

  • ClientType (типы клиентов и условия обслуживания);

  • OverdraftType (типы и лимиты овердрафта);

  • TransactionType (типы банковских операций).

XML-описание модели предметной области

На физическом уровне модель в DataSpace представляет собой XML-файл, который содержит описание предметной области. Далее на основе этого XML-описания DataSpace автоматически создает таблицы в базе данных, GraphQL-схему с типами и запросами, а также применяет все необходимые валидации.

Фрагмент XML-описания модели предметной области банковского приложения:

Пример model.xml в редакторе
Пример model.xml в редакторе

В репозитории проекта можно найти больше примеров XML-описания моделей предметной области.

Механизмы безопасности: многоуровневая защита

Одна из сильных сторон DataSpace CE — встроенная гибкая система безопасности. Она работает на нескольких уровнях одновременно. Рассмотрим их подробнее.

Ролевая модель

В банковском демо-приложении реализована двухуровневая ролевая модель — «client» и «manager». Такой подход выбран осознанно, чтобы продемонстрировать ключевые возможности DataSpace CE при администрировании доступа на разных уровнях:

  • Роль «client» типична для большинства бизнес-приложений: она иллюстрирует ограничение доступа только к своим данным с помощью Row Level Security (RLS) и позволяет показать, как DataSpace автоматически фильтрует строки для безопасности:

    o   видит только свои данные (клиенты, счета, транзакции);

    o   может изменять только свои данные;

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

  • Роль «manager» предназначена для пользователя с расширенными административными правами. Через неё удобно демонстрировать реализацию Role-Based Access Control (RBAC), когда определённые операции и справочники доступны только определённым ролям:

    o   видит данные всех клиентов;

    o   может изменять любые данные;

    o   управляет справочниками (создание, изменение, удаление).

Этот вариант прост для понимания, но универсален для расширения: его легко адаптировать под большее число ролей или сложные правила доступа.

JWT + KeyCloak: централизованная аутентификация

При входе в приложение каждый пользователь получает JWT-токен, который содержит:

  • sub — идентификатор пользователя;

  • email — email пользователя;

  • realm_access.roles — массив ролей пользователя.

DataSpace проверяет подпись токена с помощью публичных ключей JWKS из KeyCloak и извлекает данные о пользователе для дальнейших проверок. Для лучшего понимания работы механизмов безопасности можно получить JWT-токен, выполнив запрос (пример для user1):

curl -X POST \
  'http://localhost:8180/realms/bank/protocol/openid-connect/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=password' \
  -d 'client_id=bank' \
  -d 'username=user1' \
  -d 'password=12345'

Из полученного ответа скопируйте access_token:

JWT токен в ответе
JWT токен в ответе

Перейдите на сайт https://jwt.io/ для анализа токена:

Декодированный JWT токен
Декодированный JWT токен

Здесь вы можете увидеть содержимое токена: email пользователя, его роли и другие claims, которые используются DataSpace для проверки прав доступа.

Row-Level Security (RLS): фильтрация на уровне строк

С помощью механизма PathConditions в файле graphql-permissions.json можно задать правила фильтрации данных. Например, настроить фильтрацию таким образом, чтобы клиент банка видел только свои счета:

  {
    "name": "searchClient",
    "body": "query searchClient($cond: String, $sort: [_SortCriterionSpecification!], $limit: Int, $offset: Int) {\n  searchClient(cond: $cond, sort: $sort, limit: $limit, offset: $offset) {\n    elems {\n      ...ClientAttributes\n    }\n  }\n}\n    fragment ClientAttributes on _E_Client {\n  id\n  __typename\n  address\n  clientType {\n    id\n  }\n  emailAddress\n  firstName\n  inn\n  lastName\n  middleName\n  passportDetails\n  phoneNumber\n}",
    "allowEmptyChecks": true,
    "disableJwtVerification": false,
    "pathConditions": [
      {
        "path": "searchClient",
        "cond": "'manager' $in ${[]:jwt:realm_access.roles} || it.emailAddress==${String:jwt:email}"
      }
    ]
  },

DataSpace автоматически добавляет условие фильтрации WHERE к SQL-запросу ещё на уровне базы данных.

Role-Based Access Control (RBAC): проверка прав на операции

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

  {
    "name": "createClient",
    "body": "mutation createClient($input: _CreateClientInput!) {\n  packet {\n    createClient(input: $input) {\n      ...ClientAttributes\n    }\n  }\n}\n    fragment ClientAttributes on _E_Client {\n  id\n  __typename\n  address\n  clientType {\n    id\n  }\n  emailAddress\n  firstName\n  inn\n  lastName\n  middleName\n  passportDetails\n  phoneNumber\n}",
    "allowEmptyChecks": false,
    "disableJwtVerification": false,
    "checkSelects": [
      {
        "typeName": "SysRootSecurity",
        "conditionValue": "'manager' $in ${[]:jwt:realm_access.roles}",
        "description": "Только менеджеры могут создавать клиентов"
      }
    ]
  }, 

Если в массиве ролей пользователя нет роли manager, запрос вернет ошибку доступа и не позволит выполнить соответствующую операцию. Такая проверка происходит автоматически на уровне бэкенда: DataSpace анализирует значения, полученные из JWT-токена пользователя, сопоставляет их с условиями, заданными в checkSelects, и в случае несоответствия возвращает стандартное сообщение об отсутствии прав или другой преднастроенный ответ.

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

Бизнес-логика

Одной из ключевых возможностей DataSpace CE является возможность выполнения сложной бизнес-логики прямо в GraphQL-запросах. Для этого используется механизм строковых выражений для фильтрации данных.

Рассмотрим работу данного механизма на примере задачи добавления транзакции, при этом будем проверять допустимость операции на основе текущего баланса счета и допустимого овердрафта, а также в случае успешной проверки обновлять значения баланса счета.

Ключевая логика данной проверки находится в файле dataspace-ce/examples/bank/simple-ds-gql-generator/src/graphql/__generate/Account/Transaction.graphql:

mutation addTransaction(
  $accountId: ID!
  $accountNumber: String!
  $amount: BigDecimal!
  $operationType: String!
  $transactionType: ID
  $transactionDate: _DateTime
) {
  packet {
    # Блокировка счёта (защита от race conditions)
    blockAccount: getAccount(
      id: "find:it.accountNumber == ${accountNumber}"
      lock: WAIT
    ) @strExpr(string: $accountNumber) {
      id
      accountNumber
      currentBalance
    }
    
    # Проверка овердрафта (если не пройдёт - ошибка и откат)
    checkOverdraft: getAccount(
      id: "find:it.accountNumber == ${accountNumber} && (it.overdraftType==null || it.currentBalance + ${amount} >= 0 - it.overdraftType.val)"
      lock: WAIT
      failOnEmpty: true
    ) @strExpr(string: $accountNumber, bigDecimal: $amount) {
      id
      accountNumber
      currentBalance
    }
    
    # Обновление баланса
    updateAccount(
      input: { id: $accountId }
      inc: { currentBalance: { value: $amount } }
    ) {
      id
      accountNumber
      currentBalance
    }
    
    # Создание транзакции
    createTransaction(
      input: {
        account: $accountId
        amount: $amount
        operationType: $operationType
        transactionType: $transactionType
        transactionDate: $transactionDate
      }
    ) {
      ...TransactionAttributesForAdd
    }
  }
}

fragment TransactionAttributesForAdd on _E_Transaction {
  id
  __typename
  account {
    id
  }
  aggregateRoot {
    id
  }
  amount
  operationType
  transactionDate
  transactionType {
    id
  }
}

Данный запрос представляет собой GraphQL-мутатцию addTransaction, используемую для добавления транзакции. Давайте подробно разберём, что происходит на каждом этапе выполнения этой мутации:

  1. Блокировка счёта (blockAccount). На этом этапе вызывается запрос getAccount с блокировкой (lock: WAIT), чтобы предотвратить race conditions — одновременное изменение баланса со стороны нескольких транзакций. Это важно для сохранения целостности данных.

    Если простыми словами, то race conditions — это ситуация, когда несколько транзакций пытаются одновременно изменить одну и ту же запись в базе данных, что может привести к непредсказуемым результатам.

  2. Проверка на возможность овердрафта (checkOverdraft). Этот шаг нужен для проверки, не уйдет ли баланс счёта ниже допустимого лимита (овердрафта) после выполнения операции. Если текущий баланс с учетом суммы операции окажется меньше допустимого лимита, возникнет ошибка, и транзакция не будет выполнена.

  3. Обновление баланса (updateAccount). После успешной проверки производится непосредственное изменение баланса счета. Значение обновляется на сумму транзакции — как в «+» (зачисление), так и в «‑» (списание), в зависимости от знака передаваемой суммы.

  4. Создание транзакции (createTransaction). На этом этапе фиксируется новая сущность транзакции в базе данных с деталями: ID счета, сумма, тип операции, дата, и так далее. Операция возвращает набор атрибутов, описанных во фрагменте ...TransactionAttributesForAdd. Фрагменты в GraphQL используются для повторного использования фрагментов в запросах. В данном примере фрагмент TransactionAttributesForAdd определяет перечень полей, которые запрашиваются у созданной транзакции — это позволяет получить всю необходимую информацию о только что созданной записи (идентификаторы, сумму, дату и так далее).

Таким образом, данный запрос обеспечивает полную атомарность обработки банковской транзакции, гарантируя корректность и согласованность данных между системой хранения и логикой приложения. Это одна из ключевых возможностей DataSpace CE, позволяющая реализовать сложные бизнес-логику и обеспечить целостность данных в приложении.

Почему GraphQL?

Возникает резонный вопрос: почему DataSpace использует именно GraphQL, а не REST API? На это есть несколько причин.

Гибкость запросов

Клиент сам решает, какие поля ему нужны. Не надо создавать десятки эндпоинтов для разных случаев — один GraphQL-запрос может получить данные из нескольких связанных сущностей.

Например, запрос для получения списка клиентов, а также связанных с ними счетов и транзакций:

query {
  searchClient {
    elems {
      id
      firstName
      email
      accountList {
        accountNumber
        currentBalance
        transactionList {
          amount
          transactionDate
        }
      }
    }
  }
}

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

Типизация и самодокументирование

GraphQL-схема — это одновременно и контракт API, и документация. IDE автоматически подсказывает доступные поля, а GraphQL Playground показывает полную схему.

GraphQL Playground
GraphQL Playground

Ubiquitous Language

GraphQL позволяет использовать единый язык для всей команды: разработчики бэкенда, фронтенда,бизнес-аналитики, а также эксперты от бизнеса говорят на одном языке предметной области. Это позволяет избежать путаницы и ошибок в понимании данных и существенно ускоряет процесс разработки.

Unit of Work

Концепция packet в DataSpace — это реализация паттерна Unit of Work. Все операции в packet выполняются атомарно: либо все успешно, либо откатываются все изменения. Это позволяет избежать ситуаций, когда часть изменений была выполнена, а часть нет, что может привести к непредсказуемым результатам.

Безопасность

Декларативные правила доступа намного проще поддерживать, чем разбросанные по коду проверки. Все правила — в одном месте, в файле graphql-permissions.json. А механизмы PathConditions и CheckSelects позволяют реализовать сложные правила доступа к данным.

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

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

Одна из «killer feature» DataSpace CE — автоматическая генерация frontend-кода. После описания модели предметной области можно запустить несколько команд и получить готовое React-приложение. Оно представляет собой CRUD-интерфейс, который далее можно постепенно кастомизировать под свои нужды.

GraphQL Codegen — это инструмент, который автоматически создает типы, хуки и вспомогательные функции для работы с GraphQL на клиентской или серверной стороне. Основная идея: вы один раз описываете GraphQL-схему и запросы (queries/mutations/fragments), а GraphQL Codegen автоматически генерирует для вас strongly-typed TypeScript- или JavaScript-код, который обеспечивает:

  • Типобезопасность. Все запросы строго типизированы — автодополнение и проверка типов работают на уровне IDE, что снижает количество ошибок во время исполнения.

  • Автоматическое обновление при изменении схемы. При любых изменениях в GraphQL-схеме, вы просто запускаете генератор — все типы и хуки обновляются автоматически.

  • Быструю интеграцию с библиотеками. Генератор поддерживает большинство популярных библиотек для React (например, Apollo Client, urql, Relay) и создает специальные «хуки» для работы с запросами.

  • Ускорение разработки. Вам не нужно вручную писать TypeScript-интерфейсы для данных или результатах запросов — всё сгенерировано автоматически и всегда остается в актуальном состоянии.

Пример: при добавлении нового поля в модель — например, «email» у клиента — после перегенерации вы тут же получаете доступ к этому полю во всех соответствующих типах, запросах и компонентах форм.

В данном банковском демо-приложении большая часть кода была сгенерирована автоматически с помощью GraphQL Codegen и лишь малая доработана вручную.

Соотношение генерируемого и кастомного кода
Соотношение генерируемого и кастомного кода

Этапы генерации

  1. get-schema — создание файла schema.graphql на основе модели данных.

  2. gqlgen – создание CRUD GraphQL-запросов на основе schema.graphql

  3. codegen — создание TypeScript-типов и React-хуков с помощью GraphQL Code Generator.

  4. formgen — генерация React-компонентов форм для создания и редактирования сущностей.

  5. permgen — создание базовой структуры файла permissions для frontend.

Результат

  • Полная типобезопасность — все GraphQL-типы автоматически превращаются в TypeScript-интерфейсы.

  • Готовые хукиuseCreateClient(), useSearchAccount(), useUpdateTransaction() и т. д.

  • Компоненты форм — формы с валидацией для всех сущностей.

  • Консистентность — изменили модель? Запустили генерацию заново — весь код обновился.

Заключение

Platform V DataSpace Community Edition — это инструмент, который действительно ускоряет разработку. Декларативный подход и автоматическая генерация избавляют от рутины, а гибкие механизмы безопасности позволяют реализовать сложные требования к доступу.

Надеемся, эта статья помогла вам понять, как работает Platform V DataSpace Community Edition и как его можно использовать для быстрого создания полнофункциональных приложений. Если у вас есть вопросы или вы уже попробовали платформу — делитесь опытом в комментариях! Спасибо за внимание!

Полезные ссылки

Ссылка на вебинар по Platform V DataSpace

Учебный курс DataSpace CE

DataSpace CE на GitVerse

https://www.keycloak.org/documentation

https://jwt.io/

https://www.typescriptlang.org/docs/

https://www.apollographql.com/docs/

https://graphql.org/

https://www.howtographql.com/

https://the-guild.dev/graphql/codegen