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.