In this article, we cover the differences between RESTful APIs and GraphQL and how we can leverage the power of GraphQL and Apollo Client to build efficient, scalable and modern applications.
As the field of frontend development evolves, so does the complexity of the data it manages. Traditional methods of accessing APIs, such as RESTful APIs, are no longer sufficient due to their inherent limitations. One major issue with RESTful APIs is over-fetching. This happens when we request data and the server sends back too much data than we need, resulting in wasted resources and increased response times.
Another drawback is under-fetching, which occurs when a request returns less data than is required. This often leads to making multiple requests to different endpoints, which can negatively impact an application’s performance. These problems of traditional APIs can be easily solved with GraphQL.
GraphQL and Apollo Client are powerful tools for enhancing performance and effectively managing application data. In this article, we’ll discuss how to use GraphQL and Apollo Client for efficient data fetching and state management in API-driven applications.
GraphQL is a query language, as indicated by the “QL” in its name. Its syntax allows us to query or mutate data on a server. GraphQL can improve an application’s performance and scalability by helping us retrieve the exact data we require from the server.
The key feature of GraphQL is that it gives the client exactly the data it requested, unlike traditional RESTful APIs. Hence, it provides a more efficient, powerful and flexible way of data fetching in API-driven applications.
Let’s take a simple example to understand the difference between GraphQL and RESTful APIs. Imagine you are building a simple blog application with the following data model:
For a RESTful API, you may have endpoints like:
/users
(GET): Get a list of all users./users/{id}
(GET): Get details of a specific user./posts
(GET): Get a list of all posts./posts/{id}
(GET): Get details of a specific post.To get a list of posts with the author’s username, you will need to make multiple requests. For example:
Get a list of posts:
# Request 1: Get list of posts
GET /posts
# Response 1:
[
{
"id": 1,
"title": "Introduction to GraphQL",
"content": "GraphQL is awesome!",
"authorId": 123
},
// More posts...
]
For each post, get the details of the author using the user ID.
# Request 2: Get details of the author for post with ID
GET /users/123
# Response 2:
{
"id": 123,
"username": "john_doe",
"email": "john@example.com"
}
With GraphQL, you can request the specific data you need through a single query, as shown in the example below:
# GraphQL Query
query {
posts {
id
title
content
author {
id
username
}
}
}
// Response
{
"data": {
"posts": [
{
"id": 1,
"title": "Introduction to GraphQL",
"content": "GraphQL is awesome!",
"author": {
"id": 123,
"username": "john_doe"
}
},
// More posts...
]
}
}
GraphQL eliminates the problems of over-fetching and under-fetching data by helping you get exactly what you requested. If you look closely at our REST API example above, you will notice multiple endpoints. Each endpoint returns its data structure, potentially including information the app does not need.
To get a list of posts with the author’s username, you need to make at least two separate requests. This can lead to network redundancy in more complex applications. However, GraphQL offers a more efficient and flexible approach to data fetching with just a single endpoint and precise data requests to improve performance and network efficiency.
The schema is essentially the blueprint of GraphQL API. It can be seen as a contract between the client and the server. The schema specifies how the data can be requested and what types of responses should be expected.
A schema describes the structure and relationship of your data. It can be seen as a map of everything your API exposes. With schema, clients see exactly what data is available and can request specific subsets with optimized queries. It provides a concise and human-readable way to express the types, queries and mutations that make up the API, thereby shielding the client from the backend complexities. You can read more about schemas here.
In GraphQL, queries are used to request specific data from a GraphQL server. Unlike traditional REST APIs, which often receive a fixed set of data, GraphQL allows clients to define the structure of the response and request only the information they need. Queries in GraphQL are written using syntax that resembles the shape of the data you want to retrieve.
Let’s look at an example of a query to fetch a list of users and their names:
query GetUsers {
users {
id
name
}
}
The response to the query:
{
"data": {
"users": [
{
"id": "123",
"name": "John Doe"
},
{
"id": "456",
"name": "Jane Doe"
}
]
}
}
Apart from fetching data from our GraphQL API, we can also mutate data. That is, we can create, update and delete data from our API. Mutations are operations used to modify data on the server. Here is a simple example of mutations to create and update data:
mutation CreateUser($name: String!, $email: String!) {
createUser(name: $name, email: $email) {
id
name
}
}
The code snippet above creates a new user.
mutation UpdatePost($id: ID!, $title: String!) {
updatePost(id: $id, title: $title) {
id
title
}
}
This mutation updates the title of a specific blog post.
Subscriptions enable clients and servers to communicate in real-time. While queries are used for retrieving data and mutations for modifying data, subscriptions allow clients to receive real-time updates when certain events occur on the server. This is useful for building applications that require live data, such as chat applications, live notifications or any scenario where you want to push updates to clients as soon as they are available.
Apollo Client is a powerful JavaScript library used to manage state and interact with GraphQL APIs in client-side applications. It is a key part of the Apollo ecosystem, which also includes server-side components like Apollo Server. Apollo Client simplifies the process of working with GraphQL by providing tools for managing application state, making queries to a GraphQL server and handling real-time updates through subscriptions.
We are going to build a simple application that uses Apollo Client and GraphQL API in a React application. We will be using the Rick and Morty API.
To create a React project, run the the following command on your terminal:
npx create-react-app apollo-graphql-demo
cd apollo-graphql-demo
npm start
To configure Apollo Client into our project, we need to install the necessary dependencies. Run the following command in your terminal:
npm install @apollo/client graphql
To initialize ApolloClient, in your src
folder, create a Graphql
folder. Inside the folder, create a file named apolloClient.js
, and add the following code to it:
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
const client = new ApolloClient({
link: new HttpLink({ uri: "https://rickandmortyapi.com/graphql" }),
cache: new InMemoryCache(),
});
export default client;
The code above initializes a new instance of ApolloClient with a link to the Rick and Morty GraphQL API and sets up an in-memory cache for query results.
Create a file in your Grapghql
folder named queries.js
, and add the following to it:
import gql from "graphql-tag";
export const GET_CHARACTERS = gql`
query GetCharacters {
characters {
results {
id
name
status
species
image
}
}
}
`;
The code below defines a GraphQL query using the gql
function. The GET_CHARACTERS
constant is assigned the result of calling the gql \***\*
function with a template literal. This template contains a GraphQL query named GetCharacters
. The query is structured to fetch information about characters and includes the fields id
, name
, status
, species
and image
\*\***
for each character.
Now that we have successfully defined our queries, the next step is to wrap our React app with an ApolloProvider
.
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { ApolloProvider } from "@apollo/client";
import client from "./aplloClient";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
,
</React.StrictMode>
);
We are going to use the useQuery
hook to request our data. The useQuery
hook is used to execute GraphQl queries in a React component. It simplifies the process of sending GraphQL queries to a server and handling the response.
To display the data in our app, create a folder named components
. Inside the folder, create a file named Character.js
, and add the following to it:
import React from "react";
import { useQuery } from "@apollo/client";
import { GET_CHARACTERS } from "../graphql/queries";
function Characters() {
const { loading, error, data } = useQuery(GET_CHARACTERS);
if (loading) return <p>Loading characters...</p>;
if (error) return <p>Error fetching characters</p>;
return (
<div>
<h2>Character List</h2>
<ul className="characters-grid">
{data.characters.results.map((character) => (
<li key={character.id}>
<img src={character.image} alt={character.name} />
<div>
<strong>{character.name}</strong>
<p>Status: {character.status}</p>
<p>Species: {character.species}</p>
</div>
</li>
))}
</ul>
</div>
);
}
export default Characters;
Next let’s call the Character.js
component in the App.js
file:
import "./App.css";
import Characters from "./components/Characters";
function App() {
return (
<div className="App">
<h1>Rick and Morty Characters</h1>
<Characters />
</div>
);
}
export default App;
If you have reached this point, you should see the list of characters in the Rick and Morty API, as shown below.
In this article, we covered the differences between Restful APIs and GraphQL and how we can leverage the power of GraphQL and Apollo Client to build efficient, scalable and modern applications.
Chris Nwamba is a Senior Developer Advocate at AWS focusing on AWS Amplify. He is also a teacher with years of experience building products and communities.