Перейти к содержимому

4. Типы GraphQL

GraphQL Types

GraphQL — строго типизированная система. Каждый кусок данных имеет тип. Разберём все виды типов.

Типы GraphQL
├── Скалярные (Scalar) — листовые узлы
│ ├── String, Int, Float, Boolean, ID
│ └── Кастомные: Date, JSON, Upload...
├── Объектные (Object) — основа схемы
├── Input — только для аргументов
├── Enum — перечисления
├── Interface — абстракции
├── Union — объединения
└── Модификаторы: ! (non-null), [] (list)

Встроенных скаляров не хватает? Создай свой:

# В схеме:
scalar Date
scalar JSON
scalar Upload
scalar Email
type User {
id: ID!
email: Email!
birthDate: Date
metadata: JSON
}

Реализация на сервере (Apollo):

import { GraphQLScalarType, Kind } from 'graphql';
const DateScalar = new GraphQLScalarType({
name: 'Date',
description: 'Дата в формате ISO 8601',
// Из GraphQL значения в JS
parseValue(value) {
return new Date(value);
},
// Из JS значения в JSON (ответ клиенту)
serialize(value) {
return value instanceof Date
? value.toISOString()
: new Date(value).toISOString();
},
// Для inline значений в запросе
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
return new Date(ast.value);
}
return null;
},
});
// Добавить в resolvers:
const resolvers = {
Date: DateScalar,
// ...остальные resolvers
};

Или использовать готовую библиотеку:

Окно терминала
npm install graphql-scalars
import { DateTimeResolver, EmailAddressResolver } from 'graphql-scalars';
const resolvers = {
DateTime: DateTimeResolver,
EmailAddress: EmailAddressResolver,
};

Самые распространённые — описывают сущности с полями:

type Product {
id: ID!
name: String!
price: Float!
inStock: Boolean!
category: Category! # Связь с другим типом
variants: [Variant!]! # Массив объектов
}
type Category {
id: ID!
name: String!
products: [Product!]!
}

Интерфейс — абстрактный тип с набором полей, которые должны реализовать все типы:

interface Node {
id: ID!
}
interface Timestamped {
createdAt: String!
updatedAt: String!
}
type User implements Node & Timestamped {
id: ID!
createdAt: String!
updatedAt: String!
name: String!
email: String!
}
type Post implements Node & Timestamped {
id: ID!
createdAt: String!
updatedAt: String!
title: String!
body: String!
}
query {
node(id: "123") {
id
# Специфичные поля через inline fragments:
... on User {
name
email
}
... on Post {
title
}
}
}
const resolvers = {
Node: {
// GraphQL нужно знать, какой конкретный тип
__resolveType(obj) {
if (obj.email) return 'User';
if (obj.title) return 'Post';
return null;
},
},
};

Union объединяет несколько типов — но без общих полей:

type Cat {
name: String!
meows: Boolean!
}
type Dog {
name: String!
barks: Boolean!
}
union Pet = Cat | Dog
type Query {
pet(id: ID!): Pet
allPets: [Pet!]!
}
query {
allPets {
# Для Union нужны inline fragments на ВСЕ поля:
... on Cat {
name
meows
}
... on Dog {
name
barks
}
}
}
union SearchResult = User | Post | Product | Category
type Query {
search(query: String!): [SearchResult!]!
}
query Search($q: String!) {
search(query: $q) {
... on User {
name
email
}
... on Post {
title
excerpt
}
... on Product {
name
price
}
... on Category {
name
}
}
}
enum Direction {
NORTH
SOUTH
EAST
WEST
}
enum PaymentStatus {
PENDING
COMPLETED
FAILED
REFUNDED
}
enum SortOrder {
ASC
DESC
}
type Query {
products(sortBy: String, order: SortOrder = DESC): [Product!]!
}

На сервере enum приходит как строка:

const resolvers = {
Query: {
products: (_, { sortBy, order }) => {
// order будет строкой: 'ASC' или 'DESC'
return db.products.findAll({ order: [[sortBy, order]] });
},
},
};

Специальные типы ТОЛЬКО для входных данных:

input CreateProductInput {
name: String!
price: Float!
categoryId: ID!
description: String
inStock: Boolean = true
}
input ProductFilters {
minPrice: Float
maxPrice: Float
inStock: Boolean
categoryId: ID
}
input PaginationInput {
limit: Int = 20
offset: Int = 0
}
type Mutation {
createProduct(input: CreateProductInput!): Product!
}
type Query {
products(
filters: ProductFilters
pagination: PaginationInput
): [Product!]!
}

Важно: input типы:

  • НЕ могут содержать поля других объектных типов (только input или скаляры)
  • НЕ могут использоваться как возвращаемые значения
# Скаляры
scalar DateTime
scalar Money
# Перечисления
enum OrderStatus {
PENDING
CONFIRMED
PROCESSING
SHIPPED
DELIVERED
CANCELLED
REFUNDED
}
enum PaymentMethod {
CARD
PAYPAL
CRYPTO
}
# Интерфейсы
interface Node {
id: ID!
}
interface Auditable {
createdAt: DateTime!
updatedAt: DateTime!
}
# Объекты
type User implements Node & Auditable {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
name: String!
email: String!
orders: [Order!]!
}
type Product implements Node & Auditable {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
name: String!
price: Money!
category: Category!
inStock: Boolean!
}
type Category implements Node {
id: ID!
name: String!
products: [Product!]!
}
type OrderItem {
product: Product!
quantity: Int!
price: Money!
}
type Order implements Node & Auditable {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
status: OrderStatus!
items: [OrderItem!]!
total: Money!
customer: User!
}
# Union для поиска
union SearchResult = Product | Category | User
# Input типы
input CreateOrderInput {
items: [OrderItemInput!]!
paymentMethod: PaymentMethod!
}
input OrderItemInput {
productId: ID!
quantity: Int!
}
  1. Создай interface Searchable с полями id и searchText
  2. Реализуй его в типах Book, Author, Publisher
  3. Создай union SearchResult = Book | Author | Publisher
  4. Добавь кастомный скаляр ISBN для книг
  5. Создай input типы для создания и фильтрации книг с пагинацией
  • Scalar — атомарные значения (String, Int, Float, Boolean, ID + кастомные)
  • Object — основные сущности с полями
  • Interface — абстрактный тип с обязательными полями
  • Union — объединение типов без общих полей
  • Enum — перечисления
  • Input — только для аргументов мутаций/запросов

Следующий урокQueries →