DEV Community

Cover image for How to Consume GraphQL API with Apollo Client in a Reactjs Application
Stephen Gbolagade
Stephen Gbolagade

Posted on

How to Consume GraphQL API with Apollo Client in a Reactjs Application

Now that we understand what GraphQL is and why some companies prefer it over the RESTful API, let's talk about consuming GraphQL endpoints in your Reactjs application.

If you haven't read Part 1 of this blog post, you can do so here (click)

GraphQL Operations

You need one GraphQL endpoint for your entire application to perform different operations such as:

Mutation:

Sending data you collected from the UI to the database.

Query:

Fetching data to UI

We also have other operations like Subscription, but you'll be using Mutation and Query a lot.

Before we start working with data, let's set up our project and configure it.

Although there are many ways to work with a GraphQL API in Reactjs, such as using the Fetch method, React query, or Axios, we are not going to use any of these but rather, we'll be using Apollo Client from Apollo GraphQL.

What is Apollo Client?

Apollo Client lets you manage the state of your data with GraphQL. It allows you to consume GraphQL endpoints without using any external state management.

Hint: If you've worked with React Query before, you probably have an idea of using Apollo Client to consume GraphQL endpoints because they have the same syntax and solving the same problem.

Now, let's start!

Run npx create next-app app-name to bootstrap a React project with Nextjs.

Clean it up, navigate to the root level of your project, and create a new file called Apollo-client.js

Now install Apollo Client and "Js-cookies" packages:

yarn add @apollo/client js-cookie

This command will install some dependencies into our project, and we'll use the js-cookie library to store and retrieve data in our browser's storage.

In Apollo-client.js, put this code to import everything we need to setup Apollo Client:

import { ApolloClient, InMemoryCache, HttpLink, from } from "@apollo/client";
import { onError } from "@apollo/client/link/error";
import Cookies from 'js-cookie'

import { setContext } from "@apollo/client/link/context";
Enter fullscreen mode Exit fullscreen mode

These will let us handle errors, cache data, refetch, manage the local and remote state of our operations, and retrieve cookies.

Let's set the header token. The idea is that when a user logs in to your application, they'll be given an access token, and we are now going to set the access token to the header.

Note: This is compulsory for most projects especially if it's private as only authenticated people will be given access to interact with the APIs.

So update your apollo-client.js file with:

const authLink = setContext((_, { headers }) => {
  // get the authentication token from local storage if it exists
  const authToken  = Cookies.get("secret__");
  // return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: `Bearer ${authToken}`
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

Leave apollo-client.js file for now, create another file called env.local which is used to store private keys and values.

In env.local file, let's store our GraphQL endpoint:

NEXT_PUBLIC_API_URL=https://your-graphql-api.com

Now go back to apollo-client and add this code:

const httpLink = new HttpLink({
  uri: `${process.env.NEXT_PUBLIC_API_URL}/graphql`,
  credentials: "include",
});
Enter fullscreen mode Exit fullscreen mode

Let's handle any potential error, in the apollo-client.js file, add:

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors)
    graphQLErrors.forEach(({ message, locations, path }) =>
      console.log(
        `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
      ),
    );

  if (networkError) console.log(`[Network error]: ${networkError}`);
});
Enter fullscreen mode Exit fullscreen mode

Now, we need to make this apollo-client.js accessible every time we need it. In the same apollo-client.js file, add:

const client = new ApolloClient({
    cache: new InMemoryCache(),
    connectToDevTools: true,
     link: authLink.concat(httpLink),
});

// Export it

export default client;
Enter fullscreen mode Exit fullscreen mode

We are done setting up the apollo-client file. But do you notice this line in the client:

connectToDevTools: true

This allows you to explore your GraphQL API in your browser powered by Apollo Client. If you leave the value as true, you'll see a new Tab called Apollo added to your tab when you inspect the page.

The full code for the apollo-client.js is this:

import { ApolloClient, InMemoryCache, HttpLink, from } from "@apollo/client";
import { onError } from "@apollo/client/link/error";
import Cookies from 'js-cookie'

import { setContext } from "@apollo/client/link/context";

const authLink = setContext((_, { headers }) => {
  // get the authentication token from local storage if it exists
  const authToken  = Cookies.get("secret__");
  // return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: `Bearer ${authToken}`
    }
  }
});

const httpLink = new HttpLink({
  uri: `${process.env.NEXT_PUBLIC_API_URL}/graphql`,
  credentials: "include",
});

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors)
    graphQLErrors.forEach(({ message, locations, path }) =>
      console.log(
        `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
      ),
    );

  if (networkError) console.log(`[Network error]: ${networkError}`);
});

const client = new ApolloClient({
    cache: new InMemoryCache(),
    connectToDevTools: true,
     link: authLink.concat(httpLink),
});

// Export it

export default client;
Enter fullscreen mode Exit fullscreen mode

If we are on the same page, we can now start working with GraphQL endpoints.

Let's start with Mutation

How to: Mutation in GraphQL

Mutation simply means to change or alter.

When you're collecting data from user and sending it to the backend, something is changing.

For example, the data of such a user is changing from an empty state to a filled-up state.

So generally, GraphQL mutation operations on frontend mean you're sending in data. And let's demonstrate this by building a simple Register component.

Create a new file inside the 'component' file and call it RegisterUser.jsx and put your registration form.

// RegisterUser.jsx

const RegisterUser = () => {

const [registerData, setRegisterData] = useState({
 name: " ",
 age: " ",
 password: " "
})

// handle form change

const handleChange = (e) => {
 setRegisterData({...registerData, [e.target.name]: e.target.value})
}

// Handle submit function

const handleRegister = (e) => {
 e.preventDefault()

try {
 if(registerData) {
  console.log(registerData)
}
} catch(e) {
 console.log(e)
}
}


return (
  <section>

  <form on submit={handleRegister}>
     <input name="name" value={registerData.name} />

<input name="age" value={registerData.age} onChange={handleChange} />

<input name="password" value={registerData.password}
onChange={handleChange}
 />

<button type="submit" >Register</button>

  </form>

</section>
)
}
Enter fullscreen mode Exit fullscreen mode

This is a simple form, you can style it yourself. The next thing is to submit the form to Backend.

We don't have an endpoint for this, let's assume we have one, here is what we need to do:

Create the mutation

Import the useMutation hook provided by Apollo Client
Pass the user data as the variable to the mutation.

HINT: Writing the query or mutation syntax can be time-consuming because you have to ensure everything is correct. But no worries, GraphQL is introspective, use this website to see and write your queries and mutations: https://studio.apollographql.com/, just put your GraphQL API and it will show you everything on that API.

You can as well use the playground by clicking on putting your API link in the browser to see something like this:

GraphQL API Playground

So, you have 3 options to introspect your endpoint: Apollo DevTools, Apollo Studio or the default playground.

Let's continue.

Create the mutation

To create the mutation, import gql.

In the register file:


import {gql} from @apollo/client;

// Create the mutation

const REGISTER_MUTATION = gql` 
mutation Register($name: String!, $password: String!, $age: String!) { registerUser(registerInput: { name: $email, password: $password, age: $age}) {
 _id 
age
name 
} } `;

Enter fullscreen mode Exit fullscreen mode

Before we move on, let me explain the mutation above.

We store the mutation as “REGISTER_MUTATION” which you can use anything. Then, we use the gql exported from apollo-client to serialize the mutation to javascript understandable code format.

Then this line: mutation Register($name: String!, $password: String!, $age: String!)

This is called the variables: ($name: String!, $password: String!, $age: String!), that is, the expected data to be sent to the backend. The exclamatory sign ”!” shows that the data is required, if you don’t send it to the backend, you’ll see “BAD REQUEST” error.

And finally, we return some data if the operation is successful:

{
 _id 
age
name 
}
Enter fullscreen mode Exit fullscreen mode

It is optional to return these data.

Now, let’s continue.

  1. Import the useMutation hook from the Apollo client which we will use to run this operation.
  2. Initiate the mutation using the hook.
  3. and finally, create the handleSubmit function that will send this data to the backend.

Import {useMutation} from @apollo/client

// Initiate the mutation like this:

const [RegisterUser, {data, error, loading}] = useMutation(REGISTER_MUTATION, {
 variables: {
  // pass the form state here as values to the variables
age: registerData.age
name: registerData.name
Password: registerData.password

}
});

// handle Form submit

const handleSubmit =  async () => {
  // call the mutation when submit button is clicked in a try-catch block

 try {
await RegisterUser()

} catch(error) {
console.log(error)
}

 }
Enter fullscreen mode Exit fullscreen mode

Note this line: {data, error, loading}, this the default destructured props given to you by Apollo client to track the progress of your request.

  • data - The data returned by the operation
  • error - If there is error when running the operation
  • loading - If the request is still processing.

You can use these properties in your component, for instance, you can disable the submit button if the request is still loading and display error if it fails.

Finally, go to form submit button and invoke the function there:

<button type="button"  onClick={handleSubmit} disabled={loading}>Register</button>
Enter fullscreen mode Exit fullscreen mode

That’s it and you’re done.

The full code now looks like this:

import {gql, useMutation} from @apollo/client;

const REGISTER_MUTATION = gql` 
mutation Register($name: String!, $password: String!, $age: String!) { registerUser(registerInput: { name: $email, password: $password, age: $age}) {
 _id 
age
name 
} } `;


export const RegisterUser = () => {

const [registerData, setRegisterData] = useState({
 name: " ",
 age: " ",
 password: " "
})

// handle form change

const handleChange = (e) => {
 setRegisterData({...registerData, [e.target.name]: e.target.value})
}

// Initiate the mutation like this:

const [RegisterUser, {data, error, loading}] = useMutation(REGISTER_MUTATION, {
 variables: {
  // pass the form state here as values to the variables
age: registerData.age
name: registerData.name
Password: registerData.password

}
});

// handle Form submit

const handleSubmit =  async () => {
  // call the mutation when submit button is clicked in a try-catch block

 try {
await RegisterUser()

} catch(error) {
console.log(error)
}

 }



return (
  <section>

{/*If  there is an error, return the error message */}


{error && <p className=error>{error.message}</p>

  <form>
     <input name="name" value={registerData.name} />

<input name="age" value={registerData.age} onChange={handleChange} />

<input name="password" value={registerData.password}
onChange={handleChange}
 />

<button type="button"  onClick={handleSubmit}>Register</button>



  </form>

</section>
)
}

Enter fullscreen mode Exit fullscreen mode

Now, let’s talk about Query:

How to: Query in GraphQL

Whenever you want to fetch data from the backend, you use Query and you use a hook from apolloClient called useQuery.

Just like the mutation we did above, the same syntax will be used here. But it is worth noting that some Queries might require variables while some don’t.

To demonstrate this, I will use a sample Query markup.

Assuming you want to fetch all the users who register on your website, the query will look like this:


{
AllUsers {
// Return the data you need

age,
name
}
}
Enter fullscreen mode Exit fullscreen mode

In the query above, we did not pass any variable, we just fetch directly.

A scenario where you can use a variable in Query is when you are fetching data for a specific user. For instance, let’s say you want to fetch a user’s data with his _id, the query will look like something like this:

   user(_id: “12”} {
  age
  name
}
Enter fullscreen mode Exit fullscreen mode

Here we are passing the variable to it directly, so we will see a user with an id of 12.

As long as these two samples are valid, it is not a good practice to run a query like that as a front-end developer. Imagine how we hard-code the variable, it has to be dynamic.

So let’s use a good sample, let’s assume we want to fetch all users and a specific user, the full code is as follow:

Import {useQuery, gql} from @apollo/client
// store the query here

Const FETCH_ALL_USERS = gql`
  AppUsers {
  AllUsers {
  age
 name
}
}
`

const AllUsers = () => {

const {data, error, loading} = useQuery(FETCH_ALL_USERS) // we don’t need to pass any variables

 return (
  <section>
 <h2>name : {data.AllUsers.name} </h2>
 <h2>age : {data.AllUsers.age} </h2>

</section>

)

Enter fullscreen mode Exit fullscreen mode

And we are done.

Note, don’t just return data by saying data.name in this scenario. Check the data structure of the query before you return.

In a case where you need to pass a variable:

Import {useQuery, gql} from @apollo/client
// store the query here

Const FETCH_USER = gql`
  SingleUser(_id: string!) {
  User(_id: $_id) {
  age
 name
}
}
`

const User = () => {

const {data, error, loading} = useQuery(FETCH_USER, {
 // pass the id as variable

 variables: {
_id: USER_ID_HERE
}
})

 return (
  <section>
 <h2>name : {data.User.name} </h2>
 <h2>age : {data.User.age} </h2>

</section>

)

Enter fullscreen mode Exit fullscreen mode

And we are good.

In the previous series, I said we can track the state of our operation which is one of the benefits of using GraphQL over RESTful API and we have seen how to we use the data, loading, and error properties, but Apollo client provides you with more such as:

  • onCompleted: Do something if the operation is successful
  • onError: Do something if there’s an error

And many more, here is how to use it like the variables:


const {data, error, loading} = useQuery(FETCH_USER, {
 // pass the id as variable

 variables: {
_id: USER_ID_HERE
},

onCompleted() {
  // trigger something like a success modal or snackbar here
},

onError() {
// trigger something like an error modal or snackbar here}
})

Enter fullscreen mode Exit fullscreen mode

So that’s it! To learn more about GraphQL, I will advise you check Apollo GraphQL documentation here, they have the best documentation you can trust.

Thanks for reading and don’t forget to ask questions, share this with something and follow me 🙏

Top comments (0)