Build a Note-Taking App with GraphQL and React — Part 1

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.

Tip: Reuse React components

Use Bit to share and reuse React components across different projects. Collaborate over shared components as a team to build apps faster together. Let Bit do the heavy lifting so you can easily publish, install and update your individual components without any overhead. Click here to learn more.

This is image title

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 Date
type 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 Date
type 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.

This is image title

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 Date
type 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
  }
}

This is image title

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 Date
type 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

This is image title

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 Date
type 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

This is image title

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 Date
type 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

This is image title

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.

The code for the GraphQL API can be seen here on GitHub.

Part 2 will be released tomorrow!

#reactjs #react #GraphQL

Build a Note-Taking App with GraphQL and React — Part 1
1 Likes12.10 GEEK