In this tutorial, we’ll take an in-depth look at queries in GraphQL so that you better understand how to retrieve data from a GraphQL server. We will cover fields, arguments, aliases, operation syntax, and more. Once you have finished, you will be able to leverage the functionality of queries to build better APIs with GraphQL.
GraphQL is based on asking for specific fields on objects. Therefore, we can’t successfully talk about queries without talking about fields. Queries are the construct used by the client to request specific fields from the server.
Given that GraphQL is structured to optimally expose one endpoint for all requests, queries are structured to request specific fields, and the server is equally structured to respond with the exact fields being requested.
Consider a situation where a client wants to request soccer players from an API endpoint. The query will be structured like this:
{
players {
name
}
}
This is a typical GraphQL query. Queries are made up of two distinct parts:
root field
(players): The object containing the payload.payload
(name): The field(s) requested by the client.This is an essential part of GraphQL because the server knows what fields the client is asking for and always responds with that exact data. In the case of our example query, we could have this response:
{
"players": [
{"name": "Pogba"},
{"name": "Lukaku"},
{"name": "Rashford"},
{"name": "Marshal"}
]
}
The field name
returns a String type, in this case, the names of the Manchester United players. However, we are not limited to just Strings: we can have fields of all data types, just like the root field players
returns an array of items. Feel free to learn more about the GraphQL Type System in the GraphQL official docs.
In production, we would want to do more than just returning names. For instance, in our last query, we can redefine the query to select an individual player from the list and query for more data on that player. To be able to do so, we’ll need a way to identify that player so we can get the details. In GraphQL, we can achieve this with arguments.
GraphQL queries allow us to pass in arguments into query fields and nested query objects. You can pass arguments to every field and every nested object in your query to further deepen your request and make multiple fetches. Arguments serve the same purpose as your traditional query parameters
or URL segments
in a REST API. We can pass them into our query fields to further specify how the server should respond to our request.
Back to our earlier situation of fetching a specific player’s kit details like shirt size
or shoe size
. First, we’ll have to specify that player by passing in an argument id
to identify the player from the list of players, and then define the fields we want in the query payload:
{
player(id : "Pogba") {
name,
kit {
shirtSize,
bootSize
}
}
}
Here, we are requesting the desired fields on the player Pogba
because of the id
argument we passed into the query. Just like fields, there are no type restrictions. Arguments can be of different types, too. The result of the previous query with the id
argument will look like the following:
{
"player": {
"name": "Pogba",
"kit": [
{
"shirtSize": "large",
"shoeSize": "medium"
}
]
}
}
One possible pitfall here is that GraphQL queries look almost the same for single items and lists of items. In these situations, remember that we always know what to expect based on what is defined in the schema.
Also, GraphQL queries are interactive, so you can add more fields to the root field object at will. That way, you, as the client, have the flexibility to avoid round trips and request for as much data as you want in a single request.
Now, what happens if we want to fetch the same fields for two players, not just one? That’s where aliases come in.
If you take a closer look at our last example, you’ll notice that the result object fields:
// result....
"player": {
"name": "Pogba",
"kit": [
{
"shirtSize": "large",
"shoeSize": "medium"
}
]
}
Match the query fields:
// query.... has matching fields with the result
player(id : "Pogba") {
name,
kit {
shirtSize,
bootSize
}
}
But without the argument:
(id : "Pogba")
Because of this, we can’t directly query for the same field player
with different arguments. That is, we can’t do something like this:
{
player(id : "Pogba") {
name,
kit {
shirtSize,
bootSize
}
}
player(id : "Lukaku") {
name,
kit {
shirtSize,
bootSize
}
}
}
We can’t do this, but what we can do is use aliases. They let us rename the result of a field to anything we want. For our example, to query for two players’ kit details, we’ll define our query like so:
{
player1: player(id: "Pogba") {
name,
kit {
shirtSize,
shoeSize
}
}
player2: player(id: "Lukaku") {
name,
kit {
shirtSize,
shoeSize
}
}
}
Here, the two player
fields would have conflicted, but since we can alias them to different names player1
and player2
, we can get both results in one request:
{
"data": {
"player1": {
"name": "Pogba",
"kit": [
{
"shirtSize": "large",
"shoeSize": "medium"
}
]
},
"player2": {
"name": "Lukaku",
"kit": [
{
"shirtSize": "extralarge",
"shoeSize": "large"
}
]
}
}
}
Now using aliases, we have successfully queried the same field with different arguments and gotten the expected response.
Until now, we have been using the shorthand operation syntax, where we are not explicitly required to define either the operation name or type. In production, it is a best practice to use operation names and types to help make your codebase less ambiguous. It also helps with debugging your query in the case of an error.
The operation syntax comprises of two central things:
The operation type
which could be either query, mutation, or subscription. It is used to describe the type of operation you intend to carry out.
The operation name
which could be anything that will help you relate with the operation you’re trying to perform.
Now, we can rewrite our earlier example and add an operation type and name like this:
query PlayerDetails{
player(id : "Pogba") {
name,
kit {
shirtSize,
bootSize
}
}
}
In this example, query
is the operation type and PlayerDetails
is the operation name.
So far, we have been passing all our arguments directly into the query string. In most cases, the arguments we pass are dynamic. For example, let’s say the player the client wants details from comes from a text input form or a dropdown menu. The argument we pass into the query string has to be dynamic, and to do that, we need to use variables. Thus, variables are used to factor out dynamic values from queries and pass them as a separate dictionary.
Considering our last example, if we wanted to make the player dynamic such that the selected player’s details are returned, we would have to store the player’s ID value in a variable and pass it into the operation name and query argument, like so:
query PlayerDetails ($id: String){
player (id : $id) {
name,
kit {
shirtSize,
bootSize
}
}
}
Here, $title: String
is the variable definition, and title
is the variable name. It is prefixed by $
followed by the type, which in this case is String. This means that we can avoid manually interpolating strings to construct dynamic queries.
Looking at our query, you’ll notice that the player
field is practically the same for both players:
name,
kit {
shirtSize,
bootSize
}
To be more efficient with our query, we can extract this piece of shared logic into a reusable fragment on the player
field, like so:
{
player1: player(id: "Pogba") {
...playerKit
}
player2: player(id: "Lukaku") {
...playerKit
}
}
fragment playerKit on player {
name,
kit {
shirtSize,
shoeSize
}
}
The ability to extract a piece of shared code and reuse it across multiple fields is a crucial concept that helps developers avoid repeating themselves in development and even in production. If you’re working on a deeply layered codebase, you’ll find it rather useful and timely to reuse code rather than repeat yourself across multiply components.
GraphQL directives provide us with a way to tell the server whether to include
or skip
a particular field when responding to our query. There are two built-in directives in GraphQL that helps us achieve that:
@skip
: for skipping a particular field when the value passed into it is true.@include
: to include a particular field when the value passed into it is true.Let’s add a Boolean
directive and skip it on the server with the @skip
directive:
query PlayerDetails ($playerShirtDirective: Boolean!){
player(id: "Pogba") {
name,
kit {
shirtSize @skip(if: $playerShirtDirective)
bootSize
}
}
}
The next thing we’ll do is create the playerShirtDirective
directive in our query variables and set it to true:
// Query Variables
{
"itemIdDirective": true
}
This will now return the payload without the shirtSize
:
"player": {
"name": "Pogba",
"kit": [
{
"shoeSize": "medium"
}
]
}
We can reverse this situation using the @include
directive. It works as the opposite of the @skip
directive. You can use it to reverse this skip action on the server by replacing the @skip
directive with @include
in the query.
In this article, we have gone over the details of GraphQL queries.
If you’d like to learn more about GraphQL, check out the GraphQL official docs. If you’d like to try making a project with GraphQL, try our How To Build and Deploy a GraphQL Server with Node.js and MongoDB on Ubuntu 18.04 tutorial.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
I think there’s a typo in this section
Should be the following instead.