Skip to content

GraphQL API Guidelines

Table of Contents

  1. Schema Design
  2. Naming Conventions
  3. Query Structure
  4. Mutations
  5. Error Handling
  6. Performance Considerations
  7. Security
  8. Versioning

Schema Design

Types

  • Use descriptive, domain-specific type names (e.g., CustomerOrder instead of Order)
  • Group related fields into interfaces when appropriate
  • Use enums for fields with a fixed set of values
  • Document all types and fields with descriptions

Example Type Definition

"""
Represents a user in our system
"""
type User {
  """Unique identifier for the user"""
  id: ID!
  """User's full name"""
  name: String!
  """User's email address (must be unique)"""
  email: String!
  """When the user account was created"""
  createdAt: DateTime!
  """User's current status"""
  status: UserStatus!
}

enum UserStatus {
  ACTIVE
  INACTIVE
  SUSPENDED
}

Naming Conventions

  • Use PascalCase for type names
  • Use camelCase for field names
  • Use SCREAMING_SNAKE_CASE for enum values
  • Use _ prefix for deprecated fields
  • Use input for mutation input types

Query Structure

Best Practices

  • Structure queries to match the component's data requirements
  • Use fragments to share field selections
  • Avoid deep nesting (prefer < 5 levels)
  • Use pagination for lists

Example Query

query GetUserWithOrders($userId: ID!, $first: Int = 10, $after: String) {
  user(id: $userId) {
    id
    name
    email
    orders(first: $first, after: $after) {
      edges {
        node {
          id
          total
          status
        }
        cursor
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}

Mutations

Naming

  • Use verbs to describe actions (e.g., createUser, updateProfile)
  • Return the modified resource and relevant query fields
  • Make mutations idempotent when possible

Example Mutation

mutation UpdateUserProfile($input: UpdateUserInput!) {
  updateUserProfile(input: $input) {
    user {
      id
      name
      email
    }
    errors {
      field
      message
    }
  }
}

# Variables
{
  "input": {
    "name": "John Doe",
    "email": "john.doe@example.com"
  }
}

Error Handling

Standard Error Format

{
  "errors": [
    {
      "message": "Invalid email format",
      "locations": [ { "line": 2, "column": 3 } ],
      "path": [ "createUser" ],
      "extensions": {
        "code": "VALIDATION_ERROR",
        "field": "email",
        "details": "Must be a valid email address"
      }
    }
  ],
  "data": null
}

Error Types

  • AUTHENTICATION_ERROR: Invalid or missing authentication
  • FORBIDDEN: Insufficient permissions
  • NOT_FOUND: Resource doesn't exist
  • VALIDATION_ERROR: Input validation failed
  • INTERNAL_SERVER_ERROR: Server-side error

Performance Considerations

N+1 Problem

  • Use DataLoader to batch and cache database queries
  • Implement field-level dataloaders for complex relationships

Query Complexity

  • Set maximum query depth limits
  • Implement query cost analysis
  • Consider using persisted queries in production

Security

Authentication

  • Use JWT or OAuth2 for authentication
  • Include auth token in the Authorization header

Authorization

  • Implement field-level permissions
  • Use directives for role-based access control

Example with Directives

type Query {
  user(id: ID!): User @hasRole(role: "ADMIN")
  me: User @authenticated
}

type User {
  id: ID!
  email: String! @hasScope(scope: "email")
  ssn: String @hasRole(role: "HR")
}

Versioning

  • Prefer schema evolution over versioning
  • Add new fields instead of changing existing ones
  • Use @deprecated directive for deprecated fields
  • Consider using schema stitching for major changes

Tooling

  • Use GraphQL Code Generator for type safety
  • Implement Apollo Engine for monitoring
  • Set up schema linting with graphql-schema-linter
  • Document with GraphQL Voyager or GraphiQL

Example Subscription

subscription OnOrderStatusChanged($orderId: ID!) {
  orderStatusChanged(orderId: $orderId) {
    id
    status
    updatedAt
  }
}

Testing

  • Test queries and mutations in isolation
  • Mock external services in tests
  • Test error cases and edge conditions
  • Consider snapshot testing for complex queries