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";
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}`
}
}
});
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",
});
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}`);
});
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;
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;
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>
)
}
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:
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
} } `;
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
}
It is optional to return these data.
Now, let’s continue.
- Import the
useMutation
hook from the Apollo client which we will use to run this operation. - Initiate the mutation using the hook.
- 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)
}
}
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>
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>
)
}
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
}
}
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
}
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>
)
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>
)
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}
})
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)