Every few years or so it seems relevant to review the current state of GraphQL, see which trends have caught on with the industry, what the current best practices are, and make some speculation about what’s coming next. We recently reviewed the current state of GraphQL directives, which you can read about here. Today, we’ll have a refreshed (2021) look at the query syntax itself.
Are you new to GraphQL? This article will cover details on the current state of querying content with the GraphQL query syntax. For an introduction to GraphQL, have a look at this article, here.
Types of Operations
One of the confusions people face with GraphQL is “what is a query”. This confusion comes from the fact that a query is a query. The syntax that is sent to the server to fetch data is a query, but a query can have one of three operation types, query, mutation, or subscription.
If you’re looking for a more concise intro to GraphQL guide, have a look at our core concepts guide from our learning resources.
The name immediately following the operation type is the operation name. These are often used for by server-side tooling to know exactly which query was requesting that data since the query itself can have nearly any shape. Hasura Cloud uses these names to support features like observability metrics, caching, and allow lists.
To complicate matters, you’ll often see the query operation assumed by default because most GraphQL servers (as allowed by the specification) will assume a “query query type”. It’s an accepted query shorthand.
With that bit of confusion out of the way, let’s look at the remaining parts of thequery syntax.
Parts of the query
A query, regardless of type, is composed of a handful of components to create the final document sent to the server.
Fields
Fields, sometimes referred to as leaves of the query, are the actual pieces of data you are wanting to fetch from the server. As the GraphQL specification requires, the data responding from the server must return valid JSON, and so the fields are the keys to the JSON object.
Some have argued that this type of readable syntax has led to the mass adoption of GraphQL. It’s considerably readable for anyone looking to understand what data is being requested for. It’s also very expressive for its simplicity. Have a look at what a relational join query looks like.
It’s very clear what we are requesting, and what we expect will return from the server.
Aliases
A limitation to GraphQL fields, since it returns valid JSON, is that keys need to be unique, and so you can only request fields one time. But there are cases you may want to request the same field for a different reason. Or you may want to rename the key returning from the server. For that, we need to use Aliases.
Arguments
With a readable syntax, we can begin to see the benefits of GraphQL, but without the ability to pass in search parameters, we are missing a very important part of any query language! That’s where we get arguments. Arguments are defined as an Input Type, and allow us to make requests for subsets of our data. Every vendor implements arguments differently in terms of the accepted syntax. Some follow a mongo-inspired theme, others follow a more traditional SQL-like naming convention. Some frameworks and tooling like Relay requires a guaranteed set of arguments to support automatic pagination and other framework-specific features. For the purposes of our examples, I’ll be using the same syntax that Hasura uses, which means you can use these examples in your own queries.
When combined with aliases that we saw above, we can begin to see some interesting use cases.
Fragments
Looking at syntax like this, we can already see a problem arising. This is not DRY syntax! That’s where fragments let us define sub-selection of fields that we can re-use across our queries.
Fragment syntax starts by declaring a fragment, providing a name for the fragment, and specifying which type the subset of fields will be executed on.
Variables
We may want to declare a specific query format, but there may be some variable input from our users. For that, we can specify variable input syntax. Variables are declared with the $
syntax followed by the variable name and a declaration of what type it is. Variables can represent any number of complex inputs that may be needed.
We can parameterize the above query by simply accepting the integer as a variable.
Or we can provide a more complex input, replacing the entire filter statement that we generate from our application code.
The variables themselves are sent as a separate parameter in the request to the GraphQL endpoint. The final body payload represents the following shape:
It’s important to indicate which of the input arguments are required per the schema definition. $price: Integer
and $price: Integer!
have very different implications for our resolving logic on the server! If you declare a variable in your query, you must provide, and conversely, if you provide a variable, you must use it, otherwise, the query will not validate in the query planning stage.
Directives
Directives allow us to provide variable execution for our queries based on small syntactical changes. We wrote about GraphQL directives at length in this article. An example of a directive in Hasura Cloud is providing a cached
directive.
Ways to Query
All GraphQL servers accept two inputs in the body as we saw with variables above, the query
and variables
parameters. Variables can be of any valid JSON type, with all declared variables needing to be provided, and all provided variables needing to be declared. The query will always be a string type.
In the following sections, we’ll look at common examples of a compiled query, but there are a number of frameworks and tools now that provide helpers around generating queries into their final format. The days of string concatenated queries are long behind us!
String Query
As mentioned above, all queries will finally be sent to the server in a string format. In languages that support string definitions with line breaks, it can be quite helpful for declaring our query.
Regardless of how the string comes together, below we’ll look at some common patterns for putting queries together.
Examples of GraphQL Queries
Basic Query
query GetItems {
items {
name
value
}
}
Query with Inputs
query GetItems {
items(where: {
value: {
_lt: 50
}
}) {
name
value
}
}
Query with Variables
query GetItems($value: Integer) {
items(where: {
value: {
_lt: $value
}
}) {
name
value
}
}
Examples of GraphQL Mutations
Basic Mutation
mutation CreateItem {
insert_item_one(object:{
name: "New Item",
value: 50
}) {
name
value
}
}
Mutation with Variables
mutation CreateItem($item: item_insert_input!) {
insert_item_one(object: $item) {
name
value
}
}
Examples of GraphQL Subscriptions
Basic Subscription
subscription GetItems {
items {
name
value
}
}
Subscription with Inputs
subscription GetItems {
items(where: {
value: {
_lt: 50
}
}) {
name
value
}
}
Subscription with Variables
subscription GetItems($value: Integer) {
items(where: {
value: {
_lt: $value
}
}) {
name
value
}
}
Examples of Fragments, Aliases, and Directives
Fragment example
fragment ItemDetails on Item {
name
value
}
query GetItems {
items {
...ItemDetails
}
}
Alias Example
fragment ItemDetails on Item {
name
value
}
query GetItems {
set1: items { # <---
...ItemDetails
}
set2: items { # <---
...ItemDetails
}
}
Directive Examples
query GetItems @cached { # <---
items(where: {
value: {
_lt: 50
}
}) {
name
value
}
}
Try out more GraphQL queries here.
Fluent GraphQL
While all queries will end up as strings, the path to get there can be whatever you want! An entire series of clients have popped up under the umbrella of “Fluent GraphQL” - the ability to write queries as abstracted concepts. We’ve written a previous round-up on the various providers here. For the purposes of this example, we’ll use GraphQL Zeus which is the client we used for our Haura Super App which is a large reference architecture.
Whichever client you use, the pattern is fairly similar. You provide your endpoint to some form of CLI, and an SDK is generated that allows you to provide object-like query structures. The benefit of this approach is that you can use typical object manipulation such as spread operators, optional subsets, and more.
Examples of GraphQL Queries with GraphQL Zeus
Basic Query
Gql.query({
items: [{}, {
name: true,
value: true
}]
})
Query with Inputs
Gql.query({
items: [{
where: {
value: {
_lt: 50
}
}
}, {
name: true,
value: true
}]
})
Query with Variables
# import the $ helper from the generated client
Gql.query({
items: [{
where: {
value: {
_lt: $`value`
}
}
}, {
name: true,
value: true
}]
}, {
value: 50
})
Examples of GraphQL Mutations with GraphQL Zeus
Basic Mutation
Gql.mutation({
insert_item_one: [{
object: {
name: "New Items",
value: 50
}
}, {
name: true,
value: true
}]
})
Mutation with Variables
# import the $ helper from the generated client
Gql.mutation({
insert_item_one: [{
object: $`item`
}, {
name: true,
value: true
}]
}, {
item: {
name: "New Item",
value: 50,
}
})
Examples of GraphQL Subscriptions with GraphQL Zeus
Basic Subscription
Gql.subscribtion({
items: [{}, {
name: true,
value: true
}]
})
Subscription with Inputs
Gql.subscription({
items: [{
where: {
value: {
_lt: 50
}
}
}, {
name: true,
value: true
}]
})
Subscription with Variables
# import the $ helper from the generated client
Gql.subscription({
items: [{
where: {
value: {
_lt: $`value`
}
}
}, {
name: true,
value: true
}]
}, {
value: 50
})
Examples of Fragments, Aliases, and Directives with GraphQL Zeus
Fragment example
#js Example
const itemDetails = {
name: true,
value: true,
}
Gql.query({
items: [{}, {
...itemDetails #They're simple objects!
}]
})
Alias Example
Gql.query({
__alias: {
set1: {
items: [{}, {
name: true,
value: true
}]
},
set2: {
items: [{}, {
name: true,
value: true
}]
}
}
})
Directive Examples
GraphQL Examples with GraphQL Code Generator
GraphQL code generator is a tool provided by the Guild, a consortium of open source developers that provide open tooling for the GraphQL ecosystem. What sets GraphQL Code Generator apart is the ability to generate an SDK from GraphQL documents. Where you write your queries, even testing them in GraphiQL, but still benefit from the SDK-like behavior in your application. Paired with a tool like Hasura Cloud’s allow list, you get a locked-down API with an explicit handshake between your SDK and your API.
What this means, is that if we take a query such as:
query getItems {
items(where: {
value: {
_lt: 50
}
}) {
name
value
}
}
And you import it into your project, you can access it using an SDK style accessor such as:
const { items } = await sdk.getItems();
Everything is exactly the same as writing traditional style GraphQL queries, mutations, and subscriptions. You just get a wrapper with invocation and authentication built in.
Summary
The GraphQL ecosystem is growing with newer and better tools being created every day. The community is vibrant and actively pushing the specification maintainers to expand what anyone thought was possible just a few years ago, making GraphQL, and learning how to query with GraphQL, a must have skillset for any developer today.
Top comments (0)