Проектування GraphQL-схеми для веб-застосунку

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Проектування GraphQL-схеми для веб-застосунку
Середня
~2-3 робочих дні
Часті питання

Наші компетенції:

Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Проектування GraphQL-схеми для веб-додатків

Гарна GraphQL-схема—це контракт між фронтендом і бекендом, який живе роками. Погано спроектована схема обростає костилями вже через три місяці: зламані n+1, поля з незрозумілою семантикою, type UserOrError замість нормальної обробки помилок. Проектування починається з питань використання, а не з маппінгу таблиць БД.

Принципи проектування

Схема орієнтована на продукт, не на сховище. REST-ендпоїнти часто повторюють структуру БД. В GraphQL—навпаки: спочатку визначаємо, що потребує UI, потім проектуємо типи.

Nodes і Edges через Relay-специфікацію. Якщо проект середній і більший, варто одразу закладати Relay-сумісну структуру—вона задає стандарт пагінації та глобальних ID.

Nullable за замовчуванням. Поле nullable краще, ніж non-null, який зламає весь запит при одному null-значенні. Non-null (!) тільки там, де ви готові гарантувати значення на рівні бізнес-логіки.

Базові типи

type Query {
  node(id: ID!): Node
  product(id: ID!): Product
  products(filter: ProductFilter, page: PaginationInput): ProductConnection!
  order(id: ID!): Order
  viewer: User                    # поточний авторизований користувач
}

type Mutation {
  createOrder(input: CreateOrderInput!): CreateOrderPayload!
  updateOrderStatus(input: UpdateOrderStatusInput!): UpdateOrderStatusPayload!
  addToCart(input: AddToCartInput!): AddToCartPayload!
}

type Subscription {
  orderStatusUpdated(orderId: ID!): OrderStatusEvent!
}

interface Node {
  id: ID!
}

Паттерн Payload для мутацій

Ніколи не повертайте з мутації голий тип об'єкту. Використовуйте payload-обгортку:

type CreateOrderPayload {
  order: Order              # null при помилці
  userErrors: [UserError!]! # завжди масив, порожній при успіху
}

type UserError {
  field: [String!]          # шлях до поля з помилкою, напр. ["items", "0", "quantity"]
  message: String!
  code: OrderErrorCode
}

enum OrderErrorCode {
  INSUFFICIENT_STOCK
  INVALID_ADDRESS
  PAYMENT_DECLINED
  PRODUCT_UNAVAILABLE
}

Різниця між userErrors та GraphQL-помилками (errors): userErrors—передбачувані бізнес-помилки, які клієнт повинен обробити. GraphQL errors—непередбачені ситуації (виключення, network errors).

Пагінація: Cursor-based

type ProductConnection {
  edges: [ProductEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type ProductEdge {
  node: Product!
  cursor: String!           # непрозорий base64 курсор
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

input PaginationInput {
  first: Int
  after: String
  last: Int
  before: String
}

Фільтрація та сортування

input ProductFilter {
  categoryIds: [ID!]
  priceMin: Decimal
  priceMax: Decimal
  inStock: Boolean
  search: String
  tags: [String!]
}

enum ProductSortField {
  PRICE
  CREATED_AT
  POPULARITY
  NAME
}

enum SortDirection {
  ASC
  DESC
}

input ProductSort {
  field: ProductSortField!
  direction: SortDirection!
}

Версіонування та застарівання

GraphQL не версіонується через URL (/v2/graphql). Замість цього—continuous evolution:

type Product {
  id: ID!
  name: String!

  # Застарілое поле—залишаємо для сумісності
  price: Decimal @deprecated(reason: "Use `pricing.basePrice` instead")

  pricing: ProductPricing!
  variants: [ProductVariant!]!
  categories: [Category!]!

  # Метадані для відлагодження
  _debug: ProductDebugInfo @deprecated(reason: "Debug only, remove before production")
}

type ProductPricing {
  basePrice: Decimal!
  salePrice: Decimal
  currency: CurrencyCode!
  isOnSale: Boolean!
  discountPercent: Int
}

Директиви

Кастомні директиви для cross-cutting concerns:

directive @auth(requires: Role = USER) on FIELD_DEFINITION
directive @rateLimit(max: Int!, window: String!) on FIELD_DEFINITION
directive @cacheControl(maxAge: Int, scope: CacheControlScope) on FIELD_DEFINITION | OBJECT

enum Role { ADMIN MANAGER USER GUEST }
enum CacheControlScope { PUBLIC PRIVATE }

type Order {
  id: ID!
  status: OrderStatus!
  items: [OrderItem!]!
  customer: User! @auth(requires: MANAGER)
  internalNotes: String @auth(requires: ADMIN)
}

type Query {
  products: ProductConnection! @cacheControl(maxAge: 300, scope: PUBLIC)
  dashboard: DashboardStats! @auth(requires: MANAGER) @rateLimit(max: 60, window: "1m")
}

Приклад повної схеми сутності

type Order implements Node {
  id: ID!
  number: String!                   # людині зрозумілий номер, напр. "ORD-2024-001234"
  status: OrderStatus!
  createdAt: DateTime!
  updatedAt: DateTime!
  completedAt: DateTime

  customer: User!
  shippingAddress: Address!
  billingAddress: Address

  items: [OrderItem!]!
  itemsCount: Int!

  subtotal: Money!
  shippingCost: Money!
  discount: Money!
  total: Money!

  payment: Payment
  shipment: Shipment
  timeline: [OrderTimelineEvent!]!  # історія змін статусу
}

scalar DateTime
scalar Decimal

type Money {
  amount: Decimal!
  currency: CurrencyCode!
  formatted: String!               # "1 234,56 ₽"—для прямого виводу
}

Розділення схеми по доменам

Для великих проектів розбивайте схему на файли за доменами і об'єднуйте при завантаженні:

schema/
  base.graphql         # Query, Mutation, Subscription, Node
  products.graphql
  orders.graphql
  users.graphql
  payments.graphql
  scalars.graphql      # DateTime, Decimal, Money, CurrencyCode
  directives.graphql

Терміни

Проектування схеми для додатку середнього масштабу (5–10 сутностей): 3–5 днів. З ревю, документацією полів і узгодженням з командою фронтенду: 1 тиждень.