GraphQL in React applications
In the current fast-moving online development world, React and GraphQL are two of the most powerful technologies that can significantly improve both the efficiency and handling rate of web apps.
Introduction
From this point of view, when React and GraphQL are utilized as tools for the retrieval and processing of data models that are known in JavaScript frameworks and are frequently used to create user interfaces, one can easily find using graphic elements no less flexible than such script languages like SQL based on cascading style sheets or XML serving different aims. This article covers the best practices for combining React with GraphQL to help programmers take advantage of these technologies and develop stable web apps.
React for Building Interactive UIs in GraphQL Applications
React, a production of Facebook, the start-up company that produced it, is considered one of the most reliable JavaScript libraries to produce an interface that users can use interactively. According to its component-based architecture, it greatly facilitates the handling of the application state. Therefore, this leads to the effortless production of reusable UI components. This is an important feature that comes in handy when combining React with GraphQL, since the essence of modularity as well as scalability offered by React tally perfectly with the flexibility regarding fetching data from and processing data reflected by GraphQL.
GraphQL as a Data Query Language Tailored for React
Integrated into React applications, GraphQL, an open-source query language, and runtime dramatically change the way we work with APIs. Different from conventional REST APIs, GraphQL enables clients to ask for only the justified data they demand, serving as a solution to the problem of over-fetching. This conforms with React’s focus on efficient state management and UI component rendering, providing a harmonious relationship that improves the development process while enhancing performance.
Why GraphQL?
In web design, it is crucial to have effective data communication between client and server. However, for a long time, RESTful APIs continued to dominate the pastures, with GraphQL acting like an opponent and possessing several major benefits.
1. Single Request, Multiple Resources
In GraphQL, clients outline the returned object structure and ensure a high degree of flexibility in query resolution. It paves the way for customers to demand only those fields that are needed, blocking all additional endpoints with predefined structures.
2. Flexible Data Retrieval
Flexibility in data retrieval increases since GraphQL allows the client to describe a request’s answer and define its structure. Clients demand only the necessary fields and thus avoid numerous such endpoints that are led by predefined structures.
3. There is no overfetching or underfetching.
With REST usage, clients can receive too much data or lack enough additional information back, which requires new requests (overfetching or underfetching). This is where GraphQL comes into the picture since it eliminates such problems by enabling clients to precisely specify their data needs.
4. Versionless API
The versionless treatment by GraphQL minimizes the nightmare of API versions in REST services. Clients can ask for new fields, and thus, without disrupting existing services, requests are approved. It promotes a smooth evolution over time.
5. Real-time Data with Subscriptions
GraphQL works on subscriptions to provide data updates in real time. This makes it the ideal tool for applications requiring live data, including communication and collaboration platforms like chat apps or collaborative editing tools.
6. Interactive Documentation with GraphiQL
A tool that is a free, in-browser IDE for GraphQL with immediate documentation capabilities that they can quickly experiment with at the workspace. This feature results in better development and faster API understanding.
As we move to creating a GraphQL-centric React application, the benefits that come with it will become apparent. In this regard, let us consider the practical realization of GraphQL in React and Node.js, which will open a new door to efficiency as well as flexibility for web development processes alike.
GraphQL Libraries for React
To begin with, discuss the significance of selecting a proper GraphQL library for React applications. The choice of a library can not only affect the development workflow but also the performance and maintainability of application code.
Apollo Client
Apollo Client is an advanced and flexible state management library specifically created for effortless GraphQL integration into React apps. Optimized for the local and remote management of data, Apollo Client reduces fetching time, caching, and alteration via GraphQL queries and mutations.
Features
Declarative data fetching: Get data by writing a query and not tracking loading states manually.
Excellent developer tools and developer experience everywhere: Profit from useful tooling for TypeScript, Chrome and Firefox development tools, and VS Code.
Designed for modern React: Use the newest React innovations, including hooks.
Incrementally adoptable: Drop Apollo inside any JavaScript app and use it by feature.
Universally compatible: whether any build setup or any GraphQL API.
Community-driven: Deliver knowledge to thousands of developers across the GraphQL domain.
Relay
Relay is a client-side data management library built around GraphQL, but it leverages it in a way that maximizes its power.
Declarative data fetching: To improve simplicity and clarity, express data requests as GraphQL queries.
Colocation of queries: To improve maintainability and lessen the cognitive burden on users, colocate queries with React components.
Static queries: Make use of static queries to enhance early error detection and tool assistance.
Support for modern React components ensures compatibility with the changing ecosystem by integrating easily with React's most recent features.
urql
A GraphQL client that is extendable and lightweight for React is called urql. It provides the necessary capabilities for effective data fetching while emphasizing minimalism and simplicity. Because of its minimal footprint, urql is a great option for projects that might benefit from a more direct approach.
Features
Minimalist design: Focus on the most important functionality in a minimalist design to keep your project lightweight.
Extensibility: Use plugins to expand the functionality of urql and customize the library to meet your unique platform requirements.
Conscientious defaults: Make use of conscientious defaults that simplify the programming process.
Hooks-based API: Adopt a hooks-based API to create with a React-focused approach.
Building a GraphQL Application with React and Node.js
Now, we can begin a practical dive into application development with GraphQL in React and Node.js codes Apollo Client will be used in our demo application to provide power on the frontend while, at the same time, it is implemented as a backend end through Apollo Server.
The article section below will guide you step-by-step through the project with GraphQL implementation using Apollo, but you can find the whole code here
It can serve as a good playground for the first touch in GraphQL in React applications.
Application Description
In this demo, we will walk you through the process of building a pragmatic GraphQL application. Regardless of your skill level as a developer, this tutorial will give you an overview of how Apollo Client and Apollo Server can be used.
We’ll discuss CRUD basics, such as creating, retrieving, updating, and deleting items. These procedures are the foundation of numerous applications, and GraphQL facilitates this process with its declarative data retrieval model.
Second, we will discuss the intricacies of pagination, which is one of the few techniques for effectively handling big data. After this demo application description, you will now be enlightened on how to employ GraphQL in developing dynamic, scalable applications.
Let’s dive into GraphQL, React, and Node.js using this hands-on example!
Setting Up the Node.js Project
Before we create our GraphQL server with Node.js, let’s start a new project. Assuming you have Node.js installed on your machine, follow these steps:
Step 1: Create a New Project
Open a terminal window and then move into the directory in which you want to create your project. Run the following commands:
## Create a new directory for your project
mkdir graphql-server
## Move into the project directory
cd graphql-server
Step 2: Initialize the Project
Initiate a new Node.js project using npm init. Follow the prompts to set up your project details.
npm init -y
Step 3: Install Dependencies
To build a GraphQL server, we need essential packages. Install them using:
npm install express express-graphql graphql cors
Here's a quick rundown of what these packages do:
- express: A popular web framework for Node.js.
- express-graphql: Middleware for Express to handle GraphQL requests.
- graphql: The core GraphQL library.
- cors: Middleware to enable Cross-Origin resource sharing.
Step 4: Project Structure
Create the main server file, for example, server.js. You can organize your project like this:
graphql-server/
|-- node_modules/
|-- server.js
|-- package.json
|-- package-lock.json
Step 5: Initial Server Setup
Open server.js and set up a basic Express server with GraphQL:
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { GraphQLSchema, GraphQLObjectType, GraphQLString } = require('graphql');
const cors = require('cors');
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
hello: {
type: GraphQLString,
resolve: () => 'Hello, GraphQL!',
},
},
}),
});
const app = express();
app.use(cors());
app.use('/graphql', graphqlHTTP({
schema: schema,
graphiql: true,
}));
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
console.log(`GraphQL server running at http://localhost:${PORT}/graphql`);
});
By completing these steps, you launched a Node.js project and proceeded to install the needed packages for your GraphQL server while also setting up its basic framework. Now, we shall proceed to define GraphQL schema and the practical implementation of real-life request scenarios.
Getting Started with React Project
To begin, let's create a new React project. Open your terminal and run the following command:
npx create-react-app react-graphql-demo
Or you can use some more modern tools like vite if you prefer:
vite create my-react-app --template react
Once your React app is created, navigate to its root directory and install the required libraries and dependencies:
cd react-graphql-demo
and install the required dependencies:
npm install
or, if you prefer yarn:
yarn
Take a quick look at the project structure. You'll find essential directories such as src/ for your source code and public/ for static assets. The main entry point for your app is src/App.js.
Setting up Apollo Client
Next, we'll integrate Apollo Client into our project. Apollo Client is a comprehensive state management library for JavaScript, enabling us to develop how to manage local and remote data with GraphQL.
Install the Apollo Client by running:
npm install @apollo/client
or with yarn:
yarn add @apollo/client
In the upcoming sections, we'll configure Apollo Client to communicate with our GraphQL server, enabling seamless data fetching and management in our React application.
Adding ApolloProvider for Apollo Client
To make use of Apollo Client in your React application, you need to set up the ApolloProvider
. This provider component wraps your entire React application, making the Apollo Client instance available to all components that need it.
1. Create Apollo Client Instance:
First, create an Apollo Client instance in a separate javascript file, e.g., apollo.js
. Make sure to replace the uri
with the correct endpoint of your GraphQL server.
// apollo.js
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql', // Update the URI to your local server
cache: new InMemoryCache(),
headers: {
Authorization: `Bearer YOUR_LOCAL_SERVER_ACCESS_TOKEN`, // Optional: If you decide to implement authentication
},
});
export default client;
2. Wrap your app with ApolloProvider:
In your main React component (e.g., App.js
), import the ApolloProvider
from @apollo/client
and your Apollo Client instance.
// App.js
import React from 'react';
import GraphQLExample from './components/GraphQLExample';
import { ApolloProvider } from '@apollo/client';
import client from './apollo';
function App() {
return (
<ApolloProvider client={client}>
<div className="App">
<header className="App-header">
<GraphQLExample />
</header>
</div>
</ApolloProvider>
);
}
export default App;
Here we can pass to the ApolloProvider
our client
, the ApolloClient instance we already created. In our example app, it is inside the file apollo.js
.
Querying Data with Pagination
In this section, we will go through the process of querying data from the GraphQL server with pagination. A component on the React side talks to the GraphQL server, fetching and displaying a section of data and taking care of paging for a better user interface.
GraphQL Query
First, let’s consider the GraphQL query that is used to request data from a server. In our example, the GET_SAMPLE_DATA query is designed to only fetch data from a specific number of repositories (ITEMS_PER_PAGE) starting from a given cursor (after). The query is in the gql tag to enable better syntax highlighting and linting.
const GET_SAMPLE_DATA = gql`
query GetSampleData($first: Int!, $after: String) {
repositories(first: $first, after: $after) {
id
name
description
}
}
`;
React Component - useQuery Hook
The React component (GraphQLExample) uses the useQuery hook from the Apollo Client. This hook starts the GraphQL query and its parameters control pagination. In our case, the first establishes the number of items per page, and afterward, it is used to identify the cursor for paging.
const { loading: queryLoading, error: queryError, data, fetchMore, refetch } = uuseQuery(GET_SAMPLE_DATA, {
variables: { first: ITEMS_PER_PAGE, after: null }, // Ensure first is non-null
});
Fetching More Data - fetchMore Function
The fetchMore function allows us to fetch additional data when the user requests more items. It updates the query variables to fetch the next set of data based on the current cursor. The logic ensures that we don't duplicate data already fetched.
const loadMore = () => {
if (data?.repositories.length > 0) {
fetchMore({
variables: { first: ITEMS_PER_PAGE, after: data.repositories[data.repositories.length - 1].id },
updateQuery: (prevResult, { fetchMoreResult }) => {
if (!fetchMoreResult) return prevResult;
return {
repositories: [...prevResult.repositories, ...fetchMoreResult.repositories],
};
},
});
}
};
Querying Data on the Server Side
In our GraphQL server implementation, querying data involves defining the GraphQL schema, specifying the data structure, and creating a resolver to handle the actual data retrieval. Below is the relevant server-side code responsible for handling queries:
For starters, we can create some data to have something to query. So let's create 'repositories.json' file on our server. With this example of content:
[
{
"id": "1",
"name": "Repo 1",
"description": "Description for Repo 1"
},
{
"id": "2",
"name": "Repo 2",
"description": "Description for Repo 2"
},
{
"id": "3",
"name": "Repo 3",
"description": "Description for Repo 3"
},
{
"id": "4",
"name": "Repo 4",
"description": "Description for Repo 4"
},
{
"id": "5",
"name": "Repo 5",
"description": "Description for Repo 5"
},
{
"id": "6",
"name": "Repo 6",
"description": "Description for Repo 6"
}
]
This file will be an entry point for our app data and our quasi-database.
1. Defining RepositoryType:
We start by defining a GraphQLObjectType called RepositoryType. This type represents the structure of a repository with fields like id, name, and description.
const RepositoryType = new GraphQLObjectType({
name: 'Repository',
fields: {
id: { type: GraphQLString },
name: { type: GraphQLString },
description: { type: GraphQLString },
},
});
2. Reading Initial Data on Server Start:
Upon server startup, we read the initial data from the repositories.json file using fs.readFile.
The parsed data is stored in the data array.
let data = [];
fs.readFile('repositories.json', 'utf8', (err, jsonString) => {
if (err) {
console.log('Error reading file:', err);
return;
}
try {
data = JSON.parse(jsonString);
} catch (err) {
console.log('Error parsing JSON:', err);
}
});
3. QueryType for Retrieving Repositories:
The heart of our querying functionality lies in the QueryType, a GraphQLObjectType dedicated to handling queries.
We have a field named repositories within QueryType, which returns a list of repositories.
It takes arguments first (number of items to retrieve) and after (cursor for pagination).
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: {
repositories: {
type: new GraphQLList(RepositoryType),
args: {
first: { type: GraphQLNonNull(GraphQLInt) },
after: { type: GraphQLString },
},
resolve: (parent, { first, after }) => {
const startIndex = after ? data.findIndex(item => item.id === after) + 1 : 0;
const endIndex = startIndex + first;
return data.slice(startIndex, endIndex);
},
},
},
});
4. GraphQL Schema Setup:
The GraphQL schema is set up with QueryType and a MutationType (for handling mutations).
const schema = new GraphQLSchema({
query: QueryType,
mutation: MutationType, // Assuming MutationType is defined elsewhere in your code
});
By understanding these components, you gain insight more flexibility into how the GraphQL server handles incoming queries and retrieves data from the data
array.
Adding a Repository with GraphQL Mutation
Another CRUD operation that we will probably need in our applications no matter what its purpose is, adding a new item. We will use the graphql mutation for this purpose.
1. Server-Side Mutation:
On the server side, we define a mutation field addRepository
within the MutationType
.
It takes two non-null arguments: name (repository name) and description (repository description).
The resolve
function generates a new repository object, adds it to the data array, writes the updated data to the file, and returns the newly created repository.
const MutationType = new GraphQLObjectType({
name: 'Mutation',
fields: {
addRepository: {
type: RepositoryType,
args: {
name: { type: GraphQLNonNull(GraphQLString) },
description: { type: GraphQLNonNull(GraphQLString) },
},
resolve: (parent, { name, description }) => {
const newRepo = {
id: (data.length + 1).toString(),
name,
description,
};
data.push(newRepo);
writeDataToFile();
return newRepo;
},
},
// ... (other mutations)
},
});
2. Client-Side Mutation with Apollo Client:
On the client side, we define a GraphQL mutation using Apollo Client's useMutation
hook.
The mutation is named AddRepository
and takes two variables: $name
and $description
.
const ADD_REPOSITORY = gql`
mutation AddRepository($name: String!, $description: String!) {
addRepository(name: $name, description: $description) {
id
name
description
}
}
`;
Then, of course, we need to declare our mutation using the useMutation
hook:
const [addRepository, { loading: mutationLoading, error: mutationError }] = useMutation(ADD_REPOSITORY, {
update: (cache, { data: { addRepository } }) => {
refetch(); // Refetch the query to update the data immediately
},
});
The mutation updates the cache by reading the existing repositories from memory, adding the the new data to repository, and then writing the updated data back to the cache.
const handleAddRepository = () => {
if (!name || !description) {
// Basic form validation
alert('Please enter both repository name and description.');
return;
}
addRepository({
variables: {
name,
description,
},
update: (cache, { data: { addRepository: newRepo } }) => {
const existingRepos = cache.readQuery({
query: GET_SAMPLE_DATA,
variables: { first: ITEMS_PER_PAGE, after: null },
});
if (existingRepos) {
cache.writeQuery({
query: GET_SAMPLE_DATA,
variables: { first: ITEMS_PER_PAGE, after: null },
data: {
repositories: [newRepo, ...existingRepos.repositories],
},
});
}
},
});
};
By following these steps, you can seamlessly add a new repository both on the server and client sides using GraphQL.
Removing a Repository with GraphQL Mutation
If we can add something, it would also be good to be able to remove it in case of a mistake or anything else. For this purpose, we would use the useMutation
hook as well.
1. Server-Side Mutation:
On the server side, we define a mutation field removeRepository
within the MutationType
.
It takes a single non-null argument: id (the ID of the repository to be removed).
The resolve function finds the index of the repository with the given ID, removes it from the data array, writes the updated data to the file, and returns the value to the removed repository.
const MutationType = new GraphQLObjectType({
name: 'Mutation',
fields: {
// ... (other mutations)
removeRepository: {
type: RepositoryType,
args: {
id: { type: GraphQLNonNull(GraphQLString) },
},
resolve: (parent, { id }) => {
const index = data.findIndex(item => item.id === id);
if (index !== -1) {
const removedRepo = data.splice(index, 1)[0];
writeDataToFile();
return removedRepo;
} else {
throw new Error('Repository not found');
}
},
},
},
});
2. Client-Side Mutation with Apollo Client:
On the client side, we define a GraphQL mutation using Apollo Client's useMutation
hook.
The mutation is named RemoveRepository
and takes a single variable: $id.
The mutation updates the cache by refetching the query to immediately reflect the removal of the repository.
const REMOVE_REPOSITORY = gql`
mutation RemoveRepository($id: String!) {
removeRepository(id: $id) {
id
name
description
}
}
`;
const [removeRepository] = useMutation(REMOVE_REPOSITORY, {
update: (cache, { data: { removeRepository } }) => {
refetch(); // Refetch the query to update the data immediately
},
});
const handleRemoveRepository = (id) => {
removeRepository({
variables: {
id,
},
});
};
By following these steps, you can seamlessly remove a repository both on the server and client sides using GraphQL.
Updating a Repository with GraphQL Mutation
The last operation will be updating existing records. For this purpose, we will use the useMutation
hook as well.
1. Server-Side Mutation:
On the server side, we define a mutation field updateRepository
within the MutationType
.
It takes three arguments: id
(non-null, the ID of the repository to be updated), name
(optional, the new name for the repository), and description
(optional, the new description for the repository).
The resolve
function finds the repository with the given ID, updates its name and/or description if provided, writes the updated data to the file, and returns the updated repository.
const MutationType = new GraphQLObjectType({
name: 'Mutation',
fields: {
// ... (other mutations)
updateRepository: {
type: RepositoryType,
args: {
id: { type: GraphQLNonNull(GraphQLString) },
name: { type: GraphQLString },
description: { type: GraphQLString },
},
resolve: (parent, { id, name, description }) => {
const index = data.findIndex(item => item.id === id);
if (index !== -1) {
// Update the repository if the id is found
if (name) data[index].name = name;
if (description) data[index].description = description;
writeDataToFile();
return data[index];
} else {
throw new Error('Repository not found');
}
},
},
},
});
2. Client-Side Mutation with Apollo Client:
On the client side, we define a GraphQL mutation using Apollo Client's useMutation
hook.
The mutation is named UpdateRepository
and takes three variables: $id
, $name
, and $description
.
The mutation updates the cache by re-fetching the query to immediately reflect the changes, and it provides an onCompleted
callback to reset the user to editing mode after a successful update.
const UPDATE_REPOSITORY = gql`
mutation UpdateRepository($id: String!, $name: String, $description: String) {
updateRepository(id: $id, name: $name, description: $description) {
id
name
description
}
}
`;
const [updateRepository] = useMutation(UPDATE_REPOSITORY, {
update: (cache, { data: { updateRepository } }) => {
refetch(); // Refetch the query to update the data immediately
},
});
const handleUpdateRepository = (id, newName, newDescription) => {
updateRepository({
variables: {
id,
name: newName,
description: newDescription,
},
refetchQueries: [{ query: GET_SAMPLE_DATA, variables: { first: ITEMS_PER_PAGE, after: null } }],
onCompleted: () => {
setName('')
setDescription('')
setEditingItem(null); // Reset editing mode after a successful update
},
}).catch((error) => {
console.error("Error updating repository:", error);
});
};
By following these steps, you can smoothly update a repository both on the server and client sides using GraphQL.
Application Preview
In this section, we will have a closer look at the end-product aspect of the GraphQL React application. The presented screenshots demonstrate several peculiarities of the app, such as its user interface and other functionalities.
Normal mode:
Edit item mode:
This is a perfect place to try out GraphQL queries and mutations, as it would be the best introduction for developers who are starting with this technology.
Summary
React and GraphQL, a dynamic API query language, work well together thanks to programs like Apollo Client. An approachable manual for integrating GraphQL into React apps was given in this article. We investigated the capabilities of GraphQL, from project setup to CRUD operations, and the result was a fully functional React app that serves as a learning tool for developers who are new to GraphQL and React. Get the app and start exploring this potent technological combination.
FAQ
What is GraphQL and how does it integrate with React applications?
GraphQL is an open-source data query language and runtime designed to improve API usage by allowing clients to request exactly the data they need, eliminating over-fetching. Integrated into React applications, it enhances data retrieval efficiency and UI component rendering, complementing React’s state management and component-based architecture.
Why is GraphQL considered a better alternative to REST APIs in React applications?
GraphQL provides several advantages over traditional REST APIs, such as enabling single requests for multiple resources, flexible data retrieval, elimination of overfetching and underfetching, a versionless API, real-time data with subscriptions, and interactive documentation with GraphiQL. These features align well with React's focus on efficient UI rendering and state management.
What are the benefits of using Apollo Client in React applications with GraphQL?
Apollo Client offers features like declarative data fetching, extensive developer tools, and compatibility with modern React features like hooks. It facilitates efficient state management and data integration from both local and remote sources, enhancing the development experience and application performance.
How can GraphQL improve data handling in React applications?
By enabling precise data fetching and updates, GraphQL improves the performance and efficiency of React applications. It allows developers to query only the necessary data, reducing bandwidth usage and speeding up response times, which results in a smoother user experience and better state management in interactive UIs.
What are the key steps in setting up a GraphQL server with Node.js for a React application?
Setting up a GraphQL server involves initializing a new Node.js project, installing necessary packages (like express, express-graphql, and graphql), and setting up the server structure. Developers define GraphQL schemas and resolvers to process queries and mutations, enabling communication between the React frontend and the server.
How does pagination work in GraphQL and why is it important for React applications?
Pagination in GraphQL involves querying data in segments, improving performance and user experience by loading only a portion of data at a time. This is especially important for React applications dealing with large datasets, as it ensures efficient data handling and minimizes loading times.
What are some best practices for managing local and remote data in React applications using GraphQL?
Best practices include using Apollo Client for state management, structuring queries and mutations efficiently, and implementing pagination and caching strategies. Additionally, leveraging GraphQL's real-time subscriptions and adhering to security best practices are important for maintaining a robust and scalable application.