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.

Table of contents

    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.

    GraphQL in React applications-1

    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 in React applications-2

    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.

    GraphQL in React applications-3

    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:

    GraphQL in React applications-4

    Edit item mode:

    GraphQL in React applications-5

    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.

    React Native Developer
    Damian Burek React Native Developer

    Read more
    on #curiosum blog