How to build a React note-taking App with GraphQL

GraphQL is an open-source query and manipulation language for APIs, and a runtime for fulfilling those queries with your existing data. It enables front-end developers to write queries that return the exact data you want. It uses a syntax that describes how to ask for data and is generally used to load data from a server to a client.

GraphQL is language-agnostic as it can work with any server-side language. GraphQL aims to speed up development, improve the developer experience and offer better tooling. It’s often seen as an alternative to REST when building web applications.

How is GraphQL different from REST?

One of the major advantages GraphQL has over REST is that it allows you to get many resources in a single request. GraphQL queries access not just the properties of one resource but also smoothly follow references between them.

While typical REST APIs require loading from multiple URLs, GraphQL APIs get all the data your app needs in a single request.

In a bid to understand GraphQL on both the server and the frontend, you’ll build a note-taking app. The app would be able to do the following:

  • List all notes from the server
  • Add a new note by passing a title and a content to the server.
  • Edit a note by passing a title and content to the server
  • Delete a note from the server.

Prerequisites

  • Node.js and npm installed on your local machine. You can install both of these from the Node.js website here.
  • MongoDB installed on your local computer.
  • A text editor installed, such as Visual Studio Code, Atom, or Sublime Text.
  • Some experience with the React library.
  • Some working knowledge of Node.js
  • Node (8)
  • npm (5.2.0)

Installing Project Dependencies

To get started with your note-taking app, you’ll build your Node.js and GraphQL server by installing the dependencies for your project to your local development environment.

For this tutorial, you’ll create a project folder for your application named notetaking-api. Open your terminal and set up a project folder for your app to live in:

mkdir notetaking-api

Now navigate into your new project folder:

cd notetaking-api

First of all, you’ll need to create a package.json file to install all the required dependencies. The package.json file is needed to manage dependencies that might be needed in the app and also for writing scripts that help with build and test processes. Run the following command to generate a package.json file:

npm init --yes

It automatically creates a package.json file. With that done, let’s add the required dependencies with the following command:

npm install --save express graphql express-graphql graphql-tools mongoose nodemon
  • Express is a fast and minimal web framework for Node.js.
  • [graphql](https://github.com/graphql/graphql-js) is the JavaScript reference implementation for GraphQL.
  • [express-graphql](https://github.com/graphql/express-graphql) is a package that allows you to create a GraphQL HTTP server with Express.
  • [graphql-tools](https://github.com/apollographql/graphql-tools) is a package that allows us to build, mock, and stitch a GraphQL schema using the schema language.
  • [mongoose](https://github.com/Automattic/mongoose/) is an object modeling (ODM) tool that we’ll use to connect to MongoDB.
  • [nodemon](https://github.com/remy/nodemon) is a tool that listens for file changes in a Node app that automatically restarts the server.

Next, install the following dev dependencies:

npm install — save-dev babel-cli babel-preset-env babel-preset-stage-0

You’ll be writing ES6 code in this tutorial, therefore, you’ll need Babel to transpile the Node app. ECMAScript, or ES6 is a standardized name for JavaScript and it signifies the 6th version. It adds features like constants, block-scoped variables and functions, arrow functions, string templating and many more.

Now that the dependencies are installed, you can go ahead to create the express server for the Node application.

Creating the Express Server

In this section, you’ll create the foundation for the note-taking API, a minimal express server that listens on a port.

The first thing to do is to add a new script titled "start" to the package.json file which will be responsible for running the app. Next, open your package.json file in your text editor.

You can add the "start" script under the existing "test" script:

...
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon ./index.js --exec babel-node -e js"
  }
...

nodemon is used to start (and also listen for changes) the app which is at the entry point of the index.js file and the babel flag is added so as to transpile ES6 code.

To transpile the code, babel needs a config file. The file can be created by running the command below in the notetaking-api folder. The command below creates a .babelrc which will contain babel configurations.

touch .babelrc

Next, open the config file in your text editor and edit with the code below.

{
  "presets": ["env", "stage-0"]
}

Babel will look for a .babelrc in the current directory of the file being transpiled. In the code block above, we added a preset array which is essentially a configuration of how babel should transpile JavaScript code.

Let’s get things rolling by creating the Express server for the note-taking API. Create an index.js file in the notetaking-api folder, open the newly created file and edit with the code below:

import express from "express";const app = express();
const PORT = 4300;app.get("/", (req, res) => {
  res.json({
    message: "Notetaking API v1"
  });
});
app.listen(PORT, () => {
  console.log(`Server is listening on PORT ${PORT}`);
});

In the code block above, this app starts a server and listens on the port 4300 for connections. The express module is first imported from the express package. Next, the module function is assigned to a variable called app, and a variable called PORT is created which holds the port number in which the server will live .

Next, you’ll handle a GET request to our server using the .get() function. The .get() function takes two main parameters. The first is the URL for this function to act upon. In this case, we are targeting '/', which is the root of our API; in this case, localhost:4300.

Finally, the app is started using the .listen() function, which also tells the app which port to listen on by using the PORT variable created earlier.

Save the changes you just made to the index.js file and run the command below in the notetaking-api folder to run the app.

npm start

The express server will now be running on the port 4300 and you should see a JSON output when you navigate to http://localhost:4300/.

Connecting the MongoDB Server to Mongoose

In this step, you’ll see how to connect the already existing Express server to MongoDB via mongoose.

For the note-taking API, you’ll need to store the notes somewhere, this is where MongoDB comes in. MongoDB is a document-oriented, NoSQL database program. It enables storing data in JSON-like documents that can vary in structure.

Moongoose, meanwhile, is an Object Data Modeling (ODM) library for MongoDB and Node.js. It manages relationships between data, provides schema validation, and is used to translate between objects in code and the representation of those objects in MongoDB.

In this step, you’ll create a MongoDB database and connect it to the API using Mongoose.

import express from "express";
import mongoose from "mongoose";mongoose.Promise = global.Promise;
mongoose.connect("mongodb://localhost/notetaking_db", {
  useNewUrlParser: true,
  useUnifiedTopology: true
});const app = express();
const PORT = 4300;app.get("/", (req, res) => {
  res.json({
    message: "Notetaking API v1"
  });
});
app.listen(PORT, () => {
  console.log(`Server is listening on PORT ${PORT}`);
});

You first import the mongoose package and set it to connect to a MongoDB via a database called notetaking_db.

Since you’ll be using Mongoose, you can go ahead to create your model. Models are constructors that are defined manually. They allow you to access data from MongoDB in an object-oriented fashion. The first step to creating a model is defining the schema for it. In this case, the schema for the API will be created.

Therefore, create a folder named models in the notetaking-api folder,

mkdir models

And navigate into it:

cd models

Then in models, create a note.js file:

touch note.js

Now add the following content to note.js to define the schema:

import  mongoose from 'mongoose';
const Schema = mongoose.Schema;const NoteSchema = new Schema({
    title : {
        type: String,
        required: true
    },
    content: {
        type: String,
        required: true
    },
    date: {
        type: Date,
        default: Date.now
    }
});export default  mongoose.model('note', NoteSchema);

Let’s go back to the specs of the note-taking API as it’s going to help understand the schema above:

  • List all notes from the server
  • Add a new note by passing a title and a content to the server.
  • Edit a note by passing a title and content to the server
  • Delete a note from the server.

From the specs above, a note entry needs to have both a title and its content and that’s what was in the model above. The title schema accepts a string and has its required option set to true, same as the content schema. The date schema automatically logs the current date. MongoDB automatically creates an id so we need not worry about indexing.

In the section above, you created a MongoDB database and connected it to the Node.js app using Mongoose. You also created the necessary schema needed for the API. In the next section, you’ll see how to set up the GraphQL server.

Setting up the GraphQL Server

In this section, you’ll set up the GraphQL server so we can begin sending and getting data from the database. This will be done by installing some GraphQL dependencies and integrating them into the existing Node.js app.

Let’s get started by making some modifications to the index.js file. Open the index.js file and edit with the following code.

import express from "express";
import graphlHTTP from "express-graphql";
import mongoose from "mongoose";
import schema from "./schema";mongoose.Promise = global.Promise;
mongoose.connect("mongodb://localhost/notetaking_db", {
  useNewUrlParser: true,
  useUnifiedTopology: true
});const app = express();
const PORT = 4300;app.get("/", (req, res) => {
  res.json({
    message: "Notetaking API v1"
  });
});
app.use(
  "/graphql",
  graphlHTTP({
    schema: schema,
    graphiql: true
  })
);
app.listen(PORT, () => {
  console.log(`Server is listening on PORT ${PORT}`);
});

In the code block above, express-graphql is used to create a graphql server app based on a schema and resolver functions. Therefore, when you send a GraphQL query or mutation request, the middleware will be called and it runs the appropriate GraphQL operation.

A GraphQL schema is at the center of any GraphQL server as it helps describes the functionality available to the clients that connect to it. GraphQL queries are used by the client to request the data it needs from the server while a GraphQL mutation is a way of creating, updating and deleting existing data.

The graphiql option (which is set to true) in the graphqlHTTP middleware indicates that we’d like to make use of the web client which features a graphical user interface, GraphiQL (pronounced “Graphical”).

GraphiQL is the GraphQL integrated development environment (IDE). It is a tool that helps to query and mutate GraphQL data cleanly and easily. It has features like syntax highlighting, Intelligent type ahead of fields, arguments, types, real-time error highlighting and reporting an automatic query completion.

As seen in the notetaking-api/index.js file, The graphql endpoint requires a schema file though, which will be created below. Mongoose schemas are used to define the structure of the MongoDB document.

Creating a schema essentially consists of three things:

  • Referencing mongoose.
  • Defining the model.
  • Exporting the model.

Therefore, create a file named schema.js in the notetaking-api folder. The schema.js file is used to create the schema needed for the GraphQL server.

Open the newly created schema.js file and add the following code.

import { makeExecutableSchema } from 'graphql-tools';
import { resolvers } from './resolvers';const typeDefs = `
 type Note {
  _id: ID!
  title: String!,
  date: Date,
  content: String!
 }scalar Datetype Query {
  allNotes: [Note]
 }`;const schema = makeExecutableSchema({
    typeDefs,
    resolvers
});export default schema;

In the code block above, we created the GraphQL schema and defined our type definitions. GraphQL implements a human-readable schema syntax known as its Schema Definition Language, or “SDL”. The SDL is used to express the types available within a schema and how those types relate to each other.

The basic components of a schema are object types that represent an object you can fetch from the DB and the kind of fields to expect.

As seen in the code block above, SDL definition for a note entry into the DB is as follows:

type Note {
    _id: ID!
    title: String!,
    date: Date,
    content: String!
}scalar Datetype Query {
   allNotes: [Note]
}

The type Note declaration is a GraphQL Object Type, meaning it’s a type with some fields. It also defines the structure of a note model in the application.

_id, title, date , and content are fields in the Note type. ID and String are built in scalar types that resolve to a single scalar type and appending an exclamation mark ! means the field is non-nullable, meaning that the GraphQL service promises to always give you a value when you query this field.

A root Query type is also defined and it works when you try to fetch all the notes. It will run the allNotes resolver against this Query.

These are some of the default scalar types inbuilt with GraphQL:

  • Int: A signed 32‐bit integer.
  • Float: A signed double-precision floating-point value.
  • String: A UTF‐8 character sequence.
  • Boolean: true or false.
  • ID: The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache.

You’ll notice the Date type is missing. That’s because GraphQL doesn’t ship with it as a default scalar type however that can be fixed with the line of code below, which is what we did in schema up there.

scalar Date

The schema is created using the makeExecutableSchema function which also expects a resolver. Resolvers are the actual implementation of the schema we defined up there.

You can now create the resolvers.js file as referenced in the code above where we imported the resolvers function into the schema.js file. The resolvers.js file is used to create functions that will be used to either query some data or modify some data.

Therefore, create a file named resolvers.js in the notetaking-api folder. Open resolvers.js file and add the code below.

import Note from './models/note';
export const resolvers = {
    Query : {
       async allNotes(){
           return await Note.find();
       }
    }
};

In the schema above, you created a root Query that returned all the notes. Ordinarily, that won’t work the way it is, you’d need to hook it up to MongoDB and resolvers help with that.

When you try to execute the allNotes query, it will run the Query resolver and find all the notes from the MongoDB database.

With all of that done, let’s test what we have so far and try sending a query to GraphQL. We’ll be sending the query below in the GraphiQL interface.

{
    allNotes {
        _id
        title
        content
        date
    }
}

To do that, run the app by using npm start and navigate to the GraphQL web client (GraphiQL) via this URL http://localhost:4300/graphql. You can then run the allNotes query as seen above.

Copy the code above and paste in the left pane of the GraphiQL interface, then click on the play button.

The expected output on the right pane should be an empty array, rightly so because we haven’t added a note entry to the database yet.

How to build a React note-taking app with GraphQL

In this step, the GraphQL server was successfully set up. In addition to that, the GraphQL schema for the API was created as well as the necessary resolver functions that help to either read data from the server.

Modifying Notes in the Database

In this section, you’ll implement the functionality to add, retrieve, update, and delete notes in your database. You’ll achieve this by creating resolver functions to make queries or mutations to the GraphQL server.

To add notes to the database, you’ll need to create a mutation in the resolvers.js file. The mutation will help to add a new record in MongoDB. Mutations are a way of modifying data in GraphQL and they work just like a query.

Open the resolvers.js file and edit with the code below.

import Note from "./models/note";
export const resolvers = {
  Query: {
    async allNotes() {
      return await Note.find();
    }
  },
  Mutation: {
    async createNote(root, { input }) {
      return await Note.create(input);
    }
  }
};

We’ll also need to make the appropriate changes in the schema.js file.

import { makeExecutableSchema } from "graphql-tools";
import { resolvers } from "./resolvers";const typeDefs = `
 type Note {
  _id: ID!
  title: String!,
  date: Date,
  content: String!
 }scalar Datetype Query {
  allNotes: [Note]
 }input NoteInput {
  title: String!
  content: String!
 }type Mutation {
  createNote(input: NoteInput) : Note
 }`;const schema = makeExecutableSchema({
  typeDefs,
  resolvers
});export default schema;

In the code block above, we defined a type, Mutation, and assigned it to the createNote resolver we wrote above. We also added a new input type which is NoteInput. You need to pass the input argument while sending the createNote mutation. The return type of this mutation is Note.

With that done, let’s try adding a Note by running a mutation to the database via GraphiQL. Run the mutation below in the GraphiQL interface.

If the app is not running you can run the command below and go ahead to navigate to localhost:4300/graphql in your browser.

mutation {
  createNote(input: {
    title: "My First note",
    content: "Here's the content in it",
  })
  {
    _id
    title
    content
    date
  }
}

How to build a React note-taking app with GraphQL

Next thing to do is to get a single note from the database. To get a single note from the database, we’ll need to create a new getNote type in schema.js file. The idea is to be able to get a note by its id.

import { makeExecutableSchema } from 'graphql-tools';
import { resolvers } from './resolvers';const typeDefs = `type Note {
  _id: ID!
  title: String!,
  date: Date,
  content: String!
 }scalar Datetype Query {
  getNote(_id: ID!): Note
  allNotes: [Note]
 }input NoteInput {
  title: String!
  content: String!
 }type Mutation {
  createNote(input: NoteInput) : Note
 }
`;const schema = makeExecutableSchema({
    typeDefs,
    resolvers
});export default schema;

The getNote type also accepts an id parameter, and the return type of the getNote is Note. Next thing to do is to create the resolver for the getNote query. Open up the resolvers.js file and edit with the code below.

import Note from './models/note';
export  const resolvers = {
    Query : {
       async getNote(root, {_id}){
           return await Note.findById(_id);
       },
       async allNotes(){
           return await Note.find();
        }
    },
    Mutation: {
        async createNote(root, {input}){
           return await Note.create(input);
        }
    }
};

In the code block above, we added a getNote resolver that uses mongoose’s findById function to find a note entry by its ID. Let’s see this in action by running the query below in the GraphiQL interface. You can use the id of the note created earlier above to search.

{
  getNote(_id: "5d8ab091330c4b44c8a24a6e") {
    _id
    title
    content
    date
  }
}

Note: Please replace the ID above (5d8ab091330c4b44c8a24a6e) with the actual ID returned from your GraphiQL
How to build a React note-taking app with GraphQL

Next, we’ll see how to update notes in the database. To update a note’s entry in the DB, an updateNote mutation has to be created in the schema.js file.

import { makeExecutableSchema } from 'graphql-tools';
import { resolvers } from './resolvers';const typeDefs = `
 type Note {
  _id: ID!
  title: String!,
  date: Date,
  content: String!
 }scalar Datetype Query {
  getNote(_id: ID!): Note
  allNotes: [Note]
 }input NoteInput {
  title: String!
  content: String!
 }input NoteUpdateInput {
   title: String
   content: String
}type Mutation {
  createNote(input: NoteInput) : Note
  updateNote(_id: ID!, input: NoteUpdateInput): Note
}
`;const schema = makeExecutableSchema({
    typeDefs,
    resolvers
});export default schema;

A new mutation is created, updateNote, it accepts a parameter of the id and the NoteUodateInput which contains the updated data (title or content) that needs to be updated.

Let’s create the resolver for the updateNote mutation. Open up the resolvers.js file and edit with the code below.

import Note from './models/note';
export  const resolvers = {
    Query : {
       async getNote(root, {_id}){
           return await Note.findById(_id);
       },
       async allNotes(){
           return await Note.find();
        }
    },
    Mutation: {
        async createNote(root, {input}){
           return await Note.create(input);
        },
        async updateNote(root, {_id, input}){
            return await Note.findOneAndUpdate({_id},input,{new: true})
        }
    }
};

In the code block above, the updateNote function uses the findOneAndUpdate function to update the particular note’s entry in the DB. As the name suggests, it finds that particular id in the DB and updates it.

We can test updating a note by running the mutation below in the GraphiQL interface.

mutation {
  updateNote(_id: "5ba70100f24d5027d7394d68", input: {
    title: "My First note",
    content:"Here's content in it"
  })
  {
    title
    content
  }
}

Note: Please replace the ID above (5ba70100f24d5027d7394d68) with the actual ID returned from your GraphiQL

How to build a React note-taking app with GraphQL

Next, we’ll see how to delete notes in the database. To delete notes in the DB, the process is similar to the ones above. You’d first need to create a deleteNote mutation in the schema.js file and then create a resolver for it in the resolvers.js file.

import { makeExecutableSchema } from 'graphql-tools';
import { resolvers } from './resolvers';const typeDefs = `type Note {
  _id: ID!
  title: String!,
  date: Date,
  content: String!
 }scalar Datetype Query {
  getNote(_id: ID!): Note
  allNotes: [Note]
 }input NoteInput {
  title: String!
  content: String!
 }type Mutation {
  createNote(input: NoteInput) : Note
  updateNote(_id: ID!, input: NoteInput): Note
  deleteNote(_id: ID!) : Note
 }
`;const schema = makeExecutableSchema({
    typeDefs,
    resolvers
});export default schema;

The deleteNote mutation accepts a parameter of id which will be used to identify the particular note to be removed from the database. Let’s create the appropriate resolver for the newly created mutation.

import Note from './models/note';
export  const resolvers = {
    Query : {
       async getNote(root, {_id}){
           return await Note.findById(_id);
       },
       async allNotes(){
           return await Note.find();
        }
    },
    Mutation: {
        async createNote(root, {input}){
           return await Note.create(input);
        },
        async updateNote(root, {_id, input}){
            return await Note.findOneAndUpdate({_id},input,{new: true})
        },
        async deleteNote(root, {_id}){
            return await Note.findOneAndRemove({_id});
        }
    }
};

In the code block above, the deleteNote mutation finds the note by its id and then uses the findOneAndRemove function to remove from the database.

You can test deleting a note by running the mutation below in the GraphiQL interface.

mutation {
  deleteNote(_id: "5ba70100f24d5027d7394d68")
  {
    title
    content
  }
}

Note: Please replace the ID above (5ba70100f24d5027d7394d68) with the actual ID returned from your GraphiQL

How to build a React note-taking app with GraphQL

You’ve now successfully created your GraphQL API server. It’s a minimal server that does the required CRUD operations. In the next part of this tutorial, you’ll set up your React application that will interface with the GraphQL server.

Therefore you’ll need to build the UI needed to add, edit and delete notes.

You’ll also install all the necessary dependencies needed to build the app’s UI.

You’ll be using the create-react-app CLI tool to build the React app. It allows you to create React apps with no need for any build configuration.

Create a React app by running the command below in your terminal. This creates a folder named notetaking-ui in your directory.

You’ll also install all the necessary dependencies needed to build the app’s UI.

You’ll be using the create-react-app CLI tool to build the React app. It allows you to create React apps with no need for any build configuration.
Create a React app by running the command below in your terminal. This creates a folder named notetaking-ui in your directory.

npx create-react-app notetaking-ui

When the installation is done, you will have a working React app in the notetaking-ui folder. Navigate into the folder and run the yarn start command to start the app in development mode.

You will be building a minimal React app, therefore, the following views are needed for the React app:

  • A view to see all the notes listed.
  • A view to edit notes.
  • A view to add notes.

To that end, create the following files in the src folder, AllNotes.js, EditNote.js, NewNote.js. We’ll edit them later on.

Now, you’ll install the required dependencies for building the UI for your note-taking app.

yarn add react-router-dom

This command installs the react-router package which is a great library for handling routing in React apps. We’ll also be making use of Bulma, a CSS framework, to help with styling the React app. Add the line of code below to the head tag in the public/index.html file to add the Bulma framework to the app.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css">

To set up the routes for the React app, you’ll need to make use of react-router to define the routes. Open up the App.js file and edit with the code below:

import React, { Component } from 'react';
import {
  BrowserRouter as Router,
  Route,
  Link
} from 'react-router-dom'
import './App.css';
import AllNotes from './src/AllNotes'
import NewNote from './src/NewNote'
import EditNote from './src/EditNote'class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <nav className="navbar App-header" role="navigation" aria-label="main navigation">
            <div className="navbar-brand">
              <Link to="/" className="navbar-item">
                NotesQL
              </Link>
            </div><div className="navbar-end">
              <Link to="/" className="navbar-item">
                All Notes
              </Link><Link to="/newnote" className="navbar-item">
                New Note
              </Link>
            </div>
          </nav><Route exact path="/" component={AllNotes}/>
          <Route path="/newnote" component={NewNote}/>
          <Route path="/note/:id" component={EditNote}/>
        </div>
      </Router>
    );
  }
}export default App;

In the code block above, all the components needed for react-router are imported, as well as the components for the views created above.

In the render function, the whole layout is wrapped in the <Router> component. This helps by creating a history object to keep track of the location. Therefore, when there’s a location change, the app will be r-rendered. The Link component is used to navigate through the different routes.

Finally, the code block below is where the actual routes are defined:

The / route is the homepage of the app and will always point to the AllNotes component which displays all the notes, the /newnote route is pointed to the NewNote component which contains the view to add a new note and the /note/:id route is pointed to the EditNote component which contains the view needed to edit a note. The id bit is what will be used to fetch that particular note’s detail from the database.

Creating the React App views

In this step, we’ll create the views needed for the note-taking app. In the previous step, we used the create-react-app CLI to create a functional React application. As a reminder, the views to be created are:

  • View all existing notes
  • Add a new note
  • Edit an existing note

Therefore, let’s start with the view for all notes. Open up the previously created AllNotes.js file and add the code below to the file.

import React from "react";
import { Link } from "react-router-dom";const AllNotes = () => {
  let data = [1, 2, 3, 4, 5];
  return (
    <div className="container m-t-20">
      <h1 className="page-title">All Notes</h1><div className="allnotes-page">
        <div className="columns is-multiline">
          {data.length > 0
            ? data.map((item, i) => (
                <div className="column is-one-third" key={i}>
                  <div className="card">
                    <header className="card-header">
                      <p className="card-header-title">Component</p>
                    </header>
                    <div className="card-content">
                      <div className="content">
                        Lorem ipsum dolor sit amet, consectetur adipiscing elit.
                        Phasellus nec iaculis mauris. Lorem ipsum dolor sit
                        amet.
                        <br />
                      </div>
                    </div>
                    <footer className="card-footer">
                      <Link to={`note/${i}`} className="card-footer-item">
                        Edit
                      </Link>
                      <a href="#" className="card-footer-item">
                        Delete
                      </a>
                    </footer>
                  </div>
                </div>
              ))
            : "No Notes yet"}
        </div>
      </div>
    </div>
  );
}export default AllNotes;

In the code block above, we’re using the Cards component from Bulma as the layout for displaying the notes. The Card component is an all-around flexible and composable component that’s used to display information in a concise manner.

Note that we’re iterating through the data array as an example, ideally, it would be the results from the database.

We’re also doing a check in the render function to check if there are available data. If there is, we display accordingly and if not a message of “No Notes yet” is displayed.

Next, you’re going to add some custom CSS for the components created above, so go ahead to update the stylesheets accordingly.

Edit the src/App.css file with the code below.

.App-header {
  background-color: #222;
  color: white;
}
​
.navbar-item {
  color: white;
}
​
.App-title {
  font-size: 1.5em;
}
​
.App-intro {
  font-size: large;
}
​
.m-t-20 {
  margin-top: 20px;
}
​
.card {
  box-shadow: 0 1px 5px 1px rgba(0, 0, 0, 0.1);
}
​
.page-title {
  font-size: 2rem;
  margin-bottom: 20px;
}
​
.newnote-page {
  height: calc(100vh - 52px);
  width: 60%;
}
​
.field:not(:last-child) {
  margin-bottom: 2.75rem;
}
​
.card button {
  border: none;
  color: #3273dc;
  cursor: pointer;
  text-decoration: none;
  font-size: 16px;
}
.card button:hover {
  color: #363636;
}

How to build a React note-taking app with GraphQL

Next, let’s create the view for adding a new note. Open up the NewNote.js file and add the code below to the file.

import React from "react";
const NewNote = () => {
  return (
    <div className="container m-t-20">
      <h1 className="page-title">New Note</h1>
​
      <div className="newnote-page m-t-20">
        <form>
          <div className="field">
            <label className="label">Note Title</label>
            <div className="control">
              <input className="input" type="text" placeholder="Note Title" />
            </div>
          </div>
​
          <div class="field">
            <label class="label">Note Content</label>
            <div class="control">
              <textarea class="textarea" rows="10" placeholder="Note Content here..."></textarea>
            </div>
          </div>
​
​
          <div class="field">
            <div class="control">
              <button class="button is-link">Submit</button>
            </div>
          </div>
​
        </form>
      </div>
    </div>
  )
}
​
export default NewNote;

In the code bock above, a form is created and it contains an input field and a textarea field for the note title and note content respectively.

You’ll hook the form up to a function that does the actual addition of note later on.

How to build a React note-taking app with GraphQL

Lastly for the views, let’s add the code for editing an existing view. Open up the EditNote.js file and add the code below to the file.

import React from "react";
const EditNote = () => {
  return (
    <div className="container m-t-20">
      <h1 className="page-title">Edit Note</h1>
​
      <div className="newnote-page m-t-20">
        <form>
          <div className="field">
            <label className="label">Note Title</label>
            <div className="control">
              <input className="input" type="text" placeholder="Note Title" />
            </div>
          </div>
​
          <div class="field">
            <label class="label">Note Content</label>
            <div class="control">
              <textarea
                class="textarea"
                rows="10"
                placeholder="Note Content here..."
              ></textarea>
            </div>
          </div>
​
          <div class="field">
            <div class="control">
              <button class="button is-link">Submit</button>
            </div>
          </div>
        </form>
      </div>
    </div>
  );
}
​
export default EditNote;

In the code bock above, a form is created and it features and input field and a textarea field for the note title and note content respectively.

We’ll hook the form up to a function that does the actual editing of the note data later on.

How to build a React note-taking app with GraphQL

In the step above, the following views were created for the note-taking app:

  • View all existing notes
  • Add a new note
  • Edit an existing note

You can go ahead to start the app in development mode to see the progress made so far. Remember the command yarn start starts the React app in development mode.

In the next step, the functionality required to add, delete and view existing notes will be added to the React app.

Connecting the React App to the GraphQL API

The next step in this tutorial is connecting the React app to the GraphQL API. You’ll be using ApolloClient to interface with the GraphQL API.

ApolloClient is a GraphQL client that help with declarative data fetching from a GraphQL server. It has some built in features that help to implement data caching, pagination, subscriptions e.t.c out of the box.

This helps developers to write less code and with a better structure. One of the best parts about ApolloClient is that it’s adoptable anywhere, in the sense that it can be dropped into any JavaScript app with a GraphQL server and it works.

Let’s get started on connecting the React app to the GraphQL server. The first thing to do is install the various ApolloClient dependencies.

yarn add apollo-boost @apollo/react-hooks graphql graphql-tag apollo-cache-inmemory apollo-link-http
  • apollo-boost: A package containing everything you need to set up ApolloClient.
  • @apollo/react-hooks: React hooks based view layer integration.
  • graphql: Used in parsing your GraphQL queries.
  • graphql-tag: Helpful utilities for parsing GraphQL queries.
  • apollo-cache-inmemory: cache implementation for ApolloClient.
  • apollo-link-http: a standard interface for modifying control flow of GraphQL requests and fetching GraphQL results.

With that done, let’s initiate ApolloClient in our React app. The only thing needed here is the endpoint to our GraphQL server. In our index.js file, let’s import ApolloClient from apollo-boost and add the endpoint for our GraphQL server to the uri property of the client config object.

Edit thesrc/index.js file with the following code.

import React from "react";
import ReactDOM from "react-dom";import { ApolloProvider } from "@apollo/react-hooks";
import { ApolloClient } from "apollo-client";
import { createHttpLink } from "apollo-link-http";
import { ApolloLink } from "apollo-link";
import { InMemoryCache } from "apollo-cache-inmemory";import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";const httpLink = createHttpLink({ uri: "http://localhost:4300/graphql" });const link = ApolloLink.from([httpLink]);const client = new ApolloClient({
  link,
  cache: new InMemoryCache()
});ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById("root")
);// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

In the code block above, the required dependencies are imported from the installed packages. Next, an httpLink constant variable is created and connected to the ApolloClient instance with the GraphQL API. Recall that the GraphQL server is running on http://localhost:4300. ApolloClient is then initialized by passing in the httpLink and a new instance of an InMemoryCache.

Finally, the app is rendered with the root component. App is wrapped with the higher-order component ApolloProvider that gets passed the client as a prop.

Fetching all notes from the GraphQL server

Next thing to do is fetching the all the notes from the GraphQL API and displaying them with the UI already built. To do that, you’ll need to define the query to be sent to GraphQL. Remember while building the GraphQL server we wrote the query to be used in fetching notes on the GraphiQL interface. We’ll do the same here.

Let’s add this query to the AllNotes component. Navigate to the src/AllNotes.js file and let’s start editing.

import React, { Component } from 'react';
import { useQuery } from '@apollo/react-hooks';
import gql from 'graphql-tag';
import { Link } from 'react-router-dom';const NOTES_QUERY = gql`
{
  allNotes {
    title
    content
    _id
    date
  }
}
`

The query is written here as a JavaScript constant and being parsed with gql. Next, let’s get the display the result from the API and display them with the UI built. In the same AllNotes.js file, replace the existing function with the one below.

const AllNotes = () => {
  const { loading, error, data } = useQuery(NOTES_QUERY);
​
  if (loading) return "Loading...";
  if (error) return `Error! ${error.message}`;
​
  return (
    <div className="container m-t-20">
      <h1 className="page-title">All Notes</h1>
​
      <div className="allnotes-page">
        <div className="columns is-multiline">
          {data.allNotes.map(note => (
            <div className="column is-one-third" key={note._id}>
              <div className="card">
                <header className="card-header">
                  <p className="card-header-title">{note.title}</p>
                </header>
                <div className="card-content">
                  <div className="content">
                    {note.content}
                    <br />
                  </div>
                </div>
                <footer className="card-footer">
                  <Link to={`note/${note._id}`} className="card-footer-item">
                    Edit
                  </Link>
                  <a href="#" className="card-footer-item">
                    Delete
                  </a>
                </footer>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

Here we’re using the useQuery React Hook to fetch some notes from our GraphQL server and displaying them on the UI. useQuery is the primary API for executing queries in an Apollo application. To run a query within a React component, call useQuery and pass it a GraphQL query string like we did above.

useQuery returns an object from Apollo Client that contains loading, error, and data properties. These props help provide information about the data request to the GraphQL server.

  • loading: Is true as long as the request is still ongoing and the response hasn’t been received.
  • error: In case the request fails, this field will contain information about what exactly went wrong.
  • data: This is the actual data that was received from the server.

Let’s check our progress. You can do a page refresh or start the app if you haven’t with the command yarn start. You should see an error message, specifically, the error message we set above.

How to build a React note-taking app with GraphQL
Upon further inspection which can be done by checking the console of the browser, we can ascertain that it’s a CORS issue.
How to build a React note-taking app with GraphQL

Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin. - MDN

Simply put this means we have to enable our GraphQL server to accept requests from the React app. This can be done by using the cors package which is a node.js package that can be used to enable CORS with various options.

So go back to the notetaking-api project and install the package with the command below.

npm i cors

And proceed to use it in your server by editing the index.js file in the notetaking-api project with the following.

import cors from "cors";
import express from "express";
import graphlHTTP from "express-graphql";
import mongoose from "mongoose";
import schema from "./schema";
​
mongoose.Promise = global.Promise;
mongoose.connect("mongodb://localhost/notetaking_db", {
  useNewUrlParser: true,
  useUnifiedTopology: true
});
​
const app = express();
const PORT = 4300;
​
app.use(cors());
​
app.get("/", (req, res) => {
  res.json({
    message: "Notetaking API v1"
  });
});
app.use(
  "/graphql",
  graphlHTTP({
    schema: schema,
    graphiql: true
  })
);
app.listen(PORT, () => {
  console.log(`Server is listening on PORT ${PORT}`);
});
​

Now if you refresh the React app, you should get the data from the GraphQL being fetched and displayed.

How to build a React note-taking app with GraphQL

Awesome! You’ve now handled fetching data from the GraphQL server by fetching the list of notes.

Before we go any further, let’s handle errors from the GraphQL server and add a notification system to the React app. To handle errors from the GraphQL server and notifications generally, we’d need to make use of react-notify-toast and apollo-link-error.

react-notify-toast is a React library that help with toast notifications for React apps and apollo-link-error helps to capture GraphQL or network errors. Install both packages with the command below.

yarn add react-notify-toast apollo-link-error

Once installation is done, modify your index.js file to look like the one below.


...
import { onError } from 'apollo-link-error'
import Notifications, {notify} from 'react-notify-toast';
...
​
const errorLink = onError(({ graphQLErrors }) => {
  if (graphQLErrors) graphQLErrors.map(({ message }) => notify.show(message, 'error'))
})
​
const httpLink = createHttpLink({ uri: 'http://localhost:4300/graphql' });
​
const link = ApolloLink.from([
  errorLink,
  httpLink,
]);
​
const client = new ApolloClient({
  link,
  cache: new InMemoryCache()
})
​
ReactDOM.render(
  <ApolloProvider client={client}>
    <Notifications />
    <App />
  </ApolloProvider>,
  document.getElementById('root')
);
registerServiceWorker();

In the code above, the important bit is where the errorLink variable is being created. We are essentially looking out from errors from GraphQL and then displaying them nicely with the notify component.

In the preceding code block, you added the errorLink constant variable which essentially uses the onError function to show a notification whenever there’s an error from the GraphQL server.

Adding notes to GraphQL Server

Let’s see how to add notes to the GraphQL server from the React app. You’ll be editing the NewNote.js file in the src folder.

...
import { useMutation } from "@apollo/react-hooks";
import gql from 'graphql-tag';
​
const NEW_NOTE = gql`
mutation createNote($title: String! $content: String!) {
  createNote( input: {title: $title, content: $content}) {
    _id
    title
    content
    date
  }
}
`
...

In the code block above, we’re utilising the createNote mutation that was defined earlier when we were building out the API.

The useMutation React hook is the primary API for executing mutations in an Apollo application. To run a mutation, you first call useMutation within a React component and pass it a GraphQL string that represents the mutation.

Therefore, we’ll create a GraphQL mutation named NEW_NOTE. The server expects a title and a content to successfully create a new entry, and it returns the id, title, content upon creation.

Next step is to edit the NewNote function so that it utilizes the NEW_NOTE mutation . Edit the file with the code below.

import React, { useState } from "react";
import { withRouter } from "react-router-dom";
import { useMutation } from "@apollo/react-hooks";
import gql from "graphql-tag";const NEW_NOTE = gql`
  mutation createNote($title: String!, $content: String!) {
    createNote(input: { title: $title, content: $content }) {
      _id
      title
      content
      date
    }
  }
`;const NOTES_QUERY = gql`
  {
    allNotes {
      title
      content
      _id
      date
    }
  }
`;const NewNote = () => {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");const [createNote] = useMutation(NEW_NOTE, {
    update(
      cache,
      {
        data: { createNote }
      }
    ) {
      const { allNotes } = cache.readQuery({ query: NOTES_QUERY });cache.writeQuery({
        query: NOTES_QUERY,
        data: { allNotes: allNotes.concat([createNote]) }
      });
    }
  });return (
    <div className="container m-t-20">
      <h1 className="page-title">New Note</h1><div className="newnote-page m-t-20">
        <form
          onSubmit={e => {
            e.preventDefault();createNote({
              variables: {
                title,
                content,
                date: Date.now()
              }
            });
            history.push("/");
          }}
        >
          <div className="field">
            <label className="label">Note Title</label>
            <div className="control">
              <input
                className="input"
                name="title"
                type="text"
                placeholder="Note Title"
                value={title}
                onChange={e => setTitle(e.target.value)}
              />
            </div>
          </div><div className="field">
            <label className="label">Note Content</label>
            <div className="control">
              <textarea
                className="textarea"
                name="content"
                rows="10"
                placeholder="Note Content here..."
                value={content}
                onChange={e => setContent(e.target.value)}
              ></textarea>
            </div>
          </div><div className="field">
            <div className="control">
              <button className="button is-link">Submit</button>
            </div>
          </div>
        </form>
      </div>
    </div>
  );
};export default NewNote;

When your component renders, useMutation returns a tuple that includes:

  • A mutate function that you can call at any time to execute the mutation
  • An object with fields that represent the current status of the mutation’s execution

The form also has a onSubmit function which allows you to pass title and content as variables props to the GraphQL server.

One other thing we’re doing in the useMutation query is updating the cache otherwise known as interface update. This is essentially ensuring that whatever updates we make to the GraphQL server is also effected on the client in realtime without the need for a page refresh. To do this, the call to useMutation includes an update function. Let’s have a closer look at the update function.

const [createNote] = useMutation(NEW_NOTE, {
    update(cache, { data: { createNote } }) {
      const { allNotes } = cache.readQuery({ query: NOTES_QUERY });cache.writeQuery({
        query: NOTES_QUERY,
        data: { allNotes: allNotes.concat([createNote]) }
      });
    }
  });

The update function is passed a cache object that represents the Apollo Client cache and a data property that contains the result of the mutation. The cache object provides readQuery and writeQuery functions that enable you to execute GraphQL operations on the cache as though you’re interacting with a GraphQL server.

In the code block above, the update function first reads the existing notes from the cache with cache.readQuery. It then adds the newly created note from our mutation to the existing list of notes and writes it back to the cache with cache.writeQuery.

Now, whenever you add a new note, the UI will update to reflect newly cached values.

Next, let’s edit the code so that after a note has been added, the app automatically redirects the notes listing page. To do that, you’d need to import withRouter from react-router-dom in the NewNote.js file. withRouter will be used as a higher order function so that means the NewNote function will be enclosed inside the withRouter function as seen below.

...
import {withRouter} from 'react-router-dom';
...const NewNote = withRouter(({ history }) => {
  ...
});
...

With that done, go ahead to add the line of code below immediately after the createNote function that takes in the variables.

...
createNote({
  variables: {
    title,
    content
  }
});
history.push("/");
...

You’ve now handled the functionality for adding new notes to the GraphQL server. Next, you’ll see how to handle the functionality for editing existing notes.

Editing notes in the GraphQL Server

As seen in the initial React app view, there’s an Edit button that allows you to edit the data in the database. The Edit button is hooked up to the route note/:id where id is the ID of the note we wish to edit. This is done so that we can easily use the ID to find the data in the database and update using Mongoose’s findOneAndUpdate function.

If you check the AllNotes component, you’d see that we’re already passing the ID to the Link component.

<Link to={`note/${note._id}`} className="card-footer-item">Edit</Link>

If you click on the Edit button, it takes you to the Edit Note view but we don’t have the logic to either fetch that particular note’s detail or edit it. Let’s do that now. You start by fetching the note’s details itself.

To fetch the details for a particular note, you’ll first have to write a GraphQL query to fetch the note from the server. In the EditNote.js file, add the code below.

import React, { useState } from "react";
import { useQuery, useMutation } from "@apollo/react-hooks";
import {notify} from 'react-notify-toast';
import gql from 'graphql-tag';const NOTE_QUERY = gql`
query getNote($_id: ID!) {
  getNote (_id: $_id) {
    _id
    title
    content
    date
  }
}
`
...

In the query above, you’re utilising the getNote earlier defined when building the API. The server expects just the _id to successfully fetch the note.

Next, we’ll write the query that will help with updating the notes and since we’ll be sending data, it’s going to be a mutation. Add the block of code below just after the NOTE_QUERY.

...
const UPDATE_NOTE = gql`
  mutation updateNote($_id: ID!, $title: String, $content: String) {
    updateNote(_id: $_id, input: { title: $title, content: $content }) {
      _id
      title
      content
    }
  }
`;
...

In the query above, we’re creating a mutation function that accepts the _id_, title and content as variables. These variables will be sent to the GraphQL server where the appropriate action will be carried out, that is, updating a note.

Next, you’ll define the state using useState that will hold both the title and content of the note we’d like to edit and also implement the Query that will fetch the data from the GraphQL server.

Replace the entire EditNote function with the one below.

const EditNote = ({ match }) => {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");const { loading, error, data } = useQuery(NOTE_QUERY, {
    variables: {
      _id: match.params.id
    }
  });const [updateNote] = useMutation(UPDATE_NOTE);if (loading) return <div>Fetching note</div>;
  if (error) return <div>Error fetching note</div>;// set the  result gotten from rhe GraphQL server into the note variable.
  const note = data;return (
    <div className="container m-t-20">
      <h1 className="page-title">Edit Note</h1><div className="newnote-page m-t-20">
        <form
          onSubmit={e => {
            // Stop the form from submitting
            e.preventDefault();// set the title of the note to the title in the state, if not's available set to the original title gotten from the GraphQL server
            // set the content of the note to the content in the state, if not's available set to the original content gotten from the GraphQL server
            // pass the id, title and content as variables to the UPDATE_NOTE mutation.
            updateNote({
              variables: {
                _id: note.getNote._id,
                title: title ? title : note.getNote.title,
                content: content ? content : note.getNote.content
              }
            });notify.show("Note was edited successfully", "success");
          }}
        >
          <div className="field">
            <label className="label">Note Title</label>
            <div className="control">
              <input
                className="input"
                type="text"
                name="title"
                placeholder="Note Title"
                defaultValue={note.getNote.title}
                onChange={e => setTitle(e.target.value)}
                required
              />
            </div>
          </div><div className="field">
            <label className="label">Note Content</label>
            <div className="control">
              <textarea
                className="textarea"
                rows="10"
                name="content"
                placeholder="Note Content here..."
                defaultValue={note.getNote.content}
                onChange={e => setContent(e.target.value)}
                required
              ></textarea>
            </div>
          </div><div className="field">
            <div className="control">
              <button className="button is-link">Submit</button>
            </div>
          </div>
        </form>
      </div>
    </div>
  );
};

In the code block above, the useQuery hook contains the NOTE_QUERY query string and an object that contains a variable. In this case the variable is the match.params.id 's value.

useQuery returns an object from Apollo Client that contains loading, error, and data properties. These props help provide information about the data request to the GraphQL server.

The result of the query to the GraphQL server will be stored in the data property which is later set to the const note .

In the form, you’re setting the defaultValue for the input field and textarea to the values gotten from the GraphQL server.

Finally, the form has its own onSubmit function that handles the actual editing of the note. In the onSubmit function, the title and content of the note is sent to the updateNote function which is in turn attached to the useMutation hook.

You’ve now handled the functionality for editing existing notes in the GraphQL server. Next thing to do is to handle deletion of notes from the GraphQL server.

Deleting notes from the GraphQL Server

To delete notes in the database via the React app, you’ll need to make use of the useMutation hook and then use it to send a mutation query (deleteNote) to the GraphQL server.

The first thing we need to do is import the useMutation hook from apollo/react-hooks in the AllNotes.js file and write the query that performs the delete action and that will be sent to the GraphQL server, and then use the useMutation hook to send the query to the server. Edit the AllNotes.js file with the code below.

...
import { useQuery, useMutation } from "@apollo/react-hooks";
import { notify } from "react-notify-toast";...
const DELETE_NOTE_QUERY = gql`
mutation deleteNote($_id: ID!) {
  deleteNote (_id: $_id) {
    title
    content
 _id
  }
}
`const AllNotes = () => {
  const { loading, error, data } = useQuery(NOTES_QUERY);const [deleteNote] = useMutation(DELETE_NOTE_QUERY, {
    update(cache, { data: { deleteNote }}) {
      const { allNotes } = cache.readQuery({ query: NOTES_QUERY });
      const newNotes = allNotes.filter(note => note._id !== deleteNote._id);cache.writeQuery({
        query: NOTES_QUERY,
        data: { allNotes: newNotes }
      });
    }
  });if (loading) return "Loading...";
  if (error) return `Error! ${error.message}`;return (
    <div className="container m-t-20">
      <h1 className="page-title">All Notes</h1><div className="allnotes-page">
        <div className="columns is-multiline">
          {data.allNotes.map(note => (
            <div className="column is-one-third" key={note._id}>
              <div className="card">
                <header className="card-header">
                  <p className="card-header-title">{note.title}</p>
                </header>
                <div className="card-content">
                  <div className="content">
                    {note.content}
                    <br />
                  </div>
                </div>
                <footer className="card-footer">
                  <Link to={`note/${note._id}`} className="card-footer-item">
                    Edit
                  </Link>
                  <button
                    onClick={e => {
                      e.preventDefault();
                      deleteNote({ variables: { _id: note._id } });
                      notify.show("Note was deleted successfully", "success");
                    }}
                    className="card-footer-item"
                  >
                    Delete
                  </button>
                </footer>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};
...

In the code block above, we create the deleteNote function and set it to useQuery hook which contains a GraphQL query string. The deleteNote function is later used in the delete’s button onClick handler. It accepts the note’s ID as a variable and then that ID is used to find and delete the entry.

We’re also carrying out interface update here when we delete a note. The useMutation hook also accepts an object which contains an update function in which we’ll be carrying out the interface update.

update(cache, { data: { deleteNote }}) {
  const { allNotes } = cache.readQuery({ query: NOTES_QUERY });
  const newNotes = allNotes.filter(note => note._id !== deleteNote._id);cache.writeQuery({
    query: NOTES_QUERY,
    data: { allNotes: newNotes }
  });
}

In the function above, we first read the available notes using the cache.readQuery method. The readQuery allows us to fetch data without actually making a request to the GraphQL server.

The .filter method is used to check for the particular note that deleted from the page. It works by comparing the _id_ of the deleted note with that of all existing notes and then returns only items that don’t match that of the deleted item. The _id_ of the deleted item is gotten by using payload. payload contains the data that’s being sent to the server. Finally, the cache is updated with the newly filtered data.

That’s it! We have now successfully built a functional web app that has GraphQL as its server and React as its frontend.

How to build a React note-taking app with GraphQL

Conclusion

In this part of the tutorial, you learnt how to build a React app that works with a GraphQL API server. You first started by building a Node.js GraphQL API in the previous part of the tutorial and then you built a frontend app using React.

You also saw how to use Apollo Client to build React apps that interface with a GraphQL server. The React app built in this article allows you to communicate with the GraphQL thanks to Apollo Client.

You can go ahead to extend the application by having some additional features such as pagination, caching, realtime subscriptions e.t.c.

One other thing that was explored was GraphQL’s unique features and some of its advantages over the traditional REST methodology.

If you want to dive deeper and learn more about building web applications with React and GraphQL, you can use any of the resources below:

The code for the GraphQL API can be seen here on GitHub and that of the React app can be seen here.

#reactjs #graphql #web-development #database

How to build a React note-taking App with GraphQL
21.75 GEEK