1.2: GraphQL Queries

1.2: GraphQL Queries

Let's dive right into GraphQL query syntax with a pretty full-fledged example. We'll start by assuming that we have data types available at our GQL endpoint as follows, in brief:

  • People, including IDs, names, heights, favorite foods, and many other details.
  • Foods, including a label and cuisine type.
  • Gods and goddesses of ancient mythology, including names, domains, and many other details.

Observe the following GraphQL query, which we'll break down piece by piece:

{
    person(
        id: "12345"
    ) {
        name
        height(unit: INCH)
        favoriteFoods(limit: 2) {
            label
            cuisine
        }
    }

    allGods(
        domain: THUNDER,
        page: 1
        limit: 3
    ) {
        pageInfo {
            currentPage
            totalPages
        }
        gods {
            name
            mythology
        }
    }
}

A plausible response from a GraphQL server for the above query could be:

{
    "data": {
        "person": {
            "name": "Mary",
            "height": 63,
            "favoriteFoods": [
                {
                    "label": "Chicken Parmigiana",
                    "cuisine": "Italian"
                },
                {
                    "label": "Lamb Vindaloo",
                    "cuisine": "Indian"
                }
            ]
        },
        "allGods": {
            "pageInfo": {
                "currentPage": 1,
                "totalPages": 4
            },
            "gods": [
                {
                    "name": "Zeus",
                    "mythology": "GREEK"
                },
                {
                    "name": "Set",
                    "mythology": "EGYPTIAN"
                },
                {
                    "name": "Leigong",
                    "mythology": "CHINESE"
                }
            ]
        }
    }
}

The above example relies on an imaginary GraphQL schema for these data types, defined in the server implementation. For now, we're concerned only with the query syntax itself. As we break it down, observe a few significant details:

  • We're querying multiple types of data in the same request.
  • The query expresses exactly the fields we want to get back.
  • The format of the returned data closely matches that of our query expression.
  • The query uses a uniform syntax of nested fields.
  • Filtering arguments are included at various points throughout the nested structure.

Querying for What We Want

For the moment, let's say person and allGods in our example represent two different queries, for two different kinds of data. Unlike a traditional API paradigm like REST, which would define separate and explicit endpoints for each data type, GraphQL gives us the flexibility to query a single endpoint with an expression that can fetch many types of data at once. There's no discernible connection between our "person" query and a request for gods/goddesses of thunder in ancient mythology! But a particular use case in our application might find it useful and efficient to fetch them at the same time.

Likewise, the query specifies exactly the fields that are desired for both person (name, height and favoriteFoods) and allGods (pageInfo and gods), and the data we receive back mirrors that field specification. There are presumably many more fields available for these data types (perhaps age and gender for person), but we will get back only what we asked for.

Arguments

While the fields we want returned are specified within the braces of each type, named arguments and values for them are specified within parentheses after the type name. Arguments are often optional and often affect the way query results are filtered, formatted, or otherwise transformed. We are passing an id argument to person, specifying a particular person we want to query, as well as a domain, page and limit for allGods. (Note that the value passed for domain is a value of an imaginary enumeration that is presumably defined in the data schema.)

A Tree of Equals

We've leaned into the natural tendency to think of person and allGods as "queries" or "entities", as distinct from the fields that belong to them. In reality, however, the entire tree expressed in our query consists of nothing but fields, albeit nested within each other to various degrees. Look closely and observe that the expression of person is, in fact, syntactically no different from that of favoriteFoods. Both are fields, and under the hood, there is no conceptual difference between the two either.

As we'll see, the schema for a data graph consists of a tree of defined types, each type having fields that in turn have a defined complex or scalar type. Any GraphQL runtime has a single "root" type (usually called Query) to start the tree, and the types we tend to think of as "entities" are simply assigned to fields on this root. So our example query is actually making one "generic" query for the root type and specifying that we want the fields person and allGods returned. Those being complex types, we also specify the sub-fields we want from them, and so on through the entire tree of our query. We want the person field favoriteFoods, and this field is itself a complex type, so we have a sub-selection for the fields we want from it. This nesting could go several levels deep, as appropriate for whatever the schema of the data graph, and the final "leaves" of the tree occur whenever we express a field with a scalar type.

Understanding this concept that the entire tree of the query simply consists of fields will make it easier to get the hang of more complex queries. It's also key to making sense of the arguments our example query is passing for fields like height (the unit argument) and favoriteFoods (the limit argument). These arguments are no different from the filtering arguments for person, and the syntax is identical. person and height are simply fields, and all fields can potentially accept arguments and have fields of their own, leading to a very flexible and uniform query syntax.

Core Concept

The concept of nested fields is key to the GraphQL runtime implementation as well. When responding to the query, the server will start with the root type and "resolve" each field requested in the query. When the field is of a complex type, each of its fields is resolved, and so on recursively until scalar fields are reached.

Try it Live

Let's try a more or less equivalent example that you can actually use against swapi-graphql.

Remember: swapi-graphql is a live reference implementation you can query by either browsing to https://graphql.org/swapi-graphql or entering https://swapi-graphql.netlify.app/.netlify/functions/index as your endpoint in any GraphQL client.

{
    allPeople(
        first: 2
    ) {
        people {
            name
            birthYear
            filmConnection {
                films {
                    title
                    director
                    releaseDate
                }
            }
        }
    }

    allPlanets (
        first: 2
    ) {
        planets {
            name
            climates
        }
    }
}

Enter the above query in your client and observe the results. Try modifying the first argument and removing fields, and observe the effects of those changes. (If you're feeling more adventurous, try using the documentation browsing capabilities of Altair or GraphiQL to discover other fields you can add to the query.)

Core Concept

GraphQL clients obfuscate the form of the actual HTTP request being sent, but this is easy to discover. If you're using a browser-based client, observe the Network tab when a query is sent, and you'll see that the request contains a raw body consisting of "query: {string}", where {string} is simply the raw string of your entire query. If the request is being sent as a GET, the query might be encoded in the querystring parameter "query" instead. Unlike with REST, the HTTP request type doesn't matter, only the contents of the query.

Complete and Continue