GraphQL on the client (React And Apollo)

April 5, 2022

Traditional REST APIs are not always focused on the client. GraphQL is a query language where the client can request what they need from the server and receive that data exactly and predictably. Without much effort, one can easily pull nested data by just adding more properties to queries instead of adding multiple endpoints. This prevents issues like over-fetching that can impact performance. GraphQL is a way to send data over HTTP and is often presented as a revolutionary new way to think about APIs and seen as the successor of REST (Representational State Transfer). Indeed GraphQL can overcome major shortcomings of REST. GraphQL can be used in any client (web or a mobile application). In this article we’ll try to explore GraphQL in a web app and will use React and React Apollo libraries. Feel free to opt a language / GraphQL library of choice, things will remain the same. Let's start and get our hands dirty with some code.

Initial Setup

Before we start the real GraphQL stuff, let’s just go through setting up the project.

To create a React app, make sure you have Node.js installed on your machine. You can check, if you have Node.js installed by typing below into your terminal:

node -v

This should print the installed version of node on the terminal, If not, just go to the Node.js website to download node and install it.

Once node is installed and node -v starts showing node version, we are ready to start with React app. Open the terminal and run the command:

npx create-react-app frontend-graphql

At this point our react project should be ready to check if everything is up and running, execute the below commands on the terminal.

cd frontend-graphql
npm run start

Our frontend is ready and it's time to add the GraphQL capabilities to the application. In this article, we will use React Apollo library as a frontend GraphQL client. There are many others available. It depends on your choice of framework and development language which suits you the best. Some other popular clients in js world are graphql-request, urql, Relay, graphql-hooks.

This article is more focused on Front-End so we will not set up our own GraphQL server-side but only focus on how to fetch data from a GraphQL API using Apollo Client. To meet the purpose, we will use a public GraphQL API Space X API. This API is fetching data that provides all of the information about previous Space X launches, information about the rocket, mission, company, ship, users, and more.

Before diving into the code, some highlights about how React interacts with GraphQL and Apollo. Apollo Client is not aware of React. The only responsibility of Apollo Client is to interact with a GraphQL server and store data in Apollo Store (similar to Context / Redux or any other store). To get access to data, we link React to Apollo Client with the help of ApolloProvider. ApolloProvider will wrap our application and allow all children components/apps to interact with the store.

Installing Dependencies

Ok, that's lot of theory now let's jump to the real stuff. The next step is to install needed dependencies around GraphQL setup. We will use the minimalistic approach and use the following packages.

Make sure you are in project folder and to install these dependencies use the following command:

npm install graphql @apollo/client

Initial GraphQL Setup

Once all the dependencies are installed it's time to take a look at the initial project structure.

Resource

We will focus only to src folder where all the code lives. Look for the file index.js this is the entry point of react application.

ReactDOM.render(
  <React.StrictMode>
	<App  />
  </React.StrictMode>,
  document.getElementById('root')
);

The above piece of code is responsible for rendering the App component to the DOM. It adds the App component as a child to the element with ID root. However, the code related to GraphQL integration should be kept in a separate file but to keep it very simple we will add our code directly to index.js and will not introduce any new components

Intruduce ApolloClient

To gain the capability to interact with a GraphQL server from our application we need :

  1. An instance of ApolloClient
  2. The uri which is the URL of our GraphQL API server
  3. Apollo Client instance needs a cache to reduce the network requests and can make the application way faster.

Just to remind https://api.spacex.land/graphql/ is the URL of our public GraphQL API. To get an instance of ApolloClient add the below piece of code to index.js

import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';

const client = new ApolloClient({
  uri : 'https://api.spacex.land/graphql/',
  cache: new InMemoryCache(),
});

The only responsibility of Apollo Client is to interact with a GraphQL server and store data in Apollo Store. Now we need to pass this client throughout our component tree. ApolloProvider helps us by wrapping the application and allowing all child apps/components to interact with the store. This is how our index.js looks, after wrapping our <App /> component to <ApolloProvider />

import  React  from  'react';
import  ReactDOM  from  'react-dom';
import  './index.css';
import  App  from  './App';
import  reportWebVitals  from  './reportWebVitals';
import { ApolloClient, InMemoryCache, ApolloProvider } from  '@apollo/client';

const  client = new  ApolloClient({
  uri :  'https://api.spacex.land/graphql/',
  cache:  new  InMemoryCache(),
});

ReactDOM.render(
  <React.StrictMode>
    <ApolloProvider  client={client}>
      <App />
    </ ApolloProvider>
  </ React.StrictMode>,
document.getElementById('root')
);

reportWebVitals();

Hint: Documentation (Playground) of spacex API can be accessed Here

Implementation

Now we are done with the initial setup, so it's time to fire a query to fetch the data. To do so @apollo/client provides a hook useQuery. It accepts GraphQL query as argument, which can be created using the gql. First, we'll create a GraphQL query named USER_LIST. Remember to wrap GraphQL strings in the gql function to parse them into query documents:

Let's move to App.js and add the below code:

import { useQuery, gql } from '@apollo/client';

const USER_LIST = gql`
 query Users {
   users {
     id
     name
   }
}`;

USER_LIST is our query which is created with the help of gql and the documentation contains all information about the available parameters, queries.

Now Let's fire it with the help of useQuery hook and print the data on the UI. In your App.js replace App function with below.

function  App() {
  const { loading, error, data } = useQuery(USER_LIST);
  if(loading) return(<> Loading</>);
  if(error) return(<>{JSON.stringify(error)}</>);
  return (<>{JSON.stringify(data)}</>);
}

Finally our App.js file looks like below.

import  React  from  'react';
import { useQuery, gql } from  '@apollo/client';

const  USER_LIST = gql`
 query Users {
  users {
   id
   name
  }
}`;

function  App() {
  const { loading, error, data } = useQuery(USER_LIST);

  if(loading) return(<> Loading</>);
  if(error) return(<>{JSON.stringify(error)}</>);
  return (<>{JSON.stringify(data)}</>);
}

export  default  App;

That's where the useQuery hook comes in very handy. It returns loading, error, and data properties. This can be used to show loading and handling errors. For now, we can just display the data to check whether our app works. If everything is correct it should print the data on the screen.

Resource

While query is used to fetch data, the other important part(root type) is mutation which is used to modify server-side data. If queries are the GraphQL equivalent to GET calls in REST, then mutations represent the state-changing methods in REST (like POST, DELETE, PUT, PATCH, etc).

To explore mutation, let's add a mutation to App.js. The Space X API offers different mutations and we will use insert_users here for demo purpose. It should add a user to the database and a user/users query from Space X API should return the newly added user. To accomplish this we need below things - useMutation hook and the mutation - Input field to take the user name - A button to fire the mutation to add a user

We're adding an user. First, we'll create a corresponding GraphQL mutation named INSERT_USER. Remember to wrap GraphQL strings in the gql function to parse them into query documents:

const  INSERT_USER = gql`
  mutation InsertUser($name: String) {
    insert_users(objects: {name: $name}) {
      returning {
        id
        name
      }
    }
}`;

Next it's time to import and add the useMutation hook. INSERT_USER will be passed as argument to useMutation hook:

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

and inside the component

const [insertUser, { data: user, loading: userLoading, error: userError }] = useMutation(INSERT_USER);

Next, we'll create an input field to receive the value of name which we will store in the component state and it can be passed as an argument to INSERT_USER. Finally, our App.js looks like below.

import  React, { useState } from  'react';
import { useQuery, useMutation, gql } from  '@apollo/client';

const  USER_LIST = gql`
  query Users {
    users {
      id
      name
    }
  }`;

const  INSERT_USER = gql`
  mutation InsertUser($name: String) {
    insert_users(objects: { name: $name }) {
      returning {
        id
        name
      }
    }
  }`;

function  App() {
	const { loading, error, data: users } = useQuery(USER_LIST);
	const [insertUser, { data: user, loading: userLoading, error: userError }] = useMutation(INSERT_USER);
	const [userName, setUserName] = useState('');
	
	if(loading || userLoading) return(<> Loading</>);
	if(error || userError) return(<>{JSON.stringify(error)}</>);

	return (
    <>
	    <div>
	      <h5>User</h5>
	        <label>Enter your name: 
	          <input
	            onChange={e  =>  setUserName(e.target.value)}
	            placeholder='Enter your name'
	            type='text'
	            value={userName}
	          />
	        </label>
	        <button  onClick={() =>  insertUser({ variables: { name:  userName } })}> Add User </button>
	        {JSON.stringify(user)}
	    </div>
	    <div>
	      <h5>Users</h5>
	      {JSON.stringify(users)}
	    </div>
	  </>);
}

export  default  App;

We are ready to test the first mutation. Insert some string to the input field and add the user. It should be added to the spacex database:

Resource

Now refresh the application to re-fetch the user list and the list will include the recently added user.

Resource

In case we want to watch the changes on the user list and want to reflect the changes in the user list without refreshing the application here comes another root type subscription. Subscription is used to register for updates from the server. It allows watching for any changes to the data. For example, we might want, to be notified when a new user is added. The syntax is very similar to that of queries just add the type subscription in place of query. Our user subscription will look like below.

const  USER_LIST = gql`
  subscription Users {
    users {
      id
      name
    }
  }`;

Here we use subscription in the place of query. The only other change is to use useSubscription hook in place of useQuery hook.

Summary

GraphQL is an alternative to REST API. REST APIs need to follow a strict fixed data structure approach while fetching information, on the other hand GraphQL provides more flexibility. GraphQL allows requesting exact data not more, not less while providing a clear definition of the structure of data that can be requested from the server. Rather than accessing the entire set of information, GraphQL works by querying the pre-defined schema against the backend to populate on the client.

GraphQL advantages

This article doesn't focus on the advantages and disadvantages of GraphQL. Here we just try to highlight important ones.

Validation and type checking: GraphQL provides information about the types while discovering the schema. Users can see what the schema can query and how the data is set up. GraphQL validates the data format so no extra data validation mechanism is needed.

Data in a single API call: REST is based on individual endpoints. To collect all required data, one must combine multiple endpoints. On the other hand GraphQL can request all the data in one API call.

API documentation: GraphQL always keeps documentation in sync with API changes. The GraphQL API is tightly coupled with code, once a field, type, or query changes it is immediately reflected in the docs.

Great for complex systems and microservices:
GraphQL unifies multiple systems behind its API while hiding their complexity. This is especially useful while dealing with legacy infrastructures or third-party APIs. GraphQL API can handle communication between multiple microservices by merging them into one GraphQL schema. While each microservice defines its GraphQL schema and has its GraphQL endpoint.

Descriptive error: In case of error, GraphQL adds the error into a separate object which contains detailed information about the error while in REST we simply check the HTTP headers or the status of a response to determine what might went wrong.

API evolution without versioning: In REST, changing API is a tedious task. It needs to keep the old version live until consumers make the transition to the new one. In REST it’s common practice to add multiple API versions. GraphQL removes the need for versioning by deprecating APIs on a field level.

No over or under-fetching: REST API provides either too much data or not enough of it, creating the need for some more requests. GraphQL solves this problem by providing the exact data in a single request.

GraphQL disadvantages

Although GraphQL is a good alternative to REST, it’s not a replacement. Some points not in favor of GraphQL are:

GraphQL caching: GraphQL doesn’t utilize HTTP caching methods, it is more complicated to implement a simplified cache with GraphQL than implementing it in REST. However, most of the libraries built on top of GraphQL for example Apollo offer efficient caching mechanisms.

Complex queries: Enabling clients to query exactly what they want, GraphQL query can encounter performance issues if a client app asks for too many nested fields at once.

Rate limiting: In REST, it's simple to specify that we allow only this amount of requests in a time period (for ex. one day) but it becomes difficult because now the user can fire multiple queries in one call.

GraphQL can be overkill: GraphQL is a great solution for multiple microservices, at the same time REST architecture fits best in the case of a simple app. REST can be also a good approach for connecting resource-driven apps that don’t need flexible queries.

GraphQL Always returns HTTP 200: - Every query in GraphQL, whether the query is successful or not, will always return an HTTP status code of 200. With this approach, Error handling becomes a bit untraditional.

Uploading Files: GraphQL doesn’t understand file uploads. To do this, you have to use a separate library or make a separate API using base64 encoding.

Thank you for reading this far. Happy learning.

About the author: Neeraj Pathak

World's okayest developer, can speak multiple languages when high on caffeine. On a journey of "works on my machine".... to "Wow a different error message, finally some progress!!".

Comments
Join us