A Complete Tutorial on CRUD Operations with React and GraphQL

laurentiu.raducu

A Complete Tutorial on CRUD Operations with React and GraphQL

If you’re looking to learn how to implement simple CRUD (Create, Read, Update, Delete) operations using GraphQL and React, you’ve come to the right place. In this tutorial, we’ll cover everything from basic examples of reading and mutating data with React Hooks to implementing authentication, error handling, caching, and optimistic UI with Apollo Client. By the end of this tutorial, you’ll be able to create complete end-to-end CRUD operations using React and GraphQL.

What is React?

React is a powerful JavaScript library that’s designed for building user interfaces. Its primary purpose is to aid in the frontend aspect of building applications, including handling the view layer of web and mobile apps. React is component-based, which means that the various parts of a React application are broken down into smaller components and then organized inside higher-level components. These higher-level components define the final structure of the application. React also supports reusable components, allowing developers to create a component and use it multiple times in different parts of the application. This helps reduce bloated code and makes it easier to follow the DRY (Don’t Repeat Yourself) principle.

What is GraphQL?

GraphQL is a query language for APIs that provides a runtime for fulfilling queries with existing data. In simple terms, GraphQL is a syntax that describes how to ask for data. It’s generally used to load data from a server to a client. Unlike traditional REST APIs, GraphQL abstracts all requests to a single endpoint, making it declarative, meaning that whatever is requested is returned.

When to Use GraphQL

While GraphQL has a well-defined schema that prevents overfetching, not all projects require it. If you have a stable RESTful API system where you rely on data from a single data source, you may not need GraphQL. However, if you have a full-fledged product that relies on data from multiple sources (e.g., MongoDB, MySQL, Postgres, and other APIs), GraphQL can help consolidate data as a single source of truth. For instance, if you’re designing a portfolio site for yourself and want data from social media and GitHub to show contributions, as well as your own database to maintain a blog, using GraphQL to write the business logic and schema would consolidate all data as a single source of truth.

What is CRUD?

CRUD stands for Create, Read, Update, and Delete. These four basic functionalities are the building blocks of any API or application. A RESTful API uses the four most common HTTP methods (GET, POST, PUT, and DELETE) to create a CRUD system.

CRUD with GraphQL-Server

Setting up the server

To demonstrate how CRUD operations work in a React and GraphQL app, we’ll go over some GraphQL CRUD examples. First, let’s spin off a simple GraphQL server using express-graphql and get it connected to a MySQL database. You can find a good guide for this here. A GraphQL server is built on top of schema and resolvers. We’ll start by building a schema that defines types, queries, mutations, and subscriptions. This schema describes the entire app structure. Here’s an example code snippet for setting up a GraphQL server using express-graphql and MySQL database in a React app:

const express = require('express');
const graphqlHTTP = require('express-graphql');
const { buildSchema } = require('graphql');
const mysql = require('mysql');

// Create a MySQL connection
const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'my_database'
});

// Define the GraphQL schema
const schema = buildSchema(`
  type User {
    id: ID!
    name: String!
    email: String!
  }

  type Query {
    getUser(id: ID!): User
    getAllUsers: [User]
  }

  type Mutation {
    createUser(name: String!, email: String!): User
    updateUser(id: ID!, name: String, email: String): User
    deleteUser(id: ID!): User
  }
`);

// Define the GraphQL resolvers
const rootResolver = {
  getUser: ({ id }) => {
    // Retrieve a user from the database
    const query = `SELECT * FROM users WHERE id = ${id}`;
    return new Promise((resolve, reject) => {
      connection.query(query, (err, results) => {
        if (err) reject(err);
        resolve(results[0]);
      });
    });
  },

  getAllUsers: () => {
    // Retrieve all users from the database
    const query = 'SELECT * FROM users';
    return new Promise((resolve, reject) => {
      connection.query(query, (err, results) => {
        if (err) reject(err);
        resolve(results);
      });
    });
  },

  createUser: ({ name, email }) => {
    // Insert a new user into the database
    const query = `INSERT INTO users (name, email) VALUES ('${name}', '${email}')`;
    return new Promise((resolve, reject) => {
      connection.query(query, (err, results) => {
        if (err) reject(err);
        resolve({ id: results.insertId, name, email });
      });
    });
  },

  updateUser: ({ id, name, email }) => {
    // Update a user in the database
    let setClause = '';
    if (name) setClause += `name = '${name}',`;
    if (email) setClause += `email = '${email}',`;
    setClause = setClause.slice(0, -1); // remove the trailing comma
    const query = `UPDATE users SET ${setClause} WHERE id = ${id}`;
    return new Promise((resolve, reject) => {
      connection.query(query, (err, results) => {
        if (err) reject(err);
        resolve({ id, name, email });
      });
    });
  },

  deleteUser: ({ id }) => {
    // Delete a user from the database
    const query = `DELETE FROM users WHERE id = ${id}`;
    return new Promise((resolve, reject) => {
      connection.query(query, (err, results) => {
        if (err) reject(err);
        resolve({ id });
      });
    });
  }
};

// Create an express app and attach the GraphQL middleware
const app = express();
app.use('/graphql', graphqlHTTP({
  schema,
  rootValue: rootResolver,
  graphiql: true
}));

// Start the server
app.listen(4000, () => console.log('Server started on http://localhost:4000/graphql'));

Once we’ve built the schema, we’ll create respective resolvers that compute and dispatch data. A resolver maps actions with functions, and for each query declared in the typedef, we create a resolver to return data. Finally, we’ll define an endpoint and pass configurations to complete server settings. We initialize /graphql as the endpoint for our app, pass the built schema and root resolver to the graphqlHTTP middleware, and enable the GraphiQL playground. GraphiQL is an interactive in-browser GraphQL IDE that helps us play around with the GraphQL queries we build.

Connecting the database

To consolidate data from multiple databases/sources, we can connect them in the resolvers. For this article

once the ApolloProvider is set up, we can request data from our GraphQL server. We pass the query we are trying to make to the useQuery Hook, and it will provide the result for us.

In the React app, I’ve made two queries, with and without arguments, to show how we should be handling queries and mutations in the front end. useQuery tracks error and loading states for us and will be reflected in the associated object. Once the server sends the result, it will be reflected by the data property.

For example, I’ve used the useQuery Hook to retrieve data from the GraphQL server, as shown below:

const getAllUsers = useQuery(GET_USERS);
const userInfo = useQuery(VIEW_USERS, { variables: { id: 1 }});

If there is an error or the data is still loading, we can use conditional rendering to display a spinner or an error message. Once the data has been loaded successfully, we can display it on the UI.

Here’s an example of how we can render the data returned from the GraphQL server:

function App() {
  const getAllUsers = useQuery(GET_USERS);
  const userInfo = useQuery(VIEW_USERS, { variables: { id: 1 }});
  
  if (getAllUsers.loading || userInfo.loading) {
    return <Spinner color="dark" />;
  }
  
  if (getAllUsers.error || userInfo.error) {
    return <React.Fragment>Error :(</React.Fragment>;
  }

  return (
    <div className="container">
      <Card>
        <CardHeader>Query - Displaying all data</CardHeader>
        <CardBody>
          <pre>
            {JSON.stringify(getAllUsers.data, null, 2)}
          </pre>
        </CardBody>
      </Card>
      <Card>
        <CardHeader>Query - Displaying data with args</CardHeader>
        <CardBody>
          <CardSubtitle>Viewing a user by id</CardSubtitle>
          <pre>
            {JSON.stringify(userInfo.data, null, 2)}
          </pre>
        </CardBody>
      </Card>
    </div>
  );
}

In the above example, we are rendering two Cards with different data. The first Card displays all the data retrieved from the GET_USERS query. The second Card displays the data returned from the VIEW_USERS query with the user ID specified as an argument.

To summarize, in this tutorial, we learned how to implement simple CRUD operations with GraphQL and React. We covered simple examples for reading and mutating data with React Hooks. We also demonstrated how to implement authentication, error handling, caching, and optimistic UI with Apollo Client.

We hope this tutorial helps you get started with implementing CRUD operations using React and GraphQL. If you have any questions or feedback, please feel free to leave a comment below.