GraphQL is the second most common API style after REST, and for many modern apps — especially mobile and dashboard-heavy ones — it's the primary style. As a QA engineer you'll meet plenty of teams running both: a public REST API for partners, an internal GraphQL endpoint for the web and mobile clients. The testing principles you've learned still apply (auth, validation, error handling, performance), but the mechanics change. This lesson sets up the differences so the next three lessons can dive into the specifics.
What GraphQL actually is
GraphQL is a query language for APIs. The client writes a query specifying exactly what data it wants, and the server returns exactly that — no more, no less.
Where a REST API exposes many endpoints — /users, /users/123, /users/123/orders, /products — a GraphQL API exposes a single endpoint, usually /graphql. Every read, every write, every subscription goes through that one URL. The shape of the request body decides what happens.
A typical GraphQL query:
query {
user(id: 42) {
name
email
orders {
id
total
}
}
}And a response that mirrors the shape exactly:
{
"data": {
"user": {
"name": "Alice",
"email": "alice@test.com",
"orders": [
{ "id": 9001, "total": 49.99 },
{ "id": 9002, "total": 12.50 }
]
}
}
}That mirroring — request shape equals response shape — is GraphQL's defining feature. There's no fixed /users/123 payload to consume; the client describes the payload it wants.
The headline differences
REST vs GraphQL at a glance
REST
Many endpoints
/users, /products, /orders — one URL per resource type.
Server defines response shape
GET /users/123 returns whatever the server chose to include.
Verbs everywhere
GET, POST, PUT, PATCH, DELETE map to operations.
Versioned via URL
/v1/, /v2/ — old clients keep working until the version is retired.
HTTP-native error codes
404, 401, 500 distinguish failures.
Easier to cache
Standard HTTP caching applies — CDNs work out of the box.
GraphQL
Single /graphql endpoint
All operations POST to one URL with a query in the body.
Client picks fields
The query declares exactly which fields the response should include.
Three operation types
Query (read), mutation (write), subscription (live updates).
Schema evolves, no versions
Add fields freely; mark deprecated ones; removals require migration.
Errors in the response body
HTTP 200 even when the operation failed; check the errors array.
Caching is harder
Every query is a unique POST body — CDNs need GraphQL-aware tooling.
The implications run deep. With REST you test endpoints; with GraphQL you test queries. With REST a 404 tells you the resource is missing; with GraphQL a 200 with {"errors": [...]} does. The conceptual map shifts, even though the underlying concerns — auth, performance, correctness — don't.
Over-fetching and under-fetching
REST's fixed payloads mean clients often over-fetch — receiving fields they don't need — or under-fetch — needing multiple requests to assemble what they want.
A user profile page that needs the user, their orders, and the order items would, in REST, look like:
GET /users/123— user object.GET /users/123/orders— list of orders.GET /orders/9001/items,GET /orders/9002/items, … — items per order.
Three round trips minimum, more on a typical mobile network. In GraphQL, the same data is one query:
query {
user(id: 123) {
name
orders {
id
total
items {
productId
quantity
}
}
}
}One round trip. The mobile-bandwidth case is one of the strongest arguments for GraphQL.
When each style wins
GraphQL shines when:
- The client controls the data shape (mobile, single-page web apps).
- Many small relationships need to be traversed (social graphs, e-commerce catalogues).
- Bandwidth or round-trip count matters (mobile, slow networks).
- A single team owns both client and server (the schema can evolve fast).
REST shines when:
- Public consumers of all kinds need to plug in (third-party integrators).
- HTTP caching is important (CDNs, browser caching).
- Operations map cleanly to CRUD on resources (admin tools, simple data APIs).
- Tooling familiarity matters (every dev knows REST; GraphQL has a learning curve).
Most large companies run both. As QA, that's your reality — you'll need fluency in both styles.
What changes for testing
Conceptually, very little. You still test:
- Functional correctness — does the right data come back?
- Validation — does the API reject bad inputs?
- Auth — are tokens enforced on every operation?
- Error handling — does the API fail gracefully?
- Performance — does it respond within budget?
- Schema — does the response match the contract?
Mechanically, several things shift:
- HTTP method: every GraphQL request is a
POSTto/graphql, even reads. Asserting on method is rarely useful. - Status codes: HTTP 200 is normal for every response — including errors. The errors live in the body.
- Single endpoint, many operations: you can no longer talk about "testing the /users endpoint" — you test queries that return users, mutations that modify users, and so on.
- Schema as contract: the server publishes a typed schema (introspection or SDL files). Schema-aware testing tools can auto-generate tests.
- Tooling: GraphQL Playground, Apollo Studio, Insomnia, and Postman all have GraphQL-specific UIs that make writing test queries easier than hand-building JSON.
A first taste of testing GraphQL
Sending a query with curl:
curl -X POST https://api.example.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhbGciOiJI..." \
-d '{"query": "{ user(id: 42) { name email } }"}'The body is JSON with a query field containing the GraphQL query string. That's it. The response will be JSON with data, errors, or both.
A test for the same query, in pseudo-Python:
response = requests.post(
"https://api.example.com/graphql",
json={"query": "{ user(id: 42) { name email } }"},
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 200
body = response.json()
assert body.get("errors") is None, f"GraphQL errors: {body['errors']}"
assert body["data"]["user"]["email"] == "alice@test.com"Three assertions: HTTP status is 200, no errors in the body, expected data returned. That trio is the foundation of every GraphQL test.
⚠️ Common mistakes
- Treating HTTP status as the source of truth. A 200 with
errorspopulated is still a failure. GraphQL tests must checkerrorsexplicitly. - Carrying over REST URL intuition. "Where's the user endpoint?" misses the point. There's one endpoint; the query describes what you want.
- Assuming GraphQL replaces REST. Many teams run both, with REST for stable public APIs and GraphQL for client-shaped reads. Your test suite needs to handle both.
🎯 Practice task
Compare REST and GraphQL on the same data. 30 minutes.
- Open the GitHub REST API and GitHub GraphQL API docs side by side. They expose much of the same data.
- Pick a use case: "fetch the last 5 issues for a repository, with the author's name and a count of comments per issue."
- Sketch the REST flow. How many calls? Which endpoints?
- Write the GraphQL query that does it in one request. Use the GitHub GraphQL Explorer to run it.
- Compare the responses: payload size, number of round trips, fields you didn't ask for in REST.
- Stretch: add a hypothetical "for each issue, also fetch the latest comment's body and timestamp." Re-do both flows. The REST count will balloon; the GraphQL query stays one request.
You now know what GraphQL is and how it differs from REST. The next lesson covers the three operation types you'll meet — queries, mutations, and subscriptions — in concrete detail.