Kingsley Ubah 21. Web Developer. Technical Writer. African in Tech.

GraphQL local state management with Apollo

7 min read 2132

GraphQL Local State Management With Apollo

GraphQL is a data query language that provides a flexible approach for querying APIs. Unlike REST APIs, the GraphQL API provides one single endpoint for performing CRUD operations on the data store.

A big benefit of using GraphQL over the REST approach is flexibility. With GraphQL, frontend developers can be more specific about what data the API should send back, thereby ensuring the app only receives the needed data.

For a smooth GraphQL-frontend integration, the Apollo client library is used to fetch, cache, modify, and manage state data locally in a frontend application.

It includes the in-memory cache, local state management, error handling capabilities, and a React-based view layer called Apollo Provider.

In this tutorial, we’ll learn how to set up a GraphQL API in Node.js and how to manage data from the API locally in a React app with Apollo Client.

GraphQL installation

You’re required to first install Node.js on your computer if you have not already done so.

If installed, create a folder for your project, then make a server subfolder within it. After creating the subfolder, cd into it and initialize a Node project with the following terminal commands:

cd graphql-apollo/server
npm init -y

This will generate a package.json file in your project’s server folder.

Now run the following command to install the packages:

npm install express express graphql express-graphql

Alternatively, you can get the code from this GitHub repository to follow along.



Setting ip queries and mutations in GraphQL

We’ll build our application around some fake user data stored in the graphql-apollo/server/users.json file. Each user has an ID, a first name, last name, gender, and an address:

[{
  "id": 1,
  "firstName": "Ellene",
  "lastName": "Clausen-Thue",
  "email": "[email protected]",
  "gender": "Female",
  "address": "Omboué"
}, {
  "id": 2,
  "firstName": "Dani",
  "lastName": "Padley",
  "email": "[email protected]",
  "gender": "Male",
  "address": "Pogwizdów"
}, {
  "id": 3,
  "firstName": "Svend",
  "lastName": "Cathenod",
  "email": "[email protected]",
  "gender": "Male",
  "address": "Changqing"
}
...
]

Note that you can quickly generate your own data from Mockaroo.

Now in GraphQL, we can use a schema to specify the data type and structure of the database and see if it’s comprised of mutations and queries.

Queries are statements containing rules that specify how to retrieve data and what fields to retrieve for each record. Whereas mutations are statements that specify how to mutate data on the API (that is, create, update, and delete).

To keep our project organized, create a new folder named Schemas inside of the graphql-apollo/server folder.

Inside the Schemas folder, create a new folder named Types and create a User.js file in it where we’ll create the type definition for a user entity:

const graphql = require("graphql");
const { GraphQLObjectType, GraphQLInt, GraphQLString } = graphql;

const User = new GraphQLObjectType({
  name: "User",
  fields: () => ({
    id: { type: GraphQLInt },
    firstName: { type: GraphQLString },
    lastName: { type: GraphQLString },
    email: { type: GraphQLString },
    gender: { type: GraphQLString },
    address: { type: GraphQLString },
  }),
});

module.exports = User;

The User type has an id of type integer, a name of type string, and so on. We also imported the various schema types from graphql. Finally, we exported the User object to use the schema where required.

Next, we need to create all the queries and mutations for our API.

Creating the query

So, create an index.js file in the graphql-apollo/server/Schemas folder. We’ll start by importing the GraphQL types, the fake users data, and the User type:

const graphql = require("graphql");
const {
  GraphQLObjectType,
  GraphQLSchema,
  GraphQLInt,
  GraphQLString,
  GraphQLList,
} = graphql;

const usersData = require("../users.json");

const UserType = require("./Types/User");

Next, below the imports, let’s create our query:

const RootQuery = new GraphQLObjectType({
  name: "RootQueryType",
  fields: {
    getAllUsers: {
      type: new GraphQLList(UserType),
      args: { id: { type: GraphQLInt } },
      resolve(parent, args) {
        return userData;
      },
    },
  },
});

Inside RootQuery, we define the query for getting all users (getAllUsers). In this query, we specify that we want to get a list of users (specified by type: new GraphQLList(UserType)).

The args property specifies the arguments to pass in query (id), and the resolve function returns our actual data source. userData will be substituted with the URL of an actual database in a real production environment.

Creating the mutations

Next, we create our mutations:


More great articles from LogRocket:


const Mutation = new GraphQLObjectType({
  name: "Mutation",
  fields: {
    createUser: {
      type: User,
      args: {
        firstName: { type: GraphQLString },
        lastName: { type: GraphQLString },
        email: { type: GraphQLString },
        gender: { type: GraphQLString },
        address: { type: GraphQLString },
      },
      resolve(parent, args) {
        userData.push({
          id: userData.length + 1,
          firstName: args.firstName,
          lastName: args.lastName,
          email: args.email,
          gender: args.gender,
          address: args.address,
        });
        return args;
      },
    },
  },
});

Here, similar to the previous query, we created a mutation query for creating a user (createUser) and pass in the previous schema as its type.

args contains the data to pass inside the query as an argument. In the resolve function, we push the new user data object into our data store. Note that for the user id, we automatically increment the value based on the list’s length.

Finally, we must export our schema with the query and mutation at the bottom of the file:

module.exports = new GraphQLSchema({ query: RootQuery, mutation: Mutation });

In the next section, we’ll create our GraphQL server.

Starting the GraphQL Server

To start your GraphQL server, go into your graphql-apollo/server/index.js file and use the following code:

const express = require("express");
const app = express();
const PORT = 6969;
const { graphqlHTTP } = require("express-graphql");
const schema = require("./Schemas/index");
const cors = require("cors");

app.use(cors());
app.use(express.json());
app.use(
  "/graphql",
  graphqlHTTP({
    schema,
    graphiql: true,
  })
);

app.listen(PORT, () => {
  console.log("Server running");
});

After importing graphqlHTTP to create our GraphQL HTTP server, note that the GraphQL API only has one endpoint: /graphql.

As an argument to graphqlHTTP, we must pass the imported schema and set the graphiql option to true. The latter gives us access to the GraphQL graphical user interface (GUI) for testing our queries.

Finally, we start serving our GraphQL app at port 6969.

Running test queries in GraphiQL

GraphiQL is a built-in GraphQL API testing environment that enables us to visualize our API calls.

While setting up our GraphQL server in the previous section, we did set the graphilql option to true. This enables us to use the Graphiql UI on our browser to run test queries, just like API testers like Postman.

To access qraphiql, run node index.js on your command line and visit the following URL on your web browser’s address bar:

<localhost:6969/graphql>

In the left window, you can write and execute test queries. The result of each query will appear on the right window.

For example, the following image shows how we retrieve all users using the getAllUsers query, which we previously created:

The Query To Get All Users

And this is the result:

The Result

We can also add a new user to the data store by executing the createUser mutation:

Add A New User By Executing The CreateUser Mutation

Then, this is the result:

The Result

Connecting GraphQL to a React app using Apollo Client

Now that the QraphQL backend is all set, it’s time to create a React frontend to consume our GraphQL API. We’ll use the Apollo Client library to connect with the backend.

First, let’s install React via create-react-app:

npx create-react-app client

This creates a new folder inside the project root called client, which contains the frontend React code and templates.

Next, we’ll install both Apollo Client and GraphQL for the frontend:

npm install @apollo/client
npm install graphql

// For Mac
yard add @apollo/client graphql

After successful installation, we’ll import some dependencies into the root component (graphql-apollo/client/src/App.js):

import "./App.css";
import {
  ApolloClient,
  InMemoryCache,
  ApolloProvider,
  HttpLink,
  from,
} from "@apollo/client";
import { onError } from "@apollo/client/link/error";
import Users from "./Components/GetUsers";
import Form from "./Components/Form";

// [...]

Here, onError provides us with a way to catch errors, inMemoryCache enables page caching and ApolloClient will be used to create the Apollo provider.

We also imported two yet-to-be-created components, Users and Form, to display all users and create a new user respectively.

Now, we’ll create a new client instance from ApolloClient with a link to the GraphQL server, making some other configurations as well:

const link = from([
  errorLink,
  new HttpLink({ uri: "http://localhost:6969/graphql" }),
]);

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: link,
});

For GraphQL errors, display an alert with the error message(s):

const errorLink = onError(({ graphqlErrors, networkError }) => {
  if (graphqlErrors) {
    graphqlErrors.map(({ message, location, path }) => {
      alert(`Graphql error ${message}`);
    });
  }
});

Now that we have the Apollo Client instance with all the required settings, we can wrap our entire application inside the Apollo Provider:

function App() {
  return (
    <ApolloProvider client={client}>
      {" "}
      <Users />
      <Form />
    </ApolloProvider>
  );
}

export default App;

This will provide both nested components with access to the GraphQL API. Next, we’ll execute our first query to fetch all users.

Creating our frontend schema (queries and mutations)

Now that our frontend has access to the GraphQL API, we can now create the query and mutation statements to use in the client code.

In your graphql-apollo/client/src folder, create Queries.js and Mutations.js for your queries and mutations respectively, then use the following code:

// Queries.js
import { gql } from "@apollo/client";

export const LOAD_USERS = gql`
  query {
    getAllUsers {
      id
      firstName
      email
      gender
      address
    }
  }
`;

// Mutations.js
import { gql } from "@apollo/client";

export const CREATE_USER_MUTATION = gql`
  mutation createUser(
    $firstName: String!
    $lastName: String!
    $email: String!
    $gender: String
    $address: String
  ) {
    createUser(
      firstName: $firstName
      lastName: $lastName
      email: $email
      gender: $gender
      address: $address
    ) {
      id
    }
  }
`;

The CREATE_USER_MUTATION will be used in the Form when adding a new user to the GraphQL API.

Next, we’ll use these to query GraphQL for our users’ data.

Making queries and showing data from Apollo Client

Inside the graphql-apollo/client/src/Users.js component, let’s execute the LOAD_USERS query to retrieve all users from the API:

import React, { useEffect, useState } from "react";
import { useQuery, gql } from "@apollo/client";
import { LOAD_USERS } from "../GraphQL/Queries";

function Users() {
  const { error, loading, data } = useQuery(LOAD_USERS);
  const [users, setUsers] = useState([]);
  useEffect(() => {
    if (data) {
      setUsers(data.getAllUsers);
    }
  }, [data]);

  return (
    <div>
      {" "}
      {users.map((val) => {
        return <h1> {val.firstName}</h1>;
      })}
    </div>
  );
}

export default GetUsers;

Here, the useQuery Hook takes in a query statement and returns data from API along with possible error and loading states. Then, inside the useEffect Hook, we check for data and update the component state whenever there is a change.

In the template, we can now display the first name of all users by mapping the data.

Next, we’ll perform mutation on our API by adding a new user through the form input.

Mutating data on the API

Let’s add a new user to our data store by executing the mutation for creating a user. First, we make some imports and initialize our Form component’s state with empty values:

import React, { useState } from "react";
import { CREATE_USER_MUTATION } from "../GraphQL/Mutations";
import { useMutation } from "@apollo/client";

function Form() {
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");
  const [email, setEmail] = useState("");
  const [gender, setGender] = useState("");
  const [address, setAddress] = useState("");
  // [...]

Then, we pass our mutation query into the useMutation Hook. This returns createUser for adding a new user to the data store and error for possible error states:

const [createUser, { error }] = useMutation(CREATE_USER_MUTATION);

  const addUser = () => {
    createUser({
      variables: {
        firstName: firstName,
        lastName: lastName,
        email: email,
        gender: gender,
        address: address,
      },
    });

Finally, we render the UI for the form:

if (error) {
      console.log(error);
    }
  };
  return (
    <div>
      <input
        type="text"
        placeholder="First Name"
        onChange={(e) => {
          setFirstName(e.target.value);
        }}
      />
      <input
        type="text"
        placeholder="Last Name"
        onChange={(e) => {
          setLastName(e.target.value);
        }}
      />
      <input
        type="text"
        placeholder="Email"
        onChange={(e) => {
          setEmail(e.target.value);
        }}
      />
      <input
        type="text"
        placeholder="Gender"
        onChange={(e) => {
          setPassword(e.target.value);
        }}
      />
      <input
        type="text"
        placeholder="Address"
        onChange={(e) => {
          setPassword(e.target.value);
        }}
      />
      <button onClick={addUser}> Create User</button>
    </div>
  );
}

export default Form;

Here, when an input changes, we called the setter Hook to update the component state with the input value. Then, when the user clicks on the submit button, we execute the addUser function, which in turn adds the user to the database.

Conclusion

I hope this tutorial helped you understand GraphQL better. You should now be able to set up a GraphQL API on a Node server, define a schema for your data store, and make queries to create, read, update, and delete data on the API.

The final section of the article serves as a guide to help you connect a GraphQL backend to a React frontend application using Apollo Client. The core @apollo/client library provides built-in integration with React, making the process of API integration quick and easy.

You can get the source code from this GitHub repository. If you have any questions related to this topic, please let me know in the comments.

Monitor failed and slow GraphQL requests in production

While GraphQL has some features for debugging requests and responses, making sure GraphQL reliably serves resources to your production app is where things get tougher. If you’re interested in ensuring network requests to the backend or third party services are successful, try LogRocket.https://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Kingsley Ubah 21. Web Developer. Technical Writer. African in Tech.

Leave a Reply