Sending First GraphQL Queries

Learn the basics of GraphQL query language and use it to interact with the GitHub API.

Let’s learn how to manually send some simple queries to an existing public GraphQL API. Once we’re comfortable with using GraphQL, we’ll start working on automating the queries with the help of the application.

GitHub GraphQL API

There is no shortage of public GraphQL APIs, but we’ll use GitHub’s API as our training ground in this lesson. There are a few reasons for this choice. First, its API is easy to access because it’s available to everyone with a GitHub account. Second, GitHub’s API is considered one of the best and can be a good source for learning best practices when designing an API. And last but not least, almost everyone is familiar with what GitHub is.

GitHub provides a handy UI tool for exploring and interacting with the API, which is available here.

This tool allows us to send GraphQL queries to GitHub, inspect responses, and explore the API documentation.

As we proceed through each lesson, it can be helpful to go to the GitHub page and run the queries to see how they work. They don’t even need to be modified. Before getting started with the API, we suggest logging into your GitHub account. To do this, click the green button at the top right of the Explorer screen.

The interface of this GraphQL explorer has three parts:

  • A query editor on the left.
  • A documentation explorer on the right that provides API documentation.
  • A middle section that will contain the results of the last sent query.

We’ll discuss query variables in one of our later lessons, so we can ignore it for now.

GitHub's GraphQL Explorer
GitHub's GraphQL Explorer

To run a query, we’ll enter the text of the query in the left section and then click the “Play” button at the top.

Getting info about the current user

Let’s start with a simple GraphQL query and break it down step-by-step. For our first query, let’s get some basic information about a user currently logged in to GitHub.

We can do it with the following query:

query {
viewer {
login
isHireable
name
location
}
}

Before we run it, let’s learn how it’s structured. The outer layer of this code snippet contains the word query and curly brackets.:

query {
...
}

This first keyword specifies which operation we want to perform with the GraphQL API. Using query means we want to fetch data using the API, but we also have two other options:

  • mutation: This changes the data on the server.

  • subscription: This retrieves a stream of real-time updates from a GraphQL server.

We’ll look into these other operations later, so let’s focus on queries for now.

The following layer specifies the name of the query we want a GitHub server to execute. In this case, we want to execute a query named viewer.

query {
viewer {
...
}
}

As we briefly discussed in the previous lesson, each GraphQL API provides many queries that work as entry points in a graph of objects. The viewer query returns information about the currently logged-in user.

The last piece of information we need to provide is a list of fields we want to get from the object returned by the viewer query.

query {
viewer {
login
isHireable
name
location
}
}

In this case, we want to get login, isHirable, name, and location. The viewer query can return a myriad other fields as well as other objects related to a current user, and later we’ll see how we can discover all the available fields.

If we run this query, the GraphQL explorer will send it to a server and display a response.

{
"data": {
"viewer": {
"login": "mushketyk",
"isHireable": false,
"name": "Ivan Mushketyk",
"location": "Dublin, Ireland"
}
}
}

Notice that the structure of the response matches the query. In the data object, we have a viewer field that contains the return value for the viewer query. It also contains the four fields that we requested from the API.

The result will be the same for any other GraphQL query that we execute. A query defines the shape of the data that a server should return, and all successfully executed queries will return data with the shape defined by a client.

GraphQL schema

Let’s learn how to find a list of queries in the GitHub API and how to get a list of the fields each query can return. This is defined by the GraphQL schema, another core GraphQL concept.

The GraphQL schema is defined for each GraphQL API, specifying what types exist in an API, how these types are related, and what queries and mutations we can use to fetch or update this data. Since the GraphQL schema is such a foundational concept and defines what a GraphQL can do, we’ll learn about it first.

The GraphQL schema is represented using a special language. We can get the whole schema for the GitHub API here, but for now, let’s look at the relevant parts for the viewer query shown below, with the irrelevant comments and fields removed.

type User {
login: String!
isHireable: Boolean!
name: String!
location: String
... # Other fields
}
type Query
... # Other queries
viewer: User!
}

As we can see, this snippet contains two types of definitions. Type User shows a list of available fields on a User object. The GraphQL API is strongly typed, and each field has a type associated with it. We can either define our types or use one of the built-in GraphQL types:

  • Int: A signed 32-bit number.
  • Float: A signed floating-point number.
  • String: A UTF‐8 string.
  • Boolean: Either a true or false value.
  • ID: This is the same thing as a string, but specifies that this is a non-human readable ID.

Notice that in most of the fields in the code example above, the name of each type ends with the ! character. This specifies that a value for this field should be non-null and is always defined when returned by a server.

The definition of the location field is simply the String type, which means it can be null or a string.

location: String

Which means that it can be null or a string.

The second type defined in the schema above defines all queries, including the one we were using in previous examples.

type Query 
  ... # GitHub has many other queries, but we will only focus on this one for now
  viewer: User!
}

It specifies that the viewer query will return an object of a type User, and the ! character means that it will always be non-null.

Of course, we could download a GitHub schema to learn about what types we can use, but a better way to find relevant type definitions is to use the documentation section of the GraphQL explorer. We can search for either specific queries or types.

Most GraphQL explorers, including the one used by GitHub, also implement auto-completion, which you can activate by using the Ctrl+Space shortcut.

Fetching nested objects

As we discussed in the previous lesson, we can also use GraphQL to fetch nested fields. In GitHub API, each user can have a current status that contains a text message and an emoji, and we can get it using the status field on a User type.

This is how the status field is defined in the GraphQL schema:

type User {
# Fields that we used before
login: String!
isHireable: Boolean!
name: String!
location: String
# This is the field that we want to query
status: UserStatus
... # Other fields
}
## A type refered by the "User" type
type UserStatus implements Node { # We will discuss "implements" keyword later
# All fields available
createdAt: DateTime!
emoji: String
emojiHTML: HTML
expiresAt: DateTime
id: ID!
indicatesLimitedAvailability: Boolean!
message: String
organization: Organization
updatedAt: DateTime!
user: User!
}

There’s a lot to unpack in this example. First, the schema specifies that we can access a user’s status through the status field. The status is defined in the UserStatus type, and it contains some new types that we haven’t discussed yet . The DateTime and HTML types are specific types for strings in a particular format, and Organization is another type with multiple fields that we won’t use in these lessons.

Now let’s try to write a query that returns information about the current user with its status. Since the status field returns an object with multiple fields, we can’t write a query like this:

query {
viewer {
login
isHireable
name
location
status # this is not allowed
}
}

Instead, we need to explicitly list all the fields that we want a server to return from the status field, even if we want all the fields an object has.

To get a status for the current user we could send a query like this:

query {
viewer {
login
isHireable
name
location
status {
message
emoji
expiresAt
}
}
}

In this example, GitHub returns this response:

{
"data": {
"viewer": {
"login": "mushketyk",
"isHireable": false,
"name": "Ivan Mushketyk",
"location": "Dublin, Ireland",
"status": {
"message": "Building a new course",
"emoji": ":hammer_and_wrench:",
"expiresAt": "2021-07-11T20:59:59Z"
}
}
}
}

Notice that the UserStatus type has a reference to a user (type User) that set this status.

query {
viewer {
status {
user {
status {
user {
status {
message
}
}
}
}
}
}
}

A query like that would return a complex structure like this:

{
"data": {
"viewer": {
"status": {
"user": {
"status": {
"user": {
"status": {
"message": "Building a new course"
}
}
}
}
}
}
}
}

This doesn’t get us anything in this particular case, but it shows that we can request arbitrary complex nested structures using GraphQL API.

Client queries

So, how does a client send a GraphQL query?. We can inspect this easily. First, let’s open a Network tab in our browser’s developer tools, and run a GraphQL request. The result is a single POST request to https://graphql.github.com/graphql/proxy, simply sent as a string in a JSON object.

GraphQL request in Google Chrome's developer tools
GraphQL request in Google Chrome's developer tools

Summary

In this lesson, we’ve covered two new GraphQL concepts, sending queries and GraphQL schema. We saw how we can construct simple GraphQL queries and how to fetch nested fields.

Exercises

Let’s try out our newly acquired skills! Try to send a GraphQL query that gets bio information about our GitHub user. This is represented as a string in GraphQL, and we can get it using the bio field on the User type. Notice that it’s defined as a nullable string, so we can get null if it’s not set for a user.