If you have some experience working with GraphQL servers and GraphQL query language, you must have used GraphQL playgrounds on localhost or online. On these playgrounds, you can test your GraphQL queries and mutations against the GraphQL server to fetch data and make changes. You can also use variables to make these queries and mutations dynamic. So, the GraphQL playground is running on the client side as a testing tool for developers. However, when it comes to the real world, you can not ask you clients to run queries and mutations to fetch or add new data. And this is where you need a GraphQL Client program. Therefore, in this post, we are going to discuss how to use Apollo client as a GraphQL client with React applications.
If you stumble upon this post with zero understanding of GraphQL, first, read the following posts, and then come back to this one.
But, if you want to have an in-depth understanding of GraphQL I would highly recommend you to follow this series in GraphQL.
Table of Contents
What is Apollo client?
There are many Graphql clients on the market with different levels of complexity. But, we will focus on how to use the Apollo client to fetch data from the backend server that supports GraphQL.
As a reminder, a GraphQL client is a program that you can use to send queries and mutations to the GraphQL server. You can do this with just HTML and vanilla javascript without using any GraphQL client. But. that can be cumbersome as your code grows. Therefore, the best way is to use a GraphQL client such as Apollo client.
We are not going to create the backend GraphQL server here. Therefore, I am using https://graphqlzero.almansi.me/api as a mock GraphQL server. Please look at their documentation to get familiarized with the queries and mutations that you can run. The documentation is pretty easy to understand if you have basic knowledge of GraphQL.
Setting up React with Apollo client
First, we need to create a React project. I am using Vite.js for this.
Run npm create vite@latest
on your terminal and follow the prompts:
Project name: graphql-react
framework: React
variant: Javascript
It will create a folder of project files with vite.js configuration. After that.
cd graphql-react // go into the project file
npm install @apollo/client graphql
//If you prefer, you can run this command later, after we added some code
npm run dev
Now, open your root project folder in a text editor.
First thing first, we got to do some cleaning up.
- Remove all the files inside the
/src
folder exceptmain.jsx
andApp.jsx
. - Now copy and paste the following code to
main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client'
import { ApolloProvider } from '@apollo/client';
import App from './App';
import client from './api/apollo'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
</React.StrictMode>
);
As you can see in the above code, I have imported apollo.js from ./api/apollo
.
We still do not have this file. This is where we connect our Apollo client with the Backend Apollo GraphQL server( API).
- Create a folder called
api
in the src folder - Now create a file named apollo.js in the
api
folder - Copy and paste the following code to
apollo.js
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://graphqlzero.almansi.me/api',//link to our fake server
cache: new InMemoryCache(),
});
export default client
Apollo client uses a caching mechanism in fetching data from the backend server. When you send a request to the backend, the Apollo client will try to fetch data from the in-memory cache. If it does not find that data, your query will be sent to the backend server, fetch the data, store fetched data in an in-memory cache, and send the response to the client.
If you request the same data in subsequent requests, the Apollo client will fetch the data from the cache and not from the backend API. This improves the speed and performance of your application.
This is explained clearly in this diagram at Apollo client docs in their overview of caching
How to Run GraphQL queries in Apollo client
Testing the queries on GraphQL playground.
As we are using the fake API at https://graphqlzero.almansi.me/api, we are going to use their playground to test our queries and use those queries in our application.
First visit https://graphqlzero.almansi.me/api
We are going to fetch three posts from this API. The query you should run is:
query {
posts(options:{paginate : { limit: 3 }}){
data{
id
title
}
}
}
You can learn about their queries and mutation by referring to their docs at https://graphqlzero.almansi.me/
How to add GraphQL queries to your app
How are we going to run the above query in our React application?
- In the src folder, create another folder called
graphql
- After that, create a sub-folder named
posts
insidegraphql
folder - In this posts folder, create a file and name it as
queries.js
- Now copy and paste the following code
import { gql } from '@apollo/client';
export const GET_ALL_POSTS = gql`
query {
posts(options:{paginate : { limit: 3 }}){
data{
id
title
}
}
}
`;
As you can see in the above code we have imported gql function( in more specific terms it is a template literal tag ) from ‘@apollo/client’. We are passing the queries to fetch data as a parameter of this function. If you are confused about this syntax, have a quick look at the Tagged templates on MDN. We are going to use this query in another React component. Therefore, we need to export it.
Running GraphQL Queries with useQuery hook in Apollo Client
Apple client introduces the useQuery hook to use GraphQL queries to fetch data from the backend Graphql server.
The basic usage ( syntax ) : const response = useQuery( query, options )
;
query: Represents the GraphQL query that you want to execute. It defines the data fetching operation you want to perform in your GraphQL API.
options (optional): Provides additional options and configurations for the query, such as variables, context, caching behavior, and polling. It allows you to customize the behavior of the query operation.
What does the useQuery hook return?
useQuery returns an object with many properties. Therefore we can apply destructing assignment.
const { loading, error, data } = useQuery( query, options );
data: this object contains the fetched data of your GraphQL query after it completes.
error: if your query result in either graphQL errors or network errors, you can access those using this object.
loading: this property can be utilized to show the network status of your request.
A complete reference of useQuery can be accessed at Apollo API reference for hooks.
How to use useQuery hook
Let’s see how to use useQuery in our code.
- Create a file named
getAllPosts.jsx
insrc/graphql/posts
. - Copy and paste the following code ( Note: This is not the complete code of
getAllPosts.jsx
, but it is enough at this point to understand concepts.)
import React from 'react'
import { useQuery } from '@apollo/client';
import { GET_ALL_POSTS } from './queries';
export default function getAllPosts() {
const { loading, error, data } = useQuery( GET_ALL_POSTS );
if (loading) return <p>Loading...</p>;
if (error) return <p>Error : {error.message}</p>;
return (
<div>
{ data.posts.data.map( post => <li key={ post.id }>{ post.title }</li>) }
</div>
)}
As you can notice in the above code, the userQuery
is imported from ‘@apollo/client. In addition to that, you need to import the GraphQL query to be consumed in this query, and that is why we have the statement import { GET_ALL_POSTS } from './queries'
.
To run your query, you need to pass the GET_ALL_POSTS query to the useQuery hook.
Now, replace any code in App.jsx
with the code below. Here we simply import the getAllPosts component to App.jx
.
import GetAllPosts from './graphql/posts/getAllPosts';
export default function App() {
return (
<div>
<h2>All posts</h2>
<GetAllPosts />
</div>
)}
Now, you should be able to view the titles of three posts on your browser. ( if you did not run your app before, you can now run “npm run dev” on the terminal to run the app )
How to use useQuery with variables
If you look at our query in queries.js, we are limited to getting three posts by adding 3 to the limit, which is an argument.
But what if you can specify any number for this limit argument so that you can have more control in fetching posts?
For this, you need to change the query to add variables as below.
query($limit: Int ){
posts(options:{paginate : { limit: $limit }}){
data{
id
title
}
}
}
The JSON object that you would use for testing this query would be
{
"limit": 3
}
Now, you can replace the query we already have in src/qrapql/queries.js with the query above.
Then, you also need to modify the useQuery hook. Remember, we need to pass that JSON object to run the query.
useQuery has the variables
option for this. Let’s use it and modify the code.
const { loading, error, data } = useQuery( GET_ALL_POSTS ,{ variables: { limit: 3 }} );
In the above code, the value 3 is hard coded. But, you can use a variable to replace it
const { loading, error, data } = useQuery( GET_ALL_POSTS , { variables: { limit: postLimit }});
Manual query execution with useLazyQuery hook
useQuery hook executes automatically when React component renders on the browser. However, if you want to dynamically accept data and execute a query on a user event, you need to use useLazyQuery hook.
Take a look at the following code:
const [ getPost, { loading, error, data } ] = useLazyQuery( GET_POST );
GET_POST: the query to get a specific post. It searches by the Id of the post
src/qraphql/queries.js
export const GET_POST = gql`
query( $id: ID!){
post(id: $id) {
id
title
body
}
}`;
To excuse this query, you must call the getPost
function by passing the variables option.
data.posts.data.map( post => <li key={ post.id }><a href='#'
onClick={ ()=>getPost ( { variables: { id : post.id }}) }>{ post.title }</a>
</li>)
How to execute Create, Update, and Delete operations in Apollo client
Testing the mutation on the GraphQL playground
In GraphQL mutations are used to make changes in the GraphQL server. Before you add any mutations to your application, you need to test your mutations.
As with queries, use the API at https://graphqlzero.almansi.me/api, to test the following mutations. See what this mutation returns and have a close look at the variables that hold input values.
First visit https://graphqlzero.almansi.me/api
To create a post:
mutation ($input: CreatePostInput!) {
createPost(input: $input) {
id
title
body
}
}
Variables:
{
"input": {
"title": "Test Post",
"body": "This is a test post"
}
}
To update a post:
mutation (
$id: ID!,
$input: UpdatePostInput!
) {
updatePost(id: $id, input: $input) {
id
title
body
}
}`
Variables:
{
"id": 2,
"input": {
"title": "Test Update Post",
"body": "Some updated content."
}
}
To delete a post:
mutation ( $id: ID! ) {
deletePost(id: $id)
}
Variables:
{
"id": 1
}
These mutations can be found in the docs at https://graphqlzero.almansi.me/. You can customize input fields and output fields as per your requirements.
Integrating the mutation into your app
How are we going to run the above query in our React application?
- In the
src/qraphql/posts
folder create another file namedmutations.js
- Add all you following code to this file
import { gql } from '@apollo/client';
export const CREATE_POST = gql`
mutation ($input: CreatePostInput!) {
createPost(input: $input) {
id
title
body
}}`;
export const DELETE_POST = gql`
mutation ( $id: ID! ) {
deletePost(id: $id)
}`;
export const UPDATE_POST = gql`
mutation ( $id: ID!, $input: UpdatePostInput! ) {
updatePost(id: $id, input: $input) {
id
title
body
}}`;
Create, Update, and Delete records with the UseMutation hook
UseQuey is only for fetching data. In other words, it is for READ operation in a CRUD application.
Apollo Client provides another hook called the useMutation for utilizing GraphQL mutations to make changes in the backend GraphQL API
One important thing you need to remember is that, unlike the useQuery hook, the useMutation hook DOES NOT call automatically when React component renders on the browser.
This means you always need to invoke it on a user event or any other trigger.
The most basic usage (syntax ): const [ mutateFunction ] = useMutation(MUTATION_NAME);
MUTATION_NAME is the mutation that you need to pass to the useMutation. It is the mutation operation you want to perform in your GraphQL API.
In many circumstances, we need to capture the response returned by the useMutation. The basic syntax for this is
const [mutateFunction, response ] = useMutation(MUTATION_NAME);
Here the response is an object with many properties. Therefore, we can deconstruct it to access the properties
const [addPost, { data, loading, error }] = useMutation( MUTATION_NAME );
data, loading, and error are some of the properties of The object returned useMutation hook. data, loading, and error properties are similar to the useQuery hook. You can learn more about these properties and additional properties in the mutation result in the Apollo documentation on the useMutation API.
How to useMutation hook
When you create, update, or delete backend data, you need to pass data to the backend. You can use the variables option of useMutation hook to pass dynamic data from user input.
Note: we are using a mock API in the backend. Therefore, real create, update, or delete operation does not occur. They are only simulations. But, this is not an issue to understand how the useMutation hook works.
Creating new posts
- Create a new file named
createPost.jsx
insrc/graphql/posts
, and add the code from the following GitHub Gist
createPost.jsx
: code for creating a new post
When the user submits the form above, the addPost() function is called. This function takes the variables option, which is an object with the user inputs: title and body.
onSubmit={e => {
e.preventDefault();
addPost({
variables: {
input: {
title: titleRef.current.value ,
body: bodyRef.current.value }
}});
titleRef.current.value = '';
bodyRef.current.value = '';
}}
Updating and deleting a post
When it comes to updating or deleting records how you use useMutation is not that different from how you use useMutation to create new posts. The main difference is that you need to pass the id of the selected post that you decided to update.
You can create two files named updatePost.jsx
and deletePost.jsx
, and then copy and paste the code for update and delete from these Github Gists.
updatePost.jsx
: code for updating a post
deletePost.jsx
: code for deleting a post
In updatePost.jsx
, the key things that are relevant to mutation is
- Importing the mutation query and useMutatin hook
import { UPDATE_POST } from './mutations';
import { useMutation } from '@apollo/client';
- Passing the mutation query to the useMutation hook
const[ updatePost,{ data, loading, error } ] = useMutation( UPDATE_POST );
- Calling the updatePost function on a user event. In code, it is when submitting the form.
const handleSubmit = (e)=>{
e.preventDefault();
updatePost({
variables: {
id: post.id,
input: { title , body }
}});
setAllState();//reseting states and input fields
}
Deleting a post
In this demo app, the DeletePost component is responsible for deleting a post. It is a child component of the UpdatePost component.
In deletePost.jsx
, the key parts of the code that is responsible for mutation are;
- Importing the mutation query and useMutatin hook
import { DELETE_POST } from './mutations';
import { useMutation } from '@apollo/client';
- Passing the mutation query to the useMutation hook
const [ deletePost, { data, loading , error } ] = useMutation( DELETE_POST);
- Calling the deletePost function on a user event. In code, it is when submitting the form.
const handleDelete = ()=>{
deletePost( { variables : { id : post.id }})
item.current[ post.id ].remove();
setAllState('');
}
return (
<div>
<button onClick={ handleDelete } type="submit">Delete</button>
</div>
)
Conclusion
When you want to fetch records from the backend datasource via the Graphql server, you need to have a Graphql client in the front end. And We explore the Apollo client as a GraphQL client. The useQuery and useMutation hooks are the main hooks that are available in the Apollo client to pass GraphQL queries and mutations to the GraphQL server. In addition, we also the useLazyQuery hook to fetch data on a user event in our application.
In a CRUD application, while you useQuery and useLazyQuery hooks are used to pass Graphql queries to fetch data from the backend in Read operations, the useMutation hook is used to pass mutations for Create, Update, and Delete operations in the backend API or data source
Resources
Download the full code for this post
I also add the following demo app for your reference. This is basically the same app. But, it uses React context. I also change the project structure to organize the code better.