What Is GraphQL?

GraphQL is a query language for APIs developed by Facebook (Meta) in 2012 and open-sourced in 2015. Unlike REST, where the server defines what data each endpoint returns, GraphQL lets the client specify exactly which fields it needs.

GraphQL vs. REST

FeatureRESTGraphQL
EndpointsMultiple (/users, /posts)Single (/graphql)
Data fetchingServer decides response shapeClient specifies fields
Over-fetchingCommonEliminated
Under-fetchingRequires multiple requestsSolved with nested queries
VersioningURL/header versionsSchema evolution
CachingHTTP caching built-inMore complex

Core Concepts

Queries — read data (equivalent to GET):

query {
  user(id: 42) {
    name
    email
    posts {
      title
      createdAt
    }
  }
}

Mutations — write data (equivalent to POST/PUT/DELETE):

mutation {
  createUser(input: { name: "Alice", email: "alice@example.com" }) {
    id
    name
  }
}

Subscriptions — real-time updates via WebSocket:

subscription {
  newMessage(chatId: "123") {
    content
    sender {
      name
    }
  }
}

GraphQL Schema

The schema defines all available types, queries, and mutations. It serves as the API contract:

type User {
  id: ID!
  name: String!
  email: String!
  role: Role!
  posts: [Post!]!
}

enum Role {
  ADMIN
  USER
  VIEWER
}

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
}

The ! means the field is non-nullable. Use schema introspection to explore the API:

{
  __schema {
    types {
      name
      fields {
        name
        type { name }
      }
    }
  }
}

Testing GraphQL Queries

Query Validation Tests

TestInputExpected
Valid query with all fields{ user(id: 42) { name email } }200 with requested fields
Non-existent field{ user(id: 42) { phone } }Error: field not found
Missing required argument{ user { name } }Error: id argument required
Wrong argument type{ user(id: "abc") { name } }Error: type mismatch
Empty query{}Error: syntax error
Valid nested query{ user(id: 42) { posts { title } } }200 with nested data

Response Validation

  • Only requested fields are returned (no over-fetching)
  • Non-nullable fields are never null
  • Array types return arrays (even empty)
  • Enum values match schema definition

Testing GraphQL Mutations

mutation {
  createUser(input: {
    name: "Alice"
    email: "alice@example.com"
    role: USER
  }) {
    id
    name
    email
  }
}

Mutation Test Scenarios

ScenarioExpected
Valid inputSuccess with created resource
Missing required fieldError with validation message
Invalid enum valueError: not a valid Role
Duplicate unique fieldError with conflict message
Unauthorized mutationError with auth message
Input exceeding limitsError with validation message

GraphQL-Specific Security Testing

Query Depth Attack

Recursive types allow attackers to create deeply nested queries:

{
  user(id: 1) {
    posts {
      author {
        posts {
          author {
            posts {
              # ... infinite nesting
            }
          }
        }
      }
    }
  }
}

Test: Send queries with increasing depth. Verify the server rejects queries exceeding the maximum depth limit.

Query Complexity Attack

Even without deep nesting, wide queries can be expensive:

{
  users(limit: 10000) {
    posts(limit: 1000) {
      comments(limit: 1000) {
        author { name }
      }
    }
  }
}

Test: Verify query complexity limits are enforced (max results, max query cost).

Introspection Disable in Production

Schema introspection should be disabled in production to prevent API discovery:

{
  __schema { types { name } }
}

Test: Verify introspection queries fail in production environments.

Batch Query Attack

Some GraphQL implementations allow multiple operations in one request:

# Brute force login via batching
mutation { login1: login(email: "user@test.com", password: "pass1") { token } }
mutation { login2: login(email: "user@test.com", password: "pass2") { token } }
# ... 1000 more

Test: Verify batch limits and rate limiting apply to batched operations.

Testing Tools for GraphQL

ToolPurpose
GraphiQL / GraphQL PlaygroundInteractive query explorer
PostmanSupports GraphQL queries in body
Altair GraphQL ClientFeature-rich GraphQL client
graphql-inspectorSchema diff and validation
Apollo StudioPerformance monitoring

Hands-On Exercise

  1. Explore a GraphQL API: Use the Star Wars API (https://swapi-graphql.netlify.app) to send queries, test field selection, and explore the schema.
  2. Test mutations: If using a writable GraphQL API, test create/update/delete operations with valid and invalid inputs.
  3. Security testing: Try sending deeply nested queries and introspection queries to check for limits.
  4. Compare REST vs GraphQL: Fetch the same data using REST and GraphQL approaches. Compare the number of requests and data transferred.

Key Takeaways

  • GraphQL lets clients request exactly the data they need, eliminating over-fetching and under-fetching
  • Testing GraphQL requires validating queries, mutations, schema compliance, and GraphQL-specific security concerns
  • Query depth limits, complexity limits, and disabled introspection are critical security measures to test
  • The N+1 query problem is a common performance issue — monitor resolver query counts
  • Schema introspection is powerful for test design but should be disabled in production