GraphQL Schema – это описание ваших типов данных на сервере, связей между ними и логики получения этих самых данных.
Еще раз по пунктам:
resolve-методы)описание типов)resolve-методы и описание типов хитро перемешиваете и получаете вашу GraphQL Schema.Так как же данные? В каком формате и какой базе данных они будут храниться? В любой! Это неважно, когда у вас есть вами написанные resolve-методы, в которых вы напишете куда и как сходить за данными и обработать перед тем как отдать клиенту.
GraphQL задает канву, формат того как вы описываете доступ к своим данным. Это дело описывается через GraphQL-спецификацию. Которую ребята из Фейсбука очень кропотливо прорабатывали и в 2017 опубликовали под OWFa 1.0 соглашением (грубо говоря MIT-лицензией).
Так, хорошо. А какой язык программирования мне нужно использовать? Любой! Спецификацию уже реализовали на большинстве языков программирования.
Работает с любыми базами данных, на любом языке программирования - звучит хорошо!
Чтобы запустить свой GraphQL-сервер, первым делом вам необходимо объявить схему GraphQLSchema. Схема содержит в себе описания всех типов, полей и методов получения данных. Все типы в рамках GraphQL-схемы должны иметь уникальные имена. Не должно быть двух разных типов с одним именем.
GraphQL-схема это точка входа, это корень всего вашего API. Правда у этого корня, три “головы”:
query - для операций получения данныхmutation - для операций изменения данныхsubscription - для подписки на событияВ GraphQLSchema обязательным параметром является только query, без него схема просто не запустится. Инициализация схемы выглядит следующим образом:
import { GraphQLSchema, GraphQLObjectType, graphql } from 'graphql';
const schema = new GraphQLSchema({
query: new GraphQLObjectType({ name: 'Query', fields: { getUserById, findManyUsers } }),
mutation: new GraphQLObjectType({ name: 'Mutation', fields: { createUser, removeLastUser } }),
subscriptions: new GraphQLObjectType({ name: 'Subscription', fields: ... }),
// ... и ряд других настроек
});
Особо хочется остановиться на состоянии операций, в GraphQL их два:
stateless - все операции в query и mutation должны быть без состояния, т.е. если у вас в кластере много машин обслуживающих запросы клиентов, то неважно на какой из серверов прилетел запрос. Его может выполнить любая нода.statefull - должно быть у операций subscription, т.к. требуется установка постоянного подключения с клиентом, хранение данных о подписках, механизм переподключения и восстановления данных о существующих подписках. Пакет graphql никак не помогает в решении этих админских проблем.Также важно рассмотреть различия в выполнении операции для query и mutation. Если все операции (поля) в query вызываются параллельно, то в mutation они вызываются последовательно. Например:
query {
getUserById { ... }
findManyUsers { ... }
# `getUserById` и `findManyUsers` будут запрошены параллельно
}
mutation {
# сперва выполнится операция создания пользователя
createUser { ... }
# а после того как пользователь создан, выполнится операция на удаление
removeLastUser { ... }
}
Предположим у нас объявлена следующая схема:
import { GraphQLSchema, GraphQLObjectType, GraphQLString } from 'graphql';
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
hello: {
type: GraphQLString,
resolve: () => 'world',
}
}
})
});
export default schema;
После инициализации GraphQL-схемы вы можете выполнять GraphQL-запросы. Делается это достаточно просто:
import { graphql } from 'graphql';
import { schema } from './your-schema';
const query = '{ hello }';
const result = await graphql(schema, query); // returns: { data: { hello: "world" } }
Для выполнения запроса, необходимо вызвать метод graphql() из пакета graphql, который решает следующие задачи:
queryschema (если запрошены несуществующие поля, или переданы неверные аргументы, то будет возвращена ошибка)query, вызывая их resolve-методы. Подробнее об Object-типе можно почитать в разделе о Типах.null, то будет возвращена ошибка)GraphQL по натуре строго типизированный, поэтому во всех запросах проверяются входящие переменные и аргументы, формат возвращаемого ответа на соответствие типов, которые объявлены в GraphQL-схеме. Если что-то некорректно запросили или вернули, то будет возвращена ошибка.
Пакет graphql ничего не знает о сети, правах доступа, не слушает никакой порт. Всё это дело реализуется на другом уровне абстракции - GraphQL сервере. В разделе GraphQL-сервер вы найдете больше подробностей о том, что должен делать сервер, и какие популярные пакеты сейчас доступны в npm. Важно знать, что все сервера используют под капотом метод graphql() из пакета graphql.
В спецификации GraphQL для описания типов используется Schema Definition Language (SDL). Это простой, выразительный и интуитивно понятный формат описания типов, который не зависит ни от какого языка программирования. Не путать с Query Language — т.к. это другой формат для написания GraphQL-запросов, а не схемы.
Простое описание output-типа с помощью SDL выглядит следующим образом:
type Post {
id: Int!
title: String!
publishedAt: DateTime!
comments(limit: Int = 10): [Comment]
}
Тип имеет имя Post и состоит из четырёх полей:
id — поле которое non-null (восклицательный знак) и имеет тип целого числаtitle — не пустое поле с типом строкиpublishedAt — не пустое поле с кастомным типом DateTimecomments — поле которое возвращает массив (квадратные скобочки) комментариев (Comment). Также этому полю можно передать аргумент с именем limit, который является числом и по умолчанию имеет значение 10.input Credentials {
login: String!
password: String!
}
enum Direction {
NORTH
EAST
SOUTH
WEST
}
interface NamedEntity {
name: String
}
interface ValuedEntity {
value: Int
}
type Business implements NamedEntity & ValuedEntity {
name: String
value: Int
employeeCount: Int
}
union SearchResult = Photo | Person
type Person {
name: String
age: Int
}
type Photo {
height: Int
width: Int
}
type SearchQuery {
firstSearchResult: SearchResult
}
scalar Time
scalar Url
[] — массив! — не пустое/обязательное поле| SDL | Значение |
|---|---|
[Int!] |
null или массив чисел |
[Int]! |
массив чисел или null, пустой массив |
[Int!]! |
массив чисел или пустой массив |
[[Int]] |
целочисленный массив массивов |
directive @example on FIELD_DEFINITION | ARGUMENT_DEFINITION
type SomeType {
field(arg: Int @example): String @example
}
В SDL у пакета graphql есть встроенная директива @deprecated для пометки полей как устаревшее и не рекомендуемое к использованию:
directive @deprecated(
reason: String = "No longer supported"
) on FIELD_DEFINITION | ENUM_VALUE
Пример использования:
type ExampleType {
newField: String
oldField: String @deprecated(reason: "Use `newField`.")
}
Когда у вас есть на сервере GraphQL-схема, то вы можете предоставить клиенту описание типов, без resolve-методов. Т.е. клиент будет знать что может вернуть сервер, но внутренняя реализация того, как это происходит будет от него скрыта. Описание типов GraphQL-схемы называется интроспекцией и выглядит следующим образом в формате SDL:
# The main root type of your Schema
type Query {
book(id: Int): Book
author(name: String): Author
}
# Author model
type Author {
id: Int!
name: String!
}
# Description for Book model
type Book {
id: Int!
name: String!
authors: [Author]
}
Пример интроспекции побольше в формате SDL на 130 типов и она же в формате JSON. А можно посмотреть интроспекцию всех сервисов AWS Cloud размером 1.8Mb, содержащию описание для более чем 10000 типов.
Самому клиенту интроспекция может и не нужна, но вот для инструментария будет очень полезна:
Самый простой способ получить интроспекцию в формате JSON, это запросить ее у уже запущенного GraphQL-сервиса (если на сервере по причине безопасности её не запретили). Например GraphiQL и GraphQL Playground запрашивают её по http отправляя следующий GraphQL-запрос.
В JS сгенерировать файл с интроспекцией схемы можно с помощью следующих скриптов:
import fs from 'fs';
import { printSchema } from 'graphql';
import schema from './your-schema';
fs.writeFileSync('./schema.graphql', printSchema(schema));
import fs from 'fs';
import { getIntrospectionQuery } from 'graphql';
import schema from './your-schema';
async function prepareJsonFile() {
const result = await graphql(schema, getIntrospectionQuery());
fs.writeFileSync('./schema.json', JSON.stringify(result, null, 2));
}
prepareJsonFile();
.graphqlconfig и запускаете в терминале graphql get-schema --watchschemaPath, а он уже под капотом следит за этим файлом и всеми его зависимостями. И в случае изменений автоматически генерирует json и graphql файлы. Этот плагин полезен для изоморфных приложений, или как минимум тем, у кого серверные скрипты собираются через Webpack.get-graphql-schema ENDPOINT_URL > schema.graphql и на выходе получаете схему