GraphQL — это открытый язык запросов и управления данными для API.
Нам больше не нужно играть в догадки, как в случае с REST, поскольку этот язык строго типизирован. Перед выполнением запроса инструменты обеспечивают его синтаксическую верность и соответствие системе типов GraphQL.
Так как TypeScript — это типизированный язык, он идеально сочетается с GraphQL. Используя их вместе, можно обеспечить согласование типов в бэкенде и фронтенде.
Зачем нам вручную определять интерфейсы TS? При написании кода вручную велика вероятность появления ошибок, поэтому и был разработан graphql-codegen
.
Генератор кода GraphQL — это инструмент командной строки, способный генерировать типизацию TypeScript на основе схемы GraphQL. При разработке GraphQL-бэкенда возникает много случаев, когда мы пишем то, что уже описано в схеме, только в ином формате. Например, сигнатуры механизмов интерпретации, модели MongoDB, службы Angular и т.д.
graphql-code-generator.com
Если коротко, то это инструмент командной строки, который выполнит за нас всю трудоемкую работу. В этой статье мы познакомимся с ним поглубже и посмотрим, как его можно использовать для создания запроса с помощью Typescript, React и Apollo.
Настройка генератора кода GraphQL
Начнем с установки библиотеки GraphQL:
yarn add graphqlnpm install — save graphql
Далее нужно добавить cli
:
yarn add -D @graphql-codegen/clinpm install --save-dev @graphql-codegen/cli
Генератор кода считывает всю конфигурацию из файла codegen.yml
. Можно сгенерировать ее автоматически, воспользовавшись мастером установки. Запустим его:
yarn graphql-codegen initnpx graphql-codegen init
Для нашего примера выберем Typescript и Apollo.
После настройки останется только выполнить типичную команду установки:
npm install / yarn install
Чтобы не усложнять, мы будем потреблять демонстрационную конечную точку GraphQL. Можно найти много таких точек здесь.
После настройки codegen.yml
будет выглядеть так:
overwrite: true
schema: https://swapi-graphql.netlify.app/.netlify/functions/index
documents: 'src/**/*.gql'
generates:
src/generated/graphql.tsx:
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-react-apollo'
Параметр schema
сообщает инструменту о местонахождении конечной точки GraphQL, в качестве которой мы будем использовать Star Wars.
В этом сценарии мы применяем схему URL, но для ее определения также можно задействовать локальный файл .qql
:
schema: 'src/**/*.graphql'
Или указать файл с несколькими шаблонами:
// Несколько шаблонов
schema:
- 'src/app1/**/*.graphql'
- 'src/app2/**/*.graphql'
// ignores files
schema:
- 'src/**/*.graphql'
- '!src/app2/**/*.graphql'
Параметр documents
сообщает инструменту командной строки, откуда извлекать gql fragments/mutations/queries
.
Имейте в виду, что генератор кода является голым инструментом, настройка которого производится с помощью плагинов. Обратите внимание на раздел plugins
в codegen.yml
. Так настраивается ядро.
В этой статье мы сосредоточимся на React с Apollo и Typescript, но его также можно использовать и с любыми другими вендорами или средами. Можно даже выводить код в других языках программирования, например в Java, .Net и Kotlin.
Для автоматической генерации всех типов нужно выполнить:
yarn generate
Обратите внимание, что сейчас мы получим сбой, так как еще не создали файл qql
для парсинга.
Error: Unable to find any GraphQL type definitions for the following pointers: 'src/**/*.gql'
Создание запроса GraphQL
В этом примере им будет разбитый на страницы запрос для перечисления всех планет из Звездных войн.
Обратиться к конечной точке Star Wars можно здесь.
Вот как там будет выглядеть наш запрос:
{
allPlanets (first:5, after: "YXJyYXljb25uZWN0aW9uOjQ=") {
planets {
id,
name,
diameter,
population,
gravity
},
pageInfo {
endCursor
}
}
}
Создадим на основе этого запроса файл planets.qql
:
query allPlanets($after: String) {
allPlanets(first: 5, after: $after) {
planets {
id
name
diameter
population
gravity
}
pageInfo {
endCursor
}
}
}
Обратите внимание, как мы объявляем запрос с приставкой query
. Мы используем $
для объявления переменной $after
и указываем для нее тип String
. Он не является обязательным и не делает ее однозначно String
, так как на начальной странице это значение будет пусто.
Теперь можно сгенерировать первый запрос:
npm generate / yarn generate
На этот раз вместо сбоя нас ожидал успех, так как теперь есть запрос для генерации в src/queries/planets.gql
. Можете проверить сгенерированный файл src/generated/graphql.tsx
и начать использовать его в проекте.
Подстройка генератора
Мы уже сгенерировали запрос с базовой конфигурацией. Теперь посмотрим, как можно улучшить генерируемый код более углубленной настройкой.
В этом инструменте командной строки есть набор предустановленных настроек, с которыми можно ознакомиться здесь. Вот некоторые из них:
config:
withHooks: true
withComponent: false
withHOC: false
Поскольку подход Component
и HOC
устарел, вам будет рекомендована версия hook
. Если вы привыкли следовать рекомендациям, то все будет в порядке.
Есть несколько достойных внимания вариантов конфигурации. Вот три наиболее, на мой взгляд, актуальных:
1. Функциональное программирование
Сгенерированный код будет придерживаться стиля функционального программирования:
config:
constEnums: true
immutableTypes: true
Это защитит вас от возможной мутации объектов результата запроса и необходимости использовать стиль кода, не допускающий изменения. В TypeScript очень удобно предотвращать мутации с помощью модификатора readonly
.
2. Приставка
В некоторых командах принято добавлять приставки, например, к перечислениям и интерфейсам. В инструменте командной строки для этого есть ряд опций:
config:
typesPrefix: I
typesSuffix: I
enumPrefix: false
3. Стиль кода
Некоторые группы разработчиков не любят необязательные поля или используют их особым образом. Этот инструмент позволяет настроить поведение полей optional
:
config:
avoidOptionals:
field: true
inputValue: true
object: true
defaultValue: true
В документации по этой части есть и много другой полезной информации.
После настройки конфигурации итоговый файл codegen.yml
будет выглядеть так:
overwrite: true
schema: https://swapi-graphql.netlify.app/.netlify/functions/index
documents: 'src/**/*.gql'
generates:
src/generated/graphql.tsx:
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-react-apollo'
config:
constEnums: true
immutableTypes: true
Настройка клиента Apollo
Для получения возможности использовать и выполнять сгенерированный запрос, нужно настроить Apollo Client.
yarn add @apollo/client graphqlnpm install @apollo/client graphql
@apollo/client
: этот пакет содержит практически все необходимое для настройки Apollo Client. Он включает внутренний кэш памяти, управление локальным состоянием, обработку ошибок и уровень представления на основе React.
graphql
: этот пакет предоставляет логику для парсинга запросов GraphQL.
apollographql.com
Обратите внимание, что в codegen.yml
мы используем typescript-react-apollo
, который уже генерирует запросы Apollo.
Далее настроим Apollo Client и Provider:
import '../styles/globals.css'
import { ApolloProvider, ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://swapi-graphql.netlify.app/.netlify/functions/index',
cache: new InMemoryCache()
});
function MyApp({ Component, pageProps }) {
return (
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
)
}
export default MyApp
Вот теперь можно применять сгенерированный запрос.
Использование сгенерированного GraphQL-запроса
При использовании интеграции Apollo генератор будет предоставлять автоматические или ручные запросы:
// активируется автоматически при рендеринге компонентов
const { data, loading} = useAllPlanetsQuery();
// активируется вручную через вызов метода "getPlanets"
const [getPlanets, { loading, data }] = useAllPlanetsLazyQuery();
В зависимости от ситуации вы будете применять тот или иной вариант — тип данных у них будет одинаков. Давайте взглянем на возвращаемый тип для PlanetQuery
.
export type AllPlanetsQuery = (
{ readonly __typename?: 'Root' }
& { readonly allPlanets?: Maybe<(
{ readonly __typename?: 'PlanetsConnection' }
& { readonly planets?: Maybe<ReadonlyArray<Maybe<(
{ readonly __typename?: 'Planet' }
& Pick<Planet, 'id' | 'name' | 'diameter' | 'population' | 'gravity'>
)>>>, readonly pageInfo: (
{ readonly __typename?: 'PageInfo' }
& Pick<PageInfo, 'endCursor'>
) }
)> }
)
А теперь посмотрим все это в действии:
import '../styles/globals.css'
import { useAllPlanetsQuery } from './generated/graphql';
import { ApolloProvider, ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://swapi-graphql.netlify.app/.netlify/functions/index',
cache: new InMemoryCache()
});
function ListAllPlanets () {
const { data, loading} = useAllPlanetsQuery();
return loading ? (
<div>
loading...
</div>
) : (
<ul>
{data.allPlanets.planets.map((item) => {
return (
<li key={item.id}>
{item.name} - {item.population ? `${item.population} habitants` : 'N/A'}
</li>);
})}
</ul>
)
}
function MyApp() {
return (
<ApolloProvider client={client}>
<ListAllPlanets />
</ApolloProvider>
)
}
export default MyApp
Так как все генерируется автоматически, можно не бояться потери синхронизации между бэкендом и фронтендом.
Чтобы извлечь из сгенерированного файла обобщенные типы, выполните следующее:
import type { Planet } from ‘../generated/graphql’;
А этой инструкцией можно получить тип конкретного запроса и извлечь пользовательские типы:
import type { AllPlanetsQuery } from '../generated/graphql';
Заключение
Мы рассмотрели генерацию простого GraphQL-запроса, но этот инструмент способен и на многое другое. Он предлагает такие возможности, как Fragments
, Mutations
и многие другие плагины.
Graphql Code Generator
выполняет всю тяжелую работу за нас. Его очень легко настроить и адаптировать под ваши потребности. Этот инструмент уменьшает количество ручной работы, в ходе которой нередко возникают ошибки.
Удачным решением будет выполнять yarn generate
в непрерывной интеграции. Это гарантирует стабильность и целостность в процессе разработки.
Читайте также:
- TypeScript. Что, зачем и как?
- Краткий обзор нововведений TypeScript 4.1
- Сравнение архитектурных стилей API: SOAP vs REST vs GraphQL vs RPC
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Jose Granja: Mixing Typescript and GraphQL Code Generator