TLDR; Система типов GraphQL и удаленное выполнение процедур позволяют нам легко контролировать типы входящих данных. В этом посте мы увидим, как настроить простой GraphQL-серверgraphql-js) и запросить некоторые данные с помощью приложения React. Чтобы воспользоваться преимуществом внешнего интерфейса типизации GraphQL, мы будем использовать graphql-code-generator для отражения типов из внутренней схемы во внешний интерфейс. При этом у нас будет более уверенный опыт работы с данными, которыми манипулирует внешнее приложение.

Репозиторий Github доступен здесь: https://github.com/othke/graphql-typed-app

Почему типы GraphQL являются преимуществом?

Одной из величайших вещей, которые приносит GraphQL, является система типов и удаленное выполнение процедур. Эта гарантия того, что для данного запроса у нас всегда будет ожидаемый тип данных (или список ошибок, управляемый GraphQL).

Для тех, кто не знает, что такое GraphQL (это не база данных 😊), взгляните на этот пост в блоге от одного из создателей. А если вам интересно узнать, где, почему и как она появляется, посмотрите это видео. Вкратце GraphQL даст вам:

  • Язык манипулирования запросами для выполнения удаленной процедуры
  • Сильная система типов
  • Упрощенная связь с вашим бэкэндом (без избыточной выборки данных)

Можем ли мы сделать то же самое с REST?

API-интерфейсы REST великолепны, и в некоторых ситуациях они обеспечивают хорошее техническое решение. Цель GraphQL не в том, чтобы заменить REST API, а в том, чтобы заполнить пробел. Одна из повторяющихся проблем с REST API заключается в том, что он не дает нам всех инструментов для обеспечения того, чтобы полученные вами данные соответствовали вашим ожиданиям. Даже с OpenAPI и хорошей документацией REST API не имеют в своей ДНК проверки типов.

Давайте рассмотрим быстрый пример с конечной точкой /authors, которая должна возвращать данные об авторе и список его книг. Вот результат REST API.

Возможно, вы заметили, что второй объект author имеет значение null для свойства books. Даже если в документации (OpenAPI и т. д.) указано, что книги должны быть списком, ничто не выдаст ошибку, если эта спецификация не соблюдается.
Как этого избежать? Вы можете использовать такие инструменты, как Joi, для проверки ваших данных перед их отправкой во внешнее приложение, но это не поведение по умолчанию в REST API.

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

Всегда во внешнем приложении, как узнать тип данных, поступающих из внутреннего приложения? Если вы используете TypeScript, невозможно определить тип данных, поступающих из HTTP-запроса. Вам придется определить типы вручную во внешнем приложении на основе спецификации REST API. Возможно, у вас может быть общая библиотека для этого, но вы должны синхронизировать обе стороны!

Избегайте изобретения велосипеда и используйте инструменты, которые генерируют типы из спецификации OpenAPI. Эти инструменты хороши, потому что они помогут вам улучшить работу (автозаполнение) при работе с данными, поступающими из серверного приложения. Но опять же, TypeScript не может определить неправильный тип входящих данных.





Если вы хотите быть уверены, что входящие данные соответствуют правильному типу, вам придется использовать инструмент проверки, такой как уже упомянутый Joi или AJV. С помощью этих инструментов вы можете создать функцию проверки, которая будет выполняться, как только вы получите входящие данные из серверного приложения.

Здесь мы хотели продемонстрировать, что можем использовать API-интерфейсы REST и быть уверенными в данных, поступающих из серверного приложения, но это подразумевало определенные меры предосторожности.

Как GraphQL может гарантировать достоверность входящих данных?

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

Мы не собираемся глубоко погружаться 🤿 в исходный код реализации graphql-js, но быстрый взгляд на функцию GraphQL buildResponse показывает нам, что ошибки обрабатываются по умолчанию и возвращаются. Таким образом, любой недопустимый тип данных не будет отправлен во внешнее приложение, вместо этого вы получите свойство ошибки, содержащее недопустимые поля, что лучше, чем неожиданное значение.

Итак, теперь мы можем доверять данным, поступающим из серверной части. Но как мы можем узнать во внешнем приложении, какие типы входящих данных. Как и в случае с REST API, приложение TypeScript не может угадать типы данных, поступающих из HTTP-запроса. Мы должны использовать те же инструменты, что и для REST API, которые могут генерировать типы из бэкэнда. К счастью для нас, GraphQL поставляется с механизмом самоанализа, который помогает создавать типы из схемы. Инструмент, который мы собираемся использовать, называется graphql-code-generator.

Если вам интересны все те внутренние инструменты, которые предоставляет GraphQL, найдите время, чтобы прочитать эту статью.

Создание приложения GraphQL

Чтобы проиллюстрировать это, мы создадим сервер GraphQL, который возвращает команды НХЛ. Для простоты мы не будем использовать базу данных, данные будут в статическом файле, содержащем список команд. С помощью этого сервера мы могли запрашивать дивизионы НХЛ и команды НХЛ.

Вот index.js из серверного приложения. Есть комментарий, объясняющий различные шаги, но более важно то, что GraphQL экономит нам много работы, самостоятельно выполняя контроль над типом 👍

Поиграться с бэкендом можно здесь

Создайте фронтенд-приложение React

Теперь бэкэнд готов и типизирован, мы хотим, чтобы то же самое было и во фронтенд-приложении. Мы создадим приложение React с помощью create-react-app и шаблона ypeScript.

С нашей шаблонной настройкой мы хотим запросить нашу конечную точку GraphQL и знать, какими типами мы собираемся манипулировать. Для этого воспользуемся graphql-code-generator. Вам нужно будет установить основной пакет и некоторые плагины в зависимости от того, какое приложение вы используете.

# Install the cli
npm install -D @graphql-codegen/cli
# Install the plugin for build typescript types
npm install -D @graphql-codegen/typescript
# Install the plugin to make types available with the react application
npm install -D @graphql-codegen/typescript-react-apollo

Затем вы можете использовать cli для создания codegen.yml в корне вашего проекта или создать его вручную.

# codegen.yml at the root of the project
schema: http://localhost:4000/graphql
generates:
  src/generated/graphql.tsx:
    plugins:
      - typescript
      - typescript-react-apollo

Затем вы можете добавить следующий скрипт в свой package.json и запустить его, чтобы сгенерировать ваши типы на основе самоанализа вашей схемы GraphQL.

"generate": "graphql-codegen --config codegen.yml"

Выходной файл содержит все типы TypeScript, отражающие схему GraphQL. Вы, вероятно, заметите тип Maybe, который здесь для необязательных типов GraphQL. Если вы хотите избежать этого, вы должны обновить определение схемы GraphQL с помощью GraphQLNonNull для всех полей, которые не должны быть необязательными.

В приложении React мы будем использовать клиент Apollo для выполнения запроса GraphQL, но вы можете использовать то, что хотите. В следующем примере мы собираемся запросить поле teams, которое должно вернуть список команд (возможно).

Мы получаем ожидаемый тип из этой строки, выбирая в типе Query свойство teams.

type GetTeams = Pick<Query, "teams">;

Затем мы указываем этот тип универсальному useQuery‹T› от клиента Apollo. Выполнение этого TypeScript будет знать ожидаемый тип, полученный из этого запроса GraphQL. Это вводит строгую проверку типов в ваш компонент React и позволяет избежать некоторых потенциальных ошибок, вызванных неопределенным или неожиданным входящим значением.

const { data } = useQuery<GetTeams>(GET_TEAMS, {
  variables: { division, filter },
});

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

Поиграться с интерфейсом можно здесь

Вывод

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