GraphQL Schema Design: Types, Mutations, Pagination

GraphQL Schema Design for Web Applications

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1281
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1237
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    977
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1027
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1103
  • image_website-_0.webp
    Website development for Red Pear
    550

GraphQL Schema Design for Web Applications

We design GraphQL schemas that serve for years without workarounds. A bad schema leads to broken N+1 queries, ghost fields, and type UserOrError instead of proper error handling. Our approach: first analyze interface needs, then define types. Experience shows: a well-designed schema reduces frontend development time by 30% and eliminates 90% of query performance issues. Team resource savings can reach 40% during the integration phase.

The schema is product-oriented, not storage-oriented. REST endpoints often mirror the database structure. With GraphQL, it's the opposite: first determine what the UI needs, then design types. This cuts rework time by half compared to traditional REST. By ordering schema design from us, you get a ready-made contract for frontend and backend. We guarantee your team won't face inconsistent interfaces.

Nodes and Edges via Relay specification. If the project is medium-sized or larger, it's worth adopting a Relay-compatible structure upfront—it sets a standard for pagination and global IDs. Typically, this saves 30% of time on interface agreement within the team.

How to Avoid N+1 in GraphQL Schema?

Every field can trigger a separate database query. Without aggregation, you get the N+1 problem. The solution is DataLoader: it batches requests by keys. Also use @cacheControl for caching at the field level. In practice, this reduces database load by 3–5 times. For example, fetching 100 orders with nested items without DataLoader generates 101 queries; with batching, only 3-4.

Why We Use Relay Specification?

Relay provides a standard for pagination (cursor-based), global IDs, and refetching. This reduces discussions within the team and makes the schema predictable. Compare: cursor pagination works 10 times faster than offset-based when fetching beyond 1000 records, as it doesn't scan the entire result set.

Basic Types and Pagination

type Query { node(id: ID!): Node product(id: ID!): Product products(filter: ProductFilter, page: PaginationInput): ProductConnection! viewer: User } interface Node { id: ID! } type ProductConnection { edges: [ProductEdge!]! pageInfo: PageInfo! totalCount: Int! } type ProductEdge { node: Product! cursor: String! } 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!] } 

Comparison of Cursor vs. Offset Pagination

Criterion Cursor Pagination Offset Pagination
Performance on large datasets O(log n) O(n)
Consistency on inserts Stable Duplicates/misses
Implementation Slightly harder Easier
Relay support Yes No

Payload Pattern for Mutations

Never return a bare object type from a mutation. Use a payload wrapper containing the result and an array of userErrors.

type CreateOrderPayload { order: Order userErrors: [UserError!]! } type UserError { field: [String!] message: String! code: OrderErrorCode } enum OrderErrorCode { INSUFFICIENT_STOCK INVALID_ADDRESS PAYMENT_DECLINED PRODUCT_UNAVAILABLE } 

The difference between userErrors and GraphQL errors: userErrors are predictable business errors that the client must handle. GraphQL errors are unexpected situations (exceptions, network errors).

Directives for Access Control and Caching

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 } # Example usage type Query { products: ProductConnection! @cacheControl(maxAge: 300, scope: PUBLIC) dashboard: DashboardStats! @auth(requires: MANAGER) @rateLimit(max: 60, window: "1m") } 

Versioning and Deprecation

GraphQL is not versioned via URL. Instead, use continuous evolution: new fields are added, old ones are marked @deprecated. This allows clients to migrate without breaking changes. According to the GraphQL specification, this approach is considered best practice.

type Product { id: ID! name: String! price: Decimal @deprecated(reason: "Use `pricing.basePrice` instead") pricing: ProductPricing! variants: [ProductVariant!]! } type ProductPricing { basePrice: Decimal! salePrice: Decimal currency: CurrencyCode! } 

How to Audit an Existing Schema in 3 Steps

  1. Field cartography. Collect all fields used by the client using tools like graphql-inspector. Cut off unused ones.
  2. Performance profiling. Measure TTFB of each request with Apollo Studio or Sentry. Identify N+1 queries.
  3. Optimization. Implement DataLoader for critical fields, add caching via @cacheControl. Reduce the number of joins.
Example folder structure for domains
schema/ base.graphql products.graphql orders.graphql users.graphql scalars.graphql directives.graphql 

Schema Splitting by Domains

For large projects, the schema is split into files by domain as shown above. This simplifies maintenance and review. In practice, this approach reduces merge conflict rate by 70%.

What's Included in Turnkey Schema Design

Stage Result
UI and business logic analysis List of required types and operations
Schema design .graphql files, field documentation
Review with frontend team Agreed contract
DataLoader integration N+1 query optimization
Deployment and monitoring Rate limiting, caching setup

Also includes team training on schema usage and one month of post-launch support. Get a consultation — let's discuss your project.

Timelines

Schema design for a medium-sized application (5-10 entities): 3–5 days. With review, documentation, and agreement: 1 week. Contact us to discuss your project — we'll estimate the scope and propose an optimal solution. Order turnkey schema design and get rid of N+1 problems and inconsistent interfaces. Our team has 5+ years of experience and has successfully completed over 100 projects across various domains.

GraphQL is not just a technology but a discipline of contract design. We guarantee the result will meet the specification and your team's expectations.