Skip to content

Writing Queries

A GraphQL query is text, and the text has a shape. Once you can read that shape — and write it from scratch — you can talk to any GraphQL API. This lesson builds a query up one piece at a time: fields, then nested selection sets, then the operation name that wraps the whole thing.

The smallest unit of a query is a field: a single name asking for a single piece of data. Fields are wrapped in curly braces, called a selection set. The selection set at the very top of a query selects from the Query root.

{
currentUser
}

This asks for one field, currentUser. If a field returns a scalar — a String, Int, and so on — naming it is enough; you have reached a leaf and the query stops there. You can ask for several fields side by side simply by listing them:

{
serverTime
apiVersion
}

When a field returns an object type rather than a scalar, naming it alone is not enough — GraphQL needs to know which of that object’s fields you want. You answer by opening a nested selection set with its own braces:

{
currentUser {
id
name
email
}
}

currentUser returns a User object, so it must be followed by a selection set listing the User fields you care about. This nesting can go as deep as the graph allows. If User has an address field that returns an Address object, you nest again:

{
currentUser {
name
address {
city
country
}
}
}

Each level of braces steps one edge deeper into the graph. A query is therefore a tree of fields that mirrors the shape of the response you will get back.

flowchart TD
  Root["{ } selection set (Query root)"] --> CU["currentUser (User)"]
  CU --> Name["name (scalar leaf)"]
  CU --> Addr["address (Address)"]
  Addr --> City["city (scalar leaf)"]
  Addr --> Country["country (scalar leaf)"]
A nested query is a tree of fields that mirrors the response shape.

A useful rule of thumb: a field that returns an object type requires a selection set, and a field that returns a scalar forbids one. Forget either rule and the server rejects the query during validation, before any resolver runs.

The bare { ... } form is shorthand for a query. You can — and on real teams, should — write the long form, which begins with the keyword query followed by an operation name you choose:

query GetCurrentUser {
currentUser {
id
name
}
}

GetCurrentUser is the operation name. It does not change the result at all, but it earns its keep in three ways: it appears in server logs and tracing so you can tell requests apart, it is required once a document holds more than one operation, and it makes the query self-documenting. Naming operations is a habit worth forming early.

The example below selects nested fields: a currentUser that carries a name and a nested favoriteTrack object, which in turn carries a title. The query uses the long query form with an operation name. Press Run and compare the JSON to the tree of fields you wrote.

JavaScript

The response is a nested object: currentUser holds a name and a favoriteTrack, and favoriteTrack holds a title. Every brace you opened in the query produced one level of nesting in the result. Try removing the inner title line — the server will reject the query, because a field returning the Track object type must have a selection set.

When does a field require its own nested selection set?
What is the role of the operation name in "query GetCurrentUser { ... }"?
How does the structure of a GraphQL response relate to the query?