Fundamentals of GraphQL

Ankita Masand
The DevOps Corner
Published in
12 min readMay 18, 2021

This article is part 2 of the series on Exploring GraphQL. Check out the ohter articles from the series:

Part 1: What is GraphQL and why Facebook felt the need to build it?
Part 3: Building a GraphQL Server using NodeJS and Express

In this article, we are going to learn the fundamental concepts used in writing GraphQL queries. We will learn how to fetch particular records by passing arguments in a query at the root and nested levels, how to fetch multiple instances of the same field but with different arguments using Alias and how to write reusable units of a query using Fragments. We will also learn how variables can be used to write dynamic queries and how Directives are used in creating a result-set based on some conditions. We will also explore one more object type called Mutation later in the article.

Highlights from Part I of the series

  1. Representational State Transfer (REST) is an architectural style intended to share large amounts of data between multiple parties. The systems that use REST are called Restful systems. They are used for transferring resources between web services. REST is a simple and flexible model for sharing resources across web services. However, there are some downsides in this technique that causes serious performance issues in low-bandwidth mobile devices.
  2. Facebook built GraphQL as a replacement for REST. GraphQL is a static strong-typed query language that lets clients declaratively specify their data requirements. The clients specify the shape of the data that they need and the server responds with the exact same shape as the response.
  3. GraphQL solves some of the issues such as multiple endpoints, under-fetching, and over-fetching of resources and versioning for applications experienced while using the REST style architecture.
  4. GraphQL uses a Type System specification for writing queries. It helps in defining the structure of the schema of a query. We can use the scalar types — Int, Float, String, Boolean and ID, enumeration and object types in the GraphQL schema.
  5. We also explored GraphiQL, which is an interactive in-browser IDE.

You can read more on the above points here

GraphQL in GitHub APIs

GitHub made an announcement in Sept 2016 regarding the use of GraphQL in its public APIs. The GitHub REST APIs were responsible for handling 60% of the requests made to their servers. The REST APIs were not scalable and started impacting the performance as the applications became more complex.

Here’s an excerpt from the GitHub blog announcement -

Our responses were bloated and filled with all sorts of *_url hints in the JSON responses to help people continue to navigate through the API to get what they needed. Despite all the information we provided, we heard from integrators that our REST API also wasn’t very flexible. It sometimes required two or three separate calls to assemble a complete view of a resource. It seemed like our responses simultaneously sent too much data and didn’t include data that consumers needed. - The GitHub Blog

GitHub also faced problems while collecting the meta-information about the endpoints.

We wanted to collect some meta-information about our endpoints. For example, we wanted to identify the OAuth scopes required for each endpoint. We wanted to be smarter about how our resources were paginated. We wanted assurances of type-safety for user-supplied parameters. We wanted to generate documentation from our code. — The GitHub Blog

The above two issues clearly explain the rationale behind the switch from REST to GraphQL. With GraphQL, GitHub is now able to design its servers to send only those fields that are requested by the client. This helps in improving the performance of the mobile app clients where smaller payloads are required. GitHub has done an amazing job on building a GitHub GraphQL API Explorer with the help of GraphiQL. It includes the documentation about various types, fields and interfaces used in GraphQL APIs.

You can use this explorer to run complicated queries on your GitHub account. We would be using this explorer in the rest of the article to understand the fundamental concepts of GraphQL.

Arguments in GraphQL query

Let’s write a query to get the name, createdAtdate and the diskUsage of the first 5 repositories of the logged-in user

query {
viewer {
name,
email,
repositories(first:5) {
nodes {
name,
createdAt,
diskUsage
}
}
}
}

The root-level query is one of the object types. It is used for specifying the nested selection set. As we can see from the documentation, viewer is a field of type User. It has information about the currently authenticated user. It defines a selection set to get the name, email and the repositories of the user. Notice the use of first:5 alongside the repositories field. This is how we pass arguments in the GraphQL query. first:5 tells the server to send the first 5 repositories of the user. repositories is again an object type and the client is requesting for fields name, createdAt and diskUsage.

When you run this query in the GitHub GraphiQL, you’ll get the response in the format below:

{
"data": {
"viewer": {
"name": "Ankita Masand",
"email": "amasand23@gmail.com",
"repositories": {
"nodes": [
{
"name": "Inventory-React",
"createdAt": "2016-10-07T11:00:07Z",
"diskUsage": 2
},
{
"name": "chat",
"createdAt": "2016-10-23T05:51:25Z",
"diskUsage": 15
},
{
"name": "simple-calculator",
"createdAt": "2016-10-31T04:20:29Z",
"diskUsage": 3
},
{
"name": "Book-Store",
"createdAt": "2016-11-01T13:24:22Z",
"diskUsage": 3
},
{
"name": "pollapp",
"createdAt": "2017-01-18T05:47:38Z",
"diskUsage": 966
}
]
}
}
}
}

Please note: the response may vary as per the logged-in user.

The arguments can be passed in the GraphQL query using parenthesis () in the object field. In REST, we could only pass a set of arguments along with the URL of the endpoint. But with GraphQL, we can pass arguments even for the nested fields. This helps in writing intuitive queries on the front-end that clearly states what is required from the server. The arguments help in getting a specific set of data from the server and it can also be used for pagination. The arguments can be one of the scalar types, object types, and enum.

Let’s write a query that accepts a scalar, an object and, an enum as arguments

query {
securityVulnerabilities (
first: 5,
orderBy: { field: UPDATED_AT, direction: DESC }, severities: HIGH
) {
nodes {
package {
name
}
}
}
}

The above query is used for fetching the name of the packages that have a high-security vulnerability. securityVulnerabilities is an object type field. We are passing three arguments to this field - first is of type Int, orderBy is of type object and it includes the field and the direction to filter the vulnerabilities. field and direction are enums and UPDATED_AT and DESC are one of the values in these enums. The severities is also an enum with values as LOW, MODERATE, HIGH and CRITICAL.

Alias in GraphQL

Alias is generally used for giving another name to an entity. As you might have noticed, the query sent by the client is of type object.We can specify a field only once as fields are unique keys in objects.

We can fetch multiple instances of a field in GraphQL using Alias as below

query {
viewer {
small: avatarUrl(size: 30),
medium: avatarUrl(size: 40),
large: avatarUrl(size: 50)
}
}

The above query gets the URL of the image of the user in three different dimensions. The size argument passed to the avatarUrl is of type Int and it is used for specifying the size of the edge of a square image. small, medium and large are the aliases of the same field avatarUrl.

Here’s the response to the above query:

{
"data": {
"viewer": {
"small": "https://avatars0.githubusercontent.com/u/20657538?s=30&v=4",
"medium": "https://avatars0.githubusercontent.com/u/20657538?s=40&v=4",
"large": "https://avatars1.githubusercontent.com/u/20657538?s=50&v=4"
}
}
}

Please note: the response may vary as per the logged-in user.

The server sends back the response using the same keys for the alias as used in the query on the client side.

Fragments in GraphQL

Fragments are used for defining the reusable parts of a query. Let’s say, we have to display the list of forked repositories and the ones created by the user (repositories that are not forked).

The query for dealing with this use-case can be written as

query {
viewer {
forked: repositories (
isFork: true,
first: 5,
orderBy: { field: UPDATED_AT, direction: DESC }
) {
nodes {
createdAt,
name,
diskUsage,
forkCount
}
},
unforked: repositories (
isFork: false,
first: 5,
orderBy: { field: UPDATED_AT, direction: DESC }
) {
nodes {
createdAt,
name,
diskUsage,
forkCount
}
}
}
}

The fields forked and unforked are the aliases of the repositories field. Please note the value of isFork filter in both of these fields as true and false respectively. The other filters - first is used for getting the first 5 repositories and orderBy is used for getting the repositories in the descending order of the last updated date. We want the same fields createdAt, name, diskUsage and forkCount for both of them. We are repeating the same code again. We can solve this problem using Fragments. Fragments let us write a reusable unit of a query. Let's see how this can be achieved using Fragments

fragment respositoryInfo on Repository {
createdAt,
name,
diskUsage,
forkCount
}
query {
viewer {
forked: repositories (
isFork: true,
first: 5,
orderBy: { field: UPDATED_AT, direction: DESC }
) {
nodes {
...respositoryInfo
}
},
unforked: repositories (
isFork: false,
first: 5,
orderBy: { field: UPDATED_AT, direction: DESC }
) {
nodes {
...respositoryInfo
}
}
}
}

In the above query, we have defined a fragment respositoryInfo on type Repository. The respositoryInfo fragment is included in the query using the spread operator.

Here’s the response to the above query:

{
"data": {
"viewer": {
"forked": {
"nodes": [
{
"createdAt": "2018-11-15T02:51:16Z",
"name": "deep-learning-v2-pytorch",
"diskUsage": 129351,
"forkCount": 0
},
{
"createdAt": "2018-10-09T16:48:15Z",
"name": "react-jsonschema-form",
"diskUsage": 4956,
"forkCount": 0
},
{
"createdAt": "2018-10-09T06:07:10Z",
"name": "webpack.js.org",
"diskUsage": 321869,
"forkCount": 0
},
{
"createdAt": "2017-10-28T08:20:40Z",
"name": "guides",
"diskUsage": 29071,
"forkCount": 0
}
]
},
"unforked": {
"nodes": [
{
"createdAt": "2019-04-04T04:16:35Z",
"name": "ankitamasand.github.io",
"diskUsage": 1,
"forkCount": 0
},
{
"createdAt": "2018-12-31T15:24:10Z",
"name": "bookskeep-pwa",
"diskUsage": 716,
"forkCount": 4
},
{
"createdAt": "2019-01-27T16:23:41Z",
"name": "nlp-chatbot",
"diskUsage": 11,
"forkCount": 0
},
{
"createdAt": "2019-01-23T12:47:53Z",
"name": "virtual-stock-ml",
"diskUsage": 0,
"forkCount": 0
},
{
"createdAt": "2018-12-22T06:08:46Z",
"name": "react-chrome-ext-boilerplate",
"diskUsage": 192,
"forkCount": 0
}
]
}
}
}
}

We can also include additional fields in the query apart from the ones that are already included in the fragment respositoryInfo.

Fragments are pretty helpful in writing queries that include repetitive units. The complicated queries can be split into smaller chunks using fragments.

Variables in GraphQL

The values of the arguments in the above queries are hard-coded and this is not a real-world scenario. In the actual implementation, the users can select different values for these arguments on the user interface. For example, a user may want to order the list of repositories in the above query in ascending order. One of the ways of implementing this use case is to concatenate static and dynamic parts of the string to form a query. However, this is not a cleaner way to do things and is also not recommended in the GraphQL documentation.

We can use variables to pass dynamic parameters as

query ($first: Int, $orderBy: RepositoryOrder){
viewer {
repositories (first: $first, orderBy: $orderBy) {
nodes {
name
}
}
}
}

The above query accepts the variables $first of type Int and $orderBy of type RepositoryOrder. Now the values of first and orderBy in the repositories selection set is dynamically passed by the client. Notice the use of $ along with the name of the variable. The type of variables should also be included in the arguments. You should know the type if you are using variables of complex object types. For example, in the above query, the orderBy field is of type RepositoryOrder. However, it is optional to define the type of variables in the arguments. But it is a good practice to always the type for variables.

The variables can be sent in the JSON format as

{
"first": 5,
"orderBy": {
"field": "UPDATED_AT",
"direction": "DESC"
}
}

How to define non-null values for variables in GraphQL

If the type of the variable orderBy was not specified in the above query, we could have even sent null as the value for orderBy. If an argument is mandatory and should have a non-null value, it should be specified using the exclamation mark ! as below

query ($first: Int!, $orderBy: RepositoryOrder!){
viewer {
repositories (first: $first, orderBy: $orderBy) {
nodes {
name
}
}
}
}

Please note the use of ! in the above query.

How to define default values for variables in GraphQL

We can define default values for variables in GraphQL query as below

query ($first: Int = 5, $orderBy: RepositoryOrder){
viewer {
repositories (first: $first, orderBy: $orderBy) {
nodes {
name
}
}
}
}

Notice how 5 is specified as the value for the $first variable. If the value of first variable is not defined in the variables object, the server would assume its value as 5.

Directives in GraphQL

Directives are used for changing the structure of a query based on a condition. We can include some of the fields in a query only if a particular condition is met. Let’s say, we would want to include the list of followers in our query only when its corresponding client-side variable is set to true.

Let’s see this in action

query ($showFollowers: Boolean!){
viewer {
name,
email
followers (first: 10) @include(if: $showFollowers) {
nodes {
name,
email
}
}
}
}

Here’s the variables JSON object

{
"showFollowers": true
}

The above query is in control of the client! It sends the list of followers only when the value of the variable showFollowers is set to true. @include (if: Boolean) is a directive. It is used when fields are to be included in the result based on a condition. Currently, GraphQL supports two directives and they are listed below

@include (if: Boolean) - Used for including fields in the query result based on the value of its arguments @skip (if: Boolean) - Used to skip a field when a condition passed to it as an argument is true

Directives are a powerful feature for writing GraphQL queries. We can also write custom directives on the server-side.

Root Object types in GraphQL — Query and Mutation

As you see in the documentation of GitHub GraphQL APIs, there are two root types — Query and Mutation. Until now, we have been writing queries of root type Query. The type Query is generally used for fetching data from the server while the type Mutation is used for causing side-effects on the server. Mutations can be used for writing data on the server. For example, we can use mutation for creating, updating or deleting an object on the server.

The types Query and Mutation are objects and we can define a set of selection fields inside each of them. Like in Query, we can define multiple fields in Mutation. The fields defined in mutation will be executed in series, unlike queries where fields are executed in parallel.

These types can be defined as

// queryquery {
// ...
}
// mutation
mutation {
// ...
}

The use of query keyword while defining a query is optional. But it is recommended to always define root types for queries and mutations.

Let’s check out some of the mutation types in GitHub explorer. Head over to the root type Mutation. You will see fields like addTopicSuggestion, addComment etc. These fields take some payload as the input and make the required modifications to the data. For example, the addComment field is of type AddCommentPayload. It takes an input object of type AddCommentInput. The AddCommentInput object includes scalar fields - subjectId, body and clientMutationId. The mutation adds a comment on the supplied subject Id. It sends back the object of type AddCommentPayload that has the updated comments information.

We will learn how to write mutations in the next articles.

Conclusion

In this tutorial, we learned the basic concepts of GraphQL. Let’s recap all that we have learned so far

  1. We can pass arguments in the GraphQL query at the root as well as the nested levels. The arguments are used for getting a specific set of data from the server. They can be of scalar types, object types or enums.
  2. We can fetch multiple instances of the same field using an alias. Alias is used for defining different parameters for the same instance of a field.
  3. Fragments are used for writing reusable units of a query. They can be included in a query using the spread operator
  4. We can pass dynamic values for arguments in a query using variables. The variables can be passed to the server as a separate JSON object. We can also specify the type of variable for strict type-checking.
  5. Directives can be used for adding or removing fields from the query result based on a condition. GraphQL supports if and skip directives for checking the values of the arguments passed to the field.
  6. The types Query and Mutation are defined at the root level. Mutations are used for causing side effects to the server.

Now that you have understood all the fundamental concepts in GraphQL, we are ready to build our own GraphQL server. In the next article, we will be building a GraphQL server using Express and learn how to write resolvers to get the values of the fields in a query.

Next in series: Building a GraphQL Server using NodeJS and Express

This article was originally published on Buddy’s blog.

--

--

Ankita Masand
The DevOps Corner

Senior Frontend Engineer at Treebo | Freelance Writer