DEV Community

Hasura for Hasura

Posted on • Originally published at hasura.io on

Modern GraphQL examples with strings, compilers, and SDKs

Modern GraphQL examples with strings, compilers, and SDKs

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.

Modern GraphQL examples with strings, compilers, and SDKs

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.

Modern GraphQL examples with strings, compilers, and SDKs

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.

Modern GraphQL examples with strings, compilers, and SDKs

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.

Modern GraphQL examples with strings, compilers, and SDKs

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.

Modern GraphQL examples with strings, compilers, and SDKs

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.

Modern GraphQL examples with strings, compilers, and SDKs

When combined with aliases that we saw above, we can begin to see some interesting use cases.

Modern GraphQL examples with strings, compilers, and SDKs

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.

Modern GraphQL examples with strings, compilers, and SDKs

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.

Modern GraphQL examples with strings, compilers, and SDKs

Or we can provide a more complex input, replacing the entire filter statement that we generate from our application code.

Modern GraphQL examples with strings, compilers, and SDKs

The variables themselves are sent as a separate parameter in the request to the GraphQL endpoint. The final body payload represents the following shape:

Modern GraphQL examples with strings, compilers, and SDKs

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.

Modern GraphQL examples with strings, compilers, and SDKs

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.

Modern GraphQL examples with strings, compilers, and SDKs

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
  }
}

Enter fullscreen mode Exit fullscreen mode

Query with Inputs

query GetItems {
  items(where: {
    value: {
      _lt: 50
    }
  }) {
    name
    value
  }
}

Enter fullscreen mode Exit fullscreen mode

Query with Variables

query GetItems($value: Integer) {
  items(where: {
    value: {
      _lt: $value
    }
  }) {
    name
    value
  }
}

Enter fullscreen mode Exit fullscreen mode

Examples of GraphQL Mutations

Basic Mutation

mutation CreateItem {
  insert_item_one(object:{
    name: "New Item",
    value: 50
  }) {
    name
    value
  }
}

Enter fullscreen mode Exit fullscreen mode

Mutation with Variables

mutation CreateItem($item: item_insert_input!) {
  insert_item_one(object: $item) {
    name
    value
  }
}

Enter fullscreen mode Exit fullscreen mode

Examples of GraphQL Subscriptions

Basic Subscription

subscription GetItems {
  items {
    name
    value
  }
}

Enter fullscreen mode Exit fullscreen mode

Subscription with Inputs

subscription GetItems {
  items(where: {
    value: {
      _lt: 50
    }
  }) {
    name
    value
  }
}

Enter fullscreen mode Exit fullscreen mode

Subscription with Variables

subscription GetItems($value: Integer) {
  items(where: {
    value: {
      _lt: $value
    }
  }) {
    name
    value
  }
}

Enter fullscreen mode Exit fullscreen mode

Examples of Fragments, Aliases, and Directives

Fragment example

fragment ItemDetails on Item {
  name
  value
}

query GetItems {
  items {
    ...ItemDetails
  }
}
Enter fullscreen mode Exit fullscreen mode

Alias Example

fragment ItemDetails on Item {
  name
  value
}

query GetItems {
  set1: items { # <---
    ...ItemDetails
  }
  set2: items { # <---
    ...ItemDetails
  }
}

Enter fullscreen mode Exit fullscreen mode

Directive Examples

query GetItems @cached { # <---
  items(where: {
    value: {
      _lt: 50
    }
  }) {
    name
    value
  }
}

Enter fullscreen mode Exit fullscreen mode

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
    }]
})

Enter fullscreen mode Exit fullscreen mode

Query with Inputs

Gql.query({
    items: [{
        where: {
            value: {
                _lt: 50
            }
        }
    }, {
        name: true,
        value: true
    }]
})

Enter fullscreen mode Exit fullscreen mode

Query with Variables

# import the $ helper from the generated client

Gql.query({
    items: [{
        where: {
            value: {
                _lt: $`value`
            }
        }
    }, {
        name: true,
        value: true
    }]
}, {
    value: 50
})

Enter fullscreen mode Exit fullscreen mode

Examples of GraphQL Mutations with GraphQL Zeus

Basic Mutation

Gql.mutation({
    insert_item_one: [{
        object: {
            name: "New Items",
            value: 50
        }
    }, {
        name: true,
        value: true
    }]
})

Enter fullscreen mode Exit fullscreen mode

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,
    }
})

Enter fullscreen mode Exit fullscreen mode

Examples of GraphQL Subscriptions with GraphQL Zeus

Basic Subscription

Gql.subscribtion({
    items: [{}, {
        name: true,
        value: true
    }]
})

Enter fullscreen mode Exit fullscreen mode

Subscription with Inputs

Gql.subscription({
    items: [{
        where: {
            value: {
                _lt: 50
            }
        }
    }, {
        name: true,
        value: true
    }]
})

Enter fullscreen mode Exit fullscreen mode

Subscription with Variables

# import the $ helper from the generated client

Gql.subscription({
    items: [{
        where: {
            value: {
                _lt: $`value`
            }
        }
    }, {
        name: true,
        value: true
    }]
}, {
    value: 50
})

Enter fullscreen mode Exit fullscreen mode

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!
    }]
})

Enter fullscreen mode Exit fullscreen mode

Alias Example

Gql.query({
    __alias: {
        set1: {
            items: [{}, {
                name: true,
                value: true
            }]
        },
        set2: {
            items: [{}, {
                name: true,
                value: true
            }]
        }
    }
})

Enter fullscreen mode Exit fullscreen mode

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
  }
}

Enter fullscreen mode Exit fullscreen mode

And you import it into your project, you can access it using an SDK style accessor such as:

const { items } = await sdk.getItems();

Enter fullscreen mode Exit fullscreen mode

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)