GraphQl query(read) operation

How to write a GraphQL query to read data from an API.

What is GraphQL?

GraphQL is a query language, a specification that can be implemented in different programming languages. It’s an alternative to the majorly used REST architecture. It enables you to retrieve and modify(mutate) specific data in your data source, some of its advantages being preventing overfetching and minimizing the number of round trips to your server. For instance, while with the rest architecture the client has to request all information available on a resource even if only some part of it is needed, with GraphQL the client can request and get only what they need on the resource.

GraphQL query operation

A GraphQL operation type is either a query (read), mutation (write) or subscription (steady server connection for continous realtime updates). It defines the (as you might guess) type of operation you want to do.
We’ll be focusing on a query operation to read data from an api, i’ll be using examples as used on my blog which consumes a GraphQL endpoint from a headless CMS (DatoCMS).

Object & Fields

The API has models(Objects in graphQL) that contains and can be used to query records (fields).
A basic GraphQl query is about asking for specific fields on objects.
Consider the query below that fetches data on a single Homepage post:

1
2
3
4
5
6
7
8
9
10
11
12
{
post {
title
slug
excerpt
date
tags
author {
name
}
}
}

The post object(model in the api) can be used to request data about the post title, slug, excerpt, date and tags fields(records).
The author object obviously requests data on the author name.

Result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"data": {
"post": {
"title": "GraphQL query(read) operation",
"slug": "graphql-query-operation",
"excerpt": "A GraphQL operation type is either a query(read), mutation(write) or ..."
"date": "2020-09-06",
"tags": "GraphQL",
"author": {
"name": "ken wanjohi"
}
}
}
}

The query has exactly the same shape as the result; you always get back what you expect, and the server knows exactly what fields the client is asking for.

Arguments

The query above will most probably return the latest published post, say you want to get any published post, or you want to get the first 5 posts, that’s where arguments come in, they help you specify your request. In this case the query response can be further controlled by supplying the filter argument, here the post object(model) has a slug field which can be used to get the post with that exact slug name which is unique to it.

1
2
3
4
5
6
7
8
9
10
11
12
{
post(filter: {slug: {eq: "unit-tests-with-react-testing-library"}}) {
title
slug
excerpt
date
tags
author {
name
}
}
}

Result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"data": {
"post": {
"title": "Unit testing with React testing library",
"slug": "unit-tests-with-react-testing-library",
"excerpt": "React testing library enables you to write maintainable tests for your React components"
"date": "2020-09-06",
"tags": "React, Jest, testing",
"author": {
"name": "ken wanjohi"
}
}
}
}

Aliases

Consider the code below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
post(filter: {slug: {eq: "graphql-query-operation"}}) {
title
slug
excerpt
date
tags
author {
name
}
}
post(filter: {slug: {eq: "unit-testing-with-react-testing-library"}}) {
... #same fields here
}
}

An error: “message”: “Field ‘post’ has an argument conflict: is thrown. Since the result object fields match the name of the field in the query but don’t include arguments, you can’t directly query for the same field with different arguments.
To request data on two or more identical objects you use aliases.
Using aliases, you can resolve the result into two or more objects by renaming the result of a field name to anything you want in this case, post1 and post2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
post1: post(filter: {slug: {eq: "graphql-query-operation"}}) {
title
slug
excerpt
date
tags
author {
name
}
}
post2: post(filter: {slug: {eq: "unit-tests-with-react-testing-library"}}) {
... #same fields here
}
}

Result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"data": {
"post1": {
"title": "GraphQL query(read) operation",
"slug": "graphql-query-operation",
"excerpt": "A GraphQL operation type is either a query(read), mutation(write) or subscription(steady server connection for continous realtime updates). It defines the (as you might guess) type of operation you want to do.",
"date": "2020-09-06",
"tags": "GraphQL",
"author": {
"name": "ken wanjohi"
}
},
"post2": {
"title": "Unit testing with React testing library",
"slug": "unit-tests-with-react-testing-library",
"excerpt": "React testing library enables you to write maintainable tests for your React components, It's principle: The more your tests resemble the way your software is used, the more confidence they can give you. ",
"date": "2020-09-06",
"tags": "React, Jest, testing",
"author": {
"name": "ken wanjohi"
}
}
}
}

Fragments

In the previous query you might have noticed that we re-typed all the fields for both posts (even using … #same fields here, to avoid re-typing).This is repetetive and verbose. GraphQL includes re-usable units caled fragments, they’re useful when your query has alot of shared identical fields.

In the previous query we would solve the problem as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
post1: post(filter: {slug: {eq: "graphql-query-operation"}}) {
...sharedPostFields
}
post2: post(filter: {slug: {eq: "unit-tests-with-react-testing-library"}}) {
...sharedPostFields
}
}

fragment sharedPostFields on PostRecord {
title
slug
excerpt
date
tags
author {
name
}
}

We specify the type of object in which the fragment should be used in our case it’s the record of type Post(post) automatically generated as PostRecord which is a custom type defined by the API. We then use the three dots (syntax similar to JavaScript spread operator) to assign the fragment fields to the object.
The result is the same as the previous one.

Operation name & type

In the previous examples we’ve been using the shorthand syntax by omitting the query operation type. In addition to the operation type we can also define an operation name.

Consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
#query operation type and operation name
query HomePost{
post(filter: {slug: {eq: "graphql-query-operation"}}) {
title
slug
excerpt
date
tags
author {
name
}
}
}

An operation name is explicit and should be used to avoid ambiguous code, here it’s clear that we’re requesting a homepage post.

Variables

In the previous examples we’ve been writing inline arguments in the query, we’ve hard-coded the slug string to fetch the posts.Variables enable us to use dynamic arguments.
Say we want to fetch a single post details using a dynamic slug, we would achieve that as follows:

1
2
3
4
5
6
7
8
9
10
11
12
query HomePosts($slug: String){
post(filter: {slug: {eq: $slug}}) {
title
slug
excerpt
date
tags
author {
name
}
}
}

We replace the hard-coded slug with a variable name $slug, then pass the variable name as one of the variables accepted in the query, the variable type is the defined as String.

Query variable:

1
2
3
4
#the query variable
{
"slug": "graphql-query-operation"
}

Finally we pass the variablename: value as a JSON in the query variables panel if using an API explorer such as GraphiQL or in a promise based client such as axios.

Default variables

We can also provide default values in the variables. It has to be a non-required argument, or an error: “Non-null variable $slug can’t have a default value” is thrown.When default values are provided for all variables, you can call the query without passing any variables. If any variables are provided, they will override the default values.

Consider:

1
2
3
4
5
6
7
8
9
10
11
12
query HomePosts($slug: String="unit-tests-with-react-testing-library"){
post(filter: {slug: {eq: $slug}}) {
title
slug
excerpt
date
tags
author {
name
}
}
}

Query variable:

1
2
3
{
"slug": "graphql-query-operation"
}

Directives

GraphQL directives are probably the most under-utilized features of the specification. So what are directives? They are functions which extends some functionality in GraphQL execution behaviour. A directive is defined using the @ symbol. They provide a way to dynamically alter the structure of our queries. For instance, conditionally including or skipping a field.

Consider:

1
2
3
4
5
6
7
8
9
10
11
12
query HomePost($withAuthor : Boolean!){
post(filter: {slug: {eq: "graphql-query-operation"}}) {
title
slug
excerpt
date
tags
author @include(if: $withAuthor) {
name
}
}
}

Query Variable:

1
2
3
{
"withAuthor": false
}

Query Result:

1
2
3
4
5
6
7
8
9
10
11
12
{
"data": {
"post": {
"title": "GraphQL query(read) operation",
"slug": "graphql-query-operation",
"excerpt": "A GraphQL operation type is either a query(read), mutation(write)..."
"date": "2020-09-06",
"tags": "GraphQL"
#author field excluded
}
}
}

There are two reserved query-type directives: @include and @skip in the GraphQL specification.
From the specification, the @include and @skip directive may be provided for fields, fragments spreads and inline fragments to allow for conditional inclusion and exclusion respectively during execution as described by the if argument.
When set to true, the @include directive would include the author field in the above query. The @skip directive (as you might guess) excludes the field if set to true.
Conversely, the author field won’t be queried if the @include directive is set to false, while with the @skip directive, the field will only be queried if the variable is set to false.

conclusion

That kind of sums up about the GraphQL query operation. You can use the GitHub GraphQL API to experience much about GraphQL queries and mutations.
GitHub Graphql explorer