How to Write a GraphQL Query
A beginner’s guide to writing your first GraphQL query
Today, we will explore the basics of GraphQL and learn how to structure queries and retrieve data from a GraphQL API. While no prior experience with GraphQL is required, having some familiarity with React and TypeScript will be helpful in understanding the concepts we’ll cover. Whether you’re a seasoned developer or a newbie like me, this guide will provide a comprehensive introduction to help you get started with writing GraphQL queries using Apollo Client.
I’ll be sharing my tips and tricks along the way, and I hope that this article will be a valuable resource for anyone looking to learn about GraphQL. Feel free to explore the complete codebase in the GitHub repository.
Do you know what GraphQL is?
In short, GraphQL is an open-source query language that was developed with the aim of facilitating communication between client and server in web applications. It was created by Facebook in 2012 and quickly gained popularity among developers due to its innovative and efficient approach to fetching and manipulating data.
The great thing about GraphQL is that it allows for faster and more scalable web applications, with cool features such as intelligent caching, strong typing, querying for specific fields, multiple operations in a single request, and much more. As a result, we end up simplifying the code, making it easier to understand and maintain. And the best part? We save time because we don’t have to write as much logic for fetching and manipulating data from scratch.
Understanding GraphQL queries
Basically, a GraphQL query allows clients to retrieve only the data they need and nothing more from a GraphQL API.
To write a GraphQL query, you need to specify the data you want to retrieve and the structure in which you want it to be returned. This is done using a query language that is similar to JSON, where you can specify which fields you want to retrieve, and any filters or sorting you need to use. Then, the GraphQL server uses that query to grab the data you requested and sends it back to you in a nice, tidy JSON format.
In this article, we will utilize the Rick and Morty API GraphQL playground to test. There, you can experiment with writing your own queries and seeing the data that comes back. It’s a great way to get a feel for how GraphQL works and to see the benefits of being able to request exactly the data you need. So go ahead and give it a try — you might just have some fun while you’re at it!
About how to use it, let’s say we want to retrieve the name, gender, and status of all the characters from the Rick and Morty API.
We can write the following query:
query {
characters {
results {
name
gender
status
}
}
}
In this query, we are using the query keyword to indicate that we want to retrieve data from the API. We then specify the name of the endpoint we want to query, in this case, characters. Finally, we specify the fields we want to retrieve, which are name, gender, and status.
The response from a GraphQL query is also structured in a JSON-like format. Here is an example of what the response to our query might look like:
{
"data": {
"characters": {
"results": [
{
"name": "Rick Sanchez",
"gender": "Male",
"status": "Alive"
},js
{
"name": "Morty Smith",
"gender": "Male",
"status": "Alive"
},
...
]
}
}
}
In this response, the data is returned in the same structure as our query. The data field contains the results of our query, which includes the characters field and the results subfield that contains an array of character objects, each with the name, gender, and status fields that we requested.
It’s time to roll up your sleeves and get to work: Let’s fetch a GraphQL query using Apollo Client.
The first thing to know is that Apollo Client is a powerful JavaScript library that helps you work with GraphQL APIs. It’s not necessary to use Apollo Client to write a GraphQL query, but it can make your life a lot easier if you do.
One of the main benefits of using Apollo Client is that it abstracts away a lot of the low-level details of working with GraphQL APIs. It handles things like caching, network requests, and error handling, so you can focus on writing your application code instead of worrying about these details.
Step by step to starting a project:
- Before everything, you need to create a React project with Typescript. I recommend using the T3 App template, which has excellent documentation and is simple to use. Simply run the following command:
npm create t3-app@latest
- After creating the project, install Apollo Client by running the following command:
yarn add @apollo/client graphql
3. With the basic tools installed, organize the project architecture by following the steps below:
a. Create a folder named apollo
inside the src
folder of your project.
b. Create a file named index.tsx
inside the apollo
folder
c. Create a second folder named codegen
inside the apollo
folder. — We will see more about codegen
later. The project architecture should look similar to this:
src
└── apollo
| └── codegen
| └── index.tsx
└── ...
4. With the environment configured, it’s finally time for some fun: Let’s initialize our Apollo Client inside the index.tsx
file:
a. To start let's import only the symbols we need from @apollo/client
import { ApolloClient, InMemoryCache } from “@apollo/client”;
b. After that, we’ll start our Apollo Client by configuring the uri
and cache
objects, passing only the data we’ll be using. In this case, we’ll use The Rick and Morty API, and for now, to not worry about the ‘cache’, we’ll use only InMemoryCache
.
export const apolloClient = new ApolloClient({
uri: "https://rickandmortyapi.com/graphql",
cache: new InMemoryCache()
});
uri
indicates theURL
of our GraphQL server.cache
is an instance ofInMemoryCache
, which stores query results retrieved by Apollo Client.
5. To be able to use Apollo in our React application, we need to wrap it in the Apollo Client as shown in the example below from the _app.tsx
file:
import { type AppType } from "next/dist/shared/lib/utils";
import "@/styles/globals.css";
import { ApolloProvider } from "@apollo/client";
import { apolloClient } from "@/apollo";
import Head from "next/head";
const MyApp: AppType = ({ Component, pageProps }) => {
return (
<ApolloProvider client={apolloClient}>
<Head>
<title>GraphQL and Morty</title>
</Head>
<Component {...pageProps} />;
</ApolloProvider>
);
};
export default MyApp;
6. This step is optional, but strongly recommended for TypeScript users. Anyway, we will use the GraphQL Code Generator to simplify the generation of API typings.
- GraphQL Code Generator is a powerful tool that automates the process of generating type-safe code from your GraphQL schema. With just a few lines of configuration, you can generate code for your client-side application in a variety of languages, including TypeScript, JavaScript, and more.
a.First, to install the required packages you need to run the following command:yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers
. This will install the necessary dependencies for generating TypeScript code from your GraphQL schema.
b. Second, to initiate the configuration by running the command:Yarn graphql-codegen init
. This will create a configuration file for you to customize and set up according to your needs.
c. Next, run the following command: yarn graphql-code-generator init
d. At Last, generate the code by running the command: yarn codegen
.This will generate type-safe code for your client-side application based on the schema defined in your GraphQL API.
With these simple steps, you can automate the process of generating type-safe code from your GraphQL schema and enjoy the benefits of increased productivity and reduced potential for errors in your client-side application.
Now we can write our first GraphQL query
To query data from the Rick and Morty API, we can use a React Hook known as useQuery
from Apollo Client. This function takes a GraphQL query defined using the gql
tagged template literal as its argument.
You can get a better understanding by checking out the example below:
const result = useQuery<{ characters: Characters }>(
gql`
query Characters {
characters {
info {
count
next
}
results {
created
gender
id
image
name
species
status
type
}
}
}
`
);
- Note that if you’re using Visual Studio Code, I recommend downloading the Apollo GraphQL extension to make your query more readable.
The following example aims to fetch character information and display it in an infinite scroll list. To implement this, the $page
parameter of type Int
must be added to the GraphQL query and defined in the variables object passed as the second argument to the useQuery
function
const result = useQuery<{ characters: Characters }>(
gql`
query Characters($page: Int) {
characters(page: $page) {
info {
count
next
}
results {
...
}
}
}
`,
{
variables: {
page: 1,
},
},
);
The code above demonstrates that when the query is executed for the first time, the value $page
is set to 1
in order to retrieve the first page of the results.
Successively, to create the infinite scroll logic, we’ll create a state called isFetchingMore
that is used to control whether the application is fetching more data or not.
const [isFetchingMore, setFetchingMore] = useState(false);
Later, a callback
function called fetchNextPage
is defined to fetch the next page of data. The callback
function is defined using the useCallback
function from React to ensure that the function is only created once and reused in subsequent renders.
const fetchNextPage = useCallback(async () => {
if (
loading ||
isFetchingMore ||
!data?.characters.info?.next ||
!inView
) {
return;
}
setFetchingMore(true);
await fetchMore({
variables: { page: data.characters.info.next },
});
setFetchingMore(false);
}, [data, fetchMore, inView, isFetchingMore, loading]);
Within the callback
, a check is made to prevent the function from being executed in unwanted situations. First, it is checked whether the loading state of the current query is true, indicating that the application is already fetching more data. If it is true, the function is aborted.
Next, it is checked if the isFetchingMore state is true. If it is true, the function is also interrupted, indicating that the application is already fetching more data.
Then, it is checked if the next page of data exists data?.characters.info?.next
and if the element is visible on the screen inView.
If either of these conditions is not met, the function is also interrupted.
If all the above conditions are met, the state of isFetchingMore
is set to true, indicating that the application is fetching more data.
Subsequently, the fetchMore
function is executed with the variables parameter set as {page: data.characters.info.next},
retrieving the next page of data. The await keyword is used to ensure that the function waits for the response of the query before proceeding.
Finally, the state of isFetchingMore
is set back to false, indicating that the search has been completed. This logic allows the application to search for new data only after the previous search has been completed.
To implement the infinite scroll effect was used the Intersection Observer (IO) API detects when an element, defined as a reference, enters the screen. When the element is visible, the fetchNextPage
function is called to fetch the next page of data and add it to the list.
useEffect(() => {
const timeOut = setTimeout(() => {
if (inView) {
fetchNextPage();
}
}, 100);
return () => clearTimeout(timeOut);
}, [inView, fetchNextPage]);
If you’re interested in learning more details, you can check out the full code in the GitHub repository.
After that, when the user scrolls down to the end of the character list, a new query is executed to fetch the next page of results. The page number is determined by the next
property in the info
object returned by the previous query. If next
is null
, it means there are no more pages to fetch.
We’re almost there! Let’s work on the final piece of the code
To complete our project, we need to go back to our Apollo Client and customize our cache.
Within the cache configuration, it’s possible to define type policies for each type of data that will be stored. You can check by the following example:
export const apolloClient = new ApolloClient({
uri: "https://rickandmortyapi.com/graphql",
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
characters: {
keyArgs: false,
merge(existing: Characters, incoming: Characters) {
return {
...incoming,
results: [
...(existing?.results || []),
...(incoming?.results || []),
],
} satisfies Characters;
},
},
},
},
},
}),
});
In this example, the type of policy is defined for the Query
type and the characters
field. Inside the characters
field, the keyArgs
property is being set to false, which means there will be no argument keys.
The merge
property is responsible for merging the data received from the API with the existing data in the cache. This property defines a function that takes two arguments, existing
and incoming
, representing the existing data and new data, respectively. The function uses the spread operator to merge the arrays of results from the existing and new data, ensuring that no data is duplicated.
It’s worth noting that the satisfies Characters
is a type check that ensures the function’s return value conforms to the Characters
type.
Finally, you can style, use any tools you want and try out new utilities!
Note that, like every basic guide, there is much more to explore! Now that we know the basics, we can delve deeper and explore other amazing features of GraphQL, such as mutations, subscribers, and even create our own GraphQL server!
Who knows, maybe this will be the topic of my next article, huh? So, stay tuned and keep following my content to learn more about it! See you next time (: