TestingAPIs is still being built. If something breaks, email support@testingapis.com.

What GraphQL is

One endpoint, one POST, and a response shaped like the question.

A REST API gives every resource its own URL, and the server decides what each URL sends back. Ask this API for book 1 and you get the whole record:

Terminal
curl https://api.testingapis.com/api/Books/1
This example uses endpoints for signed-in members.Sign inCreate a free account
Response
{
  "id": 1,
  "title": "Clean Code",
  "author": "Robert C. Martin",
  "isbn": "9780132350884",
  "genre": "programming",
  "publishedYear": 2008,
  "pages": 464,
  "language": "English",
  "price": 37.99,
  "rating": 4.4,
  "available": true,
  "tags": ["craftsmanship", "refactoring"]
}

Twelve fields. If the page you're building only shows a title and an author, the other ten travel over the network for nothing. If it also needs the author's other books, that's a second request, and maybe a third.

GraphQL turns this around. There is one URL for the whole API, and the client sends a query that names the fields it wants. Here is the same book in GraphQL:

Query
{
  book(id: 1) {
    title
    author
  }
}
Response
{
  "data": {
    "book": {
      "title": "Clean Code",
      "author": "Robert C. Martin"
    }
  }
}

The response has the same shape as the query. You asked for book with title and author inside it, and that's what came back, nested the same way, under a key called data. Facebook built GraphQL in 2012 for its mobile apps, where every wasted byte cost battery and time, and released it publicly in 2015. GitHub, Shopify and many internal company APIs use it today.

What actually gets sent

GraphQL isn't a new protocol. It travels over ordinary HTTP, almost always as a POST with a JSON body. The query goes into that body as a string:

Request
POST /graphql HTTP/1.1
Host: api.testingapis.com
Content-Type: application/json

{"query": "{ book(id: 1) { title author } }"}
This example uses endpoints for signed-in members.Sign inCreate a free account

Because the query is a string inside JSON, any double quotes in it have to be escaped. That's the most common reason a hand-written curl command fails. This one asks for every book in the programming genre:

Terminal
curl -X POST https://api.testingapis.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ books(genre: \"programming\") { id title author } }"}'
This example uses endpoints for signed-in members.Sign inCreate a free account
Response (200 OK)
{
  "data": {
    "books": [
      { "id": 1, "title": "Clean Code", "author": "Robert C. Martin" },
      { "id": 2, "title": "The Pragmatic Programmer", "author": "Andrew Hunt & David Thomas" },
      { "id": 3, "title": "Designing Data-Intensive Applications", "author": "Martin Kleppmann" },
      { "id": 4, "title": "Refactoring", "author": "Martin Fowler" },
      { "id": 5, "title": "Code Complete", "author": "Steve McConnell" }
    ]
  }
}

You can send the same request from here:

POST/graphql
{"query":"{ books(genre: \"programming\") { id title author } }"}
This request uses an endpoint for signed-in members.Sign inCreate a free account

Every query in this tutorial goes to the same address. The path never changes, and neither does the method. Only the body does.

The response always has the same outer shape

A GraphQL response is a JSON object with up to two keys that matter: data holds the answer, and errorsholds a list of problems. Ask for a field that doesn't exist and you get errors instead of data:

Response (400 Bad Request, trimmed)
{
  "errors": [
    {
      "message": "The field `publisher` does not exist on the type `Book`.",
      "locations": [{ "line": 1, "column": 39 }],
      "path": ["books"]
    }
  ]
}

The message names the field and the type, and locationspoints at the line and column in the query. GraphQL checks the whole query against the API's schema before running any of it, so a typo in a field name never reaches your data. The lesson on errorsshows when the status is 400, when it's 200, and why that depends on a header you might not think to send.

What changes for a tester

Most of what you know about HTTP still applies. A few habits from REST testing stop working, though. These are the ones to know before you start:

  • Every request goes to one URL. Access logs, dashboards and alerts grouped by URL show a single line for the whole API. To tell operations apart you need the operation name, which the next lesson covers.
  • The client chooses the shape of the response. A test can only check the shape of the query it sent. Two clients asking for different fields are, in effect, calling two different endpoints.
  • The status code tells you less. Plenty of GraphQL servers answer 200 to a request that failed completely, with the failure described in errors. A test that only checks for 200 will pass when it shouldn't.
  • The schema is a machine-readable contract. You can read it, diff it between releases, and check every field the API offers, which is harder with a REST API that has thin documentation.

GraphQL also has costs that REST doesn't. Responses to POST requests aren't cached by browsers or CDNs. One request can ask for a lot of work, which makes rate limiting by request count meaningless. Lesson 8 tests both.

Trying queries by hand

Open the GraphQL endpoint in a browser and this API serves a GraphQL IDE, where you can type a query and see the response. Most GraphQL servers ship one. The GraphQL reference also has a playground next to every operation the API supports, and each operation has a Try it button that loads it.

For testing, curl and code matter more than the IDE, because they show you the raw HTTP: the status, the headers and the exact bytes sent. IDEs tend to hide those, and they are where many GraphQL bugs live.

Exercise

Send { books(genre: "programming") { title } } with curl or the Send button. Then add pages to the fields and send it again; only the shape of the response should change. Finally, ask for publisher, a field this API doesn't have, and read the error. Which line and column does it point to, and is that where the mistake is?

Practise and go further