Overview

GraphQL schema design is the foundation of any GraphQL API. A well-designed schema provides a clear contract between client and server, enables efficient data fetching, and supports future evolution.

Schema Definition Language (SDL)

Basic Schema Structure

type Query {
  # Entry points for data fetching
}
 
type Mutation {
  # Entry points for data modification
}
 
type Subscription {
  # Entry points for real-time data
}
 
# Custom types
type User {
  # Fields and relationships
}

Type System

Scalar Types

scalar DateTime
scalar Email
scalar URL
scalar JSON
 
type User {
  id: ID!
  email: Email!
  createdAt: DateTime!
  metadata: JSON
}

Object Types

type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
  profile: Profile
}
 
type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  publishedAt: DateTime!
}

Interface Types

interface Node {
  id: ID!
}
 
interface Commentable {
  comments: [Comment!]!
}
 
type Post implements Node & Commentable {
  id: ID!
  title: String!
  content: String!
  comments: [Comment!]!
}
 
type Video implements Node & Commentable {
  id: ID!
  title: String!
  url: URL!
  comments: [Comment!]!
}

Union Types

union SearchResult = User | Post | Comment
 
type Query {
  search(query: String!): [SearchResult!]!
}

Enum Types

enum UserRole {
  ADMIN
  MODERATOR
  USER
}
 
enum PostStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}
 
type User {
  role: UserRole!
}
 
type Post {
  status: PostStatus!
}

Input Types

input CreateUserInput {
  name: String!
  email: String!
  password: String!
}
 
input UpdateUserInput {
  name: String
  email: String
}
 
type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
}

Schema Design Principles

Start with Requirements

  • Domain Analysis: Understand business domain
  • Client Needs: What data do clients need?
  • Use Cases: How will the API be used?

Design for Clients

  • Client-Centric: Design from client perspective
  • Data Requirements: What fields are needed together?
  • Performance: Consider N+1 query problems

Schema Evolution

  • Backward Compatibility: Don’t break existing clients
  • Deprecation: Use @deprecated directive
  • Versioning: Schema versioning strategies

Field Design

Naming Conventions

# Good: camelCase for fields
type User {
  firstName: String!
  lastName: String!
  emailAddress: String!
  createdAt: DateTime!
}
 
# Avoid: abbreviations
type User {
  fname: String!  # Bad
  lname: String!  # Bad
  email: String!  # OK if standard
}

Field Arguments

type Query {
  # Simple argument
  user(id: ID!): User
  
  # Multiple arguments
  users(limit: Int = 10, offset: Int = 0): [User!]!
  
  # Complex arguments
  posts(
    authorId: ID
    status: PostStatus
    publishedAfter: DateTime
    sortBy: PostSort = CREATED_AT
    sortOrder: SortOrder = DESC
  ): [Post!]!
}
 
enum PostSort {
  CREATED_AT
  UPDATED_AT
  TITLE
}
 
enum SortOrder {
  ASC
  DESC
}

Field Nullability

type User {
  # Required fields
  id: ID!
  name: String!
  
  # Optional fields
  email: String
  phone: String
  
  # Required list, optional items
  posts: [Post!]!
  
  # Optional list, required items
  comments: [Comment!]
}

Relationship Design

One-to-One Relationships

type User {
  id: ID!
  name: String!
  profile: Profile
}
 
type Profile {
  id: ID!
  bio: String
  avatar: URL
  user: User!
}

One-to-Many Relationships

type User {
  id: ID!
  name: String!
  posts: [Post!]!
}
 
type Post {
  id: ID!
  title: String!
  author: User!
}

Many-to-Many Relationships

type User {
  id: ID!
  name: String!
  following: [User!]!
  followers: [User!]!
}
 
type Post {
  id: ID!
  title: String!
  tags: [Tag!]!
}
 
type Tag {
  id: ID!
  name: String!
  posts: [Post!]!
}

Connection Pattern (Relay)

type User {
  id: ID!
  name: String!
  posts(first: Int, after: String): PostConnection!
}
 
type PostConnection {
  edges: [PostEdge!]!
  pageInfo: PageInfo!
}
 
type PostEdge {
  node: Post!
  cursor: String!
}
 
type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

Schema Organization

Modular Schemas

# schema.graphql
schema {
  query: Query
  mutation: Mutation
  subscription: Subscription
}
 
# Separate files
# types/
#   - User.graphql
#   - Post.graphql
#   - Comment.graphql
# - queries.graphql
# - mutations.graphql

Schema Stitching

// Combine multiple schemas
import { makeExecutableSchema } from '@graphql-tools/schema';
import userSchema from './user.graphql';
import postSchema from './post.graphql';
 
const schema = makeExecutableSchema({
  typeDefs: [userSchema, postSchema],
  resolvers: mergeResolvers([userResolvers, postResolvers])
});

Advanced Patterns

Generic Types

# Generic pagination
type PaginatedResult<T> {
  items: [T!]!
  totalCount: Int!
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
}
 
# Usage (not valid GraphQL, but conceptual)
type Query {
  users(page: Int, limit: Int): PaginatedResult<User>!
}

Custom Directives

directive @auth(requires: Role!) on FIELD_DEFINITION
directive @deprecated(reason: String) on FIELD_DEFINITION | ENUM_VALUE
 
type Query {
  adminUsers: [User!]! @auth(requires: ADMIN)
  oldField: String @deprecated(reason: "Use newField instead")
}

Schema Extensions

# Base schema
type User {
  id: ID!
  name: String!
}
 
# Extension
extend type User {
  email: String
  posts: [Post!]!
}

Schema Validation

Type Validation

  • Strong Typing: All fields have explicit types
  • Non-Null: Use ! for required fields
  • Lists: Proper list type definitions

Schema Linting

// GraphQL ESLint rules
{
  "@graphql-eslint/no-unused-fields": "error",
  "@graphql-eslint/require-description": ["error", { types: true }],
  "@graphql-eslint/strict-id-in-types": "error"
}

Schema Testing

const { makeExecutableSchema } = require('@graphql-tools/schema');
 
describe('Schema', () => {
  it('should be valid', () => {
    expect(() => makeExecutableSchema({ typeDefs, resolvers })).not.toThrow();
  });
});

Performance Considerations

Query Complexity

  • Field Costs: Assign complexity scores
  • Depth Limits: Prevent deeply nested queries
  • Rate Limiting: Limit query execution time

Schema Optimization

  • Batch Loading: Use DataLoader for N+1 problems
  • Caching: Cache expensive field resolutions
  • Indexing: Ensure database indexes for queried fields

Documentation

Field Descriptions

type User {
  "Unique identifier for the user"
  id: ID!
  
  "Full name of the user"
  name: String!
  
  "Email address (only visible to user)"
  email: String @auth(requires: OWNER)
}

Type Documentation

"""
Represents a user in the system
"""
type User {
  id: ID!
  name: String!
  email: String
}

Tooling

Schema Development Tools

  • GraphQL Playground: Interactive schema exploration
  • GraphiQL: In-browser IDE
  • Apollo Studio: Schema management and analytics
  • GraphQL Voyager: Visual schema exploration

Code Generation

  • GraphQL Code Generator: Generate types and resolvers
  • TypeScript: Type-safe GraphQL operations
  • Apollo Client: Generate queries and mutations

Best Practices

Schema Design

  • Client-First: Design for client needs
  • Consistent Naming: Follow conventions
  • Progressive Disclosure: Expose complexity gradually

Evolution

  • Additive Changes: Prefer adding over removing
  • Deprecation: Mark deprecated fields
  • Versioning: Plan for breaking changes

Performance

  • Query Analysis: Monitor query patterns
  • Optimization: Address N+1 query problems
  • Caching: Implement appropriate caching strategies

0 items under this folder.