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
| Feature | REST | GraphQL |
|---|---|---|
| Endpoints | Multiple (/users, /posts) | Single (/graphql) |
| Data fetching | Server decides response shape | Client specifies fields |
| Over-fetching | Common | Eliminated |
| Under-fetching | Requires multiple requests | Solved with nested queries |
| Versioning | URL/header versions | Schema evolution |
| Caching | HTTP caching built-in | More 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
| Test | Input | Expected |
|---|---|---|
| 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
| Scenario | Expected |
|---|---|
| Valid input | Success with created resource |
| Missing required field | Error with validation message |
| Invalid enum value | Error: not a valid Role |
| Duplicate unique field | Error with conflict message |
| Unauthorized mutation | Error with auth message |
| Input exceeding limits | Error 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
| Tool | Purpose |
|---|---|
| GraphiQL / GraphQL Playground | Interactive query explorer |
| Postman | Supports GraphQL queries in body |
| Altair GraphQL Client | Feature-rich GraphQL client |
| graphql-inspector | Schema diff and validation |
| Apollo Studio | Performance monitoring |
Hands-On Exercise
- Explore a GraphQL API: Use the Star Wars API (https://swapi-graphql.netlify.app) to send queries, test field selection, and explore the schema.
- Test mutations: If using a writable GraphQL API, test create/update/delete operations with valid and invalid inputs.
- Security testing: Try sending deeply nested queries and introspection queries to check for limits.
- 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