Building a simple CRUD with Node, GraphQL, and React

GraphQL reduces the complexity of building APIs by abstracting all requests to a single endpoint. Unlike traditional REST APIs, it is declarative; whatever is requested is returned faster.

Of course, not all projects require GraphQL — it is merely a tool to consolidate data. It has well-defined schema, so we know for sure we won’t overfetch. But if we already have a stable RESTful API system where we rely on data from a single data source, we don’t need GraphQL.

For instance, let’s assume we are creating a simple blog and we decide to store, retrieve, and communicate to data in a single MySQL database. In this case, we are not doing anything architecturally complex, hence unless we get really popular to serve great amounts of traffic we may not need GraphQL.

On the other hand, let’s imagine we have a full-fledged product that relies on data from multiple sources (e.g., MongoDB, MySQL, Postgres, and other APIs). Here using GraphQl makes more sense, as it will bring data from all these places to a single place, which in turn will make it much simpler and faster to query data.

In an another instance, let’s say, if we’re designing a portfolio site and we want data from social media and GitHub (to show contributions), and we also have our own database to maintain a blog, we can use GraphQL to write the business logic and schema. It will consolidate data as a single source of truth.

Once we have the resolver functions to dispatch the right data to the front end, it will be easy to manage data within a single source. In this article, we’re going to implement simple end-to-end CRUD operations with GraphQL.

CRUD with graphql-server

Setting up our server

We are going to spin off a simple GraphQL server using express-graphql and get it connected to a MySQL database. The source code and the MySQL files are in this repository.

A GraphQL server is built on top of schema and resolvers. In the first step, we will build a schema (defining types, queries, mutations, and subscriptions). This schema describes the whole app structure.

Secondly, for the stuff defined in the schema, we’re building respective resolvers to compute and dispatch data. A resolver maps actions with functions; for each query declared in typedef, we create a resolver to return data.

Finally, we complete server settings by defining an endpoint and passing configurations. We initialize /graphql as the endpoint for our app. To the graphqlHTTP middleware, we pass the built schema and root resolver.

Along with the schema and root resolver, we’re enabling the GraphiQL playground. GraphiQL is an interactive in-browser GraphQL IDE that helps us play around with the GraphQL queries we build.

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

var schema = buildSchema(`
  type Query {
    hello: String
  }
`);

var root = {
  hello: () => "World"
};

var app = express();

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));

app.listen(4000);

console.log('Running a GraphQL API server at localhost:4000/graphql');

Once the server is ready, we are good to go, running the app with node index.js will start the server on http://localhost:4000/graphql. We can query for hello and get the string “World” as a response.

Connecting the database

We are going to establish the connection with the MySQL database as shown below:

var mysql = require('mysql');

app.use((req, res, next) => {
  req.mysqlDb = mysql.createConnection({
    host     : 'localhost',
    user     : 'root',
    password : '',
    database : 'userapp'
  });
  req.mysqlDb.connect();
  next();
});

We can connect multiple databases/sources and get them consolidated in the resolvers. I’m connecting to a single MySQL database here. The database dump I’ve used for this article is in the GitHub repository, with name userapp.sql

Reading and writing data with GraphQL

We use queries and mutations to read and modify data in data-sources. In this example, I’ve defined a generic queryDB function to help query the database.

Queries

All the SELECT statements (or read operations) to list and view data goes into the type Query typedef. We have two queries defined here: one to list all the users in the database, and another to view a single user by id.

  1. Listing data: To list users, we’re defining a GraphQL schema object type called User, which represents what we can fetch or expect from the getUsers query. We then define the getUsers query to return an array of users.
  2. Viewing a single record: To view a single record, we’re taking id as an argument with the getUserInfo query we have defined. It queries for that particular id in the database and returns the data to the front end.

GraphiQL Demo Returning Array Of Users

Now that we have put together the queries to fetch all records and to view record by id, when we try to query for users from GraphiQL, it will list an array of users on the screen! 

var schema = buildSchema(`
  type User {
    id: String
    name: String
    job_title: String
    email: String
  }
  type Query {
    getUsers: [User],
    getUserInfo(id: Int) : User
  }
`);

const queryDB = (req, sql, args) => new Promise((resolve, reject) => {
    req.mysqlDb.query(sql, args, (err, rows) => {
        if (err)
            return reject(err);
        rows.changedRows || rows.affectedRows || rows.insertId ? resolve(true) : resolve(rows);
    });
});

var root = {
  getUsers: (args, req) => queryDB(req, "select * from users").then(data => data),
  getUserInfo: (args, req) => queryDB(req, "select * from users where id = ?", [args.id]).then(data => data[0])
};

Mutations

The write operations for the database — CREATE, UPDATE, DELETE — are generally defined under mutations. The mutations are executed in a sequential manner by the GraphQL engine. Queries are executed parallelly.

  1. Creating data: We have defined a mutation, createUser, that takes the specified arguments to create data in the MySQL database.
  2. Updating or deleting data: Similar to viewing a record, update (updateUserInfo) and delete (deleteUser) take id as a param and modify the database.

The functions resolve with a boolean to indicate whether the change happened or not.

var schema = buildSchema(`
  type Mutation {
    updateUserInfo(id: Int, name: String, email: String, job_title: String): Boolean
    createUser(name: String, email: String, job_title: String): Boolean
    deleteUser(id: Int): Boolean
  }
`);

var root = {
  updateUserInfo: (args, req) => queryDB(req, "update users SET ? where id = ?", [args, args.id]).then(data => data),
  createUser: (args, req) => queryDB(req, "insert into users SET ?", args).then(data => data),
  deleteUser: (args, req) => queryDB(req, "delete from users where id = ?", [args.id]).then(data => data)
};

Now that we have set and sorted the server side of things, let’s try and connect the back end to our React app.

CRUD with graphql-client

Once we have the server in place, creating client logic to display and mutate data is easy. Apollo Client helps in state management and caching. It is also highly abstracted and quick: all of the logic for retrieving your data, tracking loading and error states, and updating UI is encapsulated by the useQuery Hook.

Connecting to graphql-server

I have created a CRA boilerplate and have installed GraphQLapollo-boost, and @apollo/react-hooks. We initialize Apollo Client and get it hooked to React.

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from '@apollo/react-hooks';

const client = new ApolloClient({
  uri: 'http://localhost:4000/graphql'
});

ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
);

Reading and mutating data

I have managed all the GraphQL queries in the Queries folder of my source code. I’m going to request data from the server with the useQuery Hook, which is built on top of the React Hooks API. It helps in bringing in data into the UI.

GraphQL queries are generally wrapped in the gql function. gql helps convert query string into a query document. Here’s how we define queries in our app.

import { gql } from 'apollo-boost';

export const GET_USERS = gql`
  {
    getUsers {
      id,
      name,
      job_title,
      email
    }
  }
`;

export const VIEW_USERS = gql`
  query ($id: Int){
    getUserInfo(id: $id) {
      id,
      name,
      job_title,
      email
    }
  }
`;

export const ADD_USER = gql`
  mutation($name: String, $email: String, $job_title: String) {
    createUser (name: $name, email: $email, job_title: $job_title)
  }
`;

export const EDIT_USER = gql`
  mutation($id: Int, $name: String, $email: String, $job_title: String) {
    updateUserInfo (id: $id, name: $name, email: $email, job_title: $job_title)
  }
`;

export const DELETE_USER = gql`
  mutation($id: Int) {
    deleteUser(id: $id)
  }
`

Once ApolloProvider is set, 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.

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.

import React from 'react';
import { useQuery } from '@apollo/react-hooks';
import { GET_USERS, VIEW_USERS } from "./Queries";
import { Card, CardBody, CardHeader, CardSubtitle, Spinner } from 'reactstrap';

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>
  )
}

export default App;

Similar to querying, mutations will use the same useQuery Hook and will pass data as variables into the query.

const deleteMutation = useQuery(DELETE_USER, { variables: { id: 8 }});
const editMutation = useQuery(EDIT_USER, { variables: { id: 9, name: "Username", email: "email", job_title: "job" }});
const createMutation = useQuery(ADD_USER, { variables: { name: "Username", email: "email", job_title: "job" }});

Conclusion

Ta-da! We just did end-to-end CRUD operations with GraphQL. On the client side, reading and mutating data has become very simple after the introduction of React Hooks. Apollo Client also provides provisions for authentication, better error handling, caching, and optimistic UI.

Happy Programming!

Build a GraphQL API with Node

In this article, we’ll learn to build a GraphQL API server with Node.js and Express.

You may use the code discussed in this example here in the GitHub repository.

Before getting started with GraphQL, let’s first understand what GraphQL is and the advantages it offers over the REST method of building APIs.

What is GraphQL

GraphQL is serves as a runtime as well as a query language for building web APIs. It allows developers to provide a detailed description of data used in the application which allows clients to query for exactly the data they need nothing more, nothing less.

Advantages of GraphQL

Companies like Facebook, Shopify, IBM, GitHub and Twitter migrated from REST to GraphQL APIs consider the advantages. Let’s see some of them:

  • GraphQL enables developers to use a single request for fetching data that may require consuming multiple REST endpoints. This means writing less code which increases productivity.
  • GraphQL provides better performance as it is less network overhead since you can describe the data you want in one query and send/receive fewer network requests and responses.
  • Support for nested data which makes building complex queries easier than REST.

Prerequisites

In order to follow this article from scratch, you are expected to have:

  • Working knowledge of JavaScript and familiarity with Node.
  • Node and NPM installed on your development machine. You can head to the official website to download the binaries required for your system or use NVM to install Node.js.

If you have these prerequisites, you are good to go!

Setting up the Project

Let’s now set up our demo project. Open a new terminal and execute the following commands to create a package.json file with default values:

mkdir node-graphql-demo
cd node-graphql-demo
npm init -y

Here we are just creating a directory for the project files, and initiating npm. In the next step, we need to install the following dependencies:

npm install  graphql express express-graphql sqlite3 --save

This will install the Express framework, the GraphQL implementation for Node.js, the GraphQL middleware for Express and SQLite3.

To keep things simple, we’ll be using SQLite3 for the database.

Create the GraphQL Server

Now that we have a project set up with the required dependencies, let’s create the API server. In your project’s directory, create an index.js file and add the following imports:

const express = require('express');
const sqlite3 = require('sqlite3').verbose();
const graphql = require("graphql");
const ExpressGraphQL = require("express-graphql");

We’re importing Express, SQLite3, GraphQL and GraphQL middleware for Express.

Next, add the following code to create an Express app and a SQLite 3 database called my.db in the current folder:

const app = express();
const database = new sqlite3.Database("./my.db");

Next, add the createContactTable() method which creates a contacts table in the database and call the function immediately:

const createContactTable = () => {
    const query = `
        CREATE TABLE IF NOT EXISTS contacts (
        id integer PRIMARY KEY,
        firstName text,
        lastName text,
        email text UNIQUE)`;
    return database.run(query);
}
createContactTable();

We created a SQL table to store contacts. Each contact has a unique identifier, first name, last name, and email.

Next, add the following code to define a GraphQL type:

const ContactType = new graphql.GraphQLObjectType({
    name: "Contact",
    fields: {
        id: { type: graphql.GraphQLID },
        firstName: { type: graphql.GraphQLString },
        lastName: { type: graphql.GraphQLString },
        email: { type: graphql.GraphQLString }   
    }
});

We use the basic built-in GraphQL types such as [GraphQLID](https://graphql.github.io/graphql-spec/draft/#sec-ID) and [GraphQLString](https://graphql.github.io/graphql-spec/draft/#sec-String) to create our custom type that corresponds to a contact in the database.

Next, define the query type as follows:

var queryType = new graphql.GraphQLObjectType({
    name: 'Query',
    fields: {
        contacts: {
            type: graphql.GraphQLList(ContactType),
            resolve: (root, args, context, info) => {
                return new Promise((resolve, reject) => {

                    database.all("SELECT * FROM contacts;", function (err, rows) {
                        if (err) {
                            reject([]);
                        }
                        resolve(rows);
                    });
                });

            }
        },
        contact: {
            type: ContactType,
            args: {
                id: {
                    type: new graphql.GraphQLNonNull(graphql.GraphQLID)
                }
            },
            resolve: (root, {
                id
            }, context, info) => {
                return new Promise((resolve, reject) => {

                    database.all("SELECT * FROM contacts WHERE id = (?);", [id], function (err, rows) {
                        if (err) {
                            reject(null);
                        }
                        resolve(rows[0]);
                    });
                });
            }
        }
    }
});

Our query has two fields: contacts, that can be used to get all the contacts in the database, and contact, used to get a single contact by id. The contact field accepts a required id argument of type GraphQLID.

Each field can have a type, which specifies the type of the returned data, args for specifying any arguments expected from the client, and resolve, which specifies the actual method that executes the logic for fetching data.

For both fields, the resolve() method is where the actual logic happens — we simply call database.all() or database.run() methods to execute the right SQL query for fetching data from SQLite and we return a Promise that resolves to the fetched data.

We can access any passed arguments from the second parameter of the resolve() method.

Next, let’s create a mutation type that corresponds to the create, update and delete operations:

var mutationType = new graphql.GraphQLObjectType({
    name: 'Mutation',
    fields: {
        createContact: {
            type: ContactType,
            args: {
                firstName: {
                    type: new graphql.GraphQLNonNull(graphql.GraphQLString)
                },
                lastName: {
                    type: new graphql.GraphQLNonNull(graphql.GraphQLString)
                },
                email: {
                    type: new graphql.GraphQLNonNull(graphql.GraphQLString)
                }
            },
            resolve: (root, {
                firstName,
                lastName,
                email
            }) => {
                return new Promise((resolve, reject) => {
                    database.run('INSERT INTO contacts (firstName, lastName, email) VALUES (?,?,?);', [firstName, lastName, email], (err) => {
                        if (err) {
                            reject(null);
                        }
                        database.get("SELECT last_insert_rowid() as id", (err, row) => {

                            resolve({
                                id: row["id"],
                                firstName: firstName,
                                lastName: lastName,
                                email: email
                            });
                        });
                    });
                })

            }
        },
        updateContact: {
            type: graphql.GraphQLString,
            args: {
                id: {
                    type: new graphql.GraphQLNonNull(graphql.GraphQLID)
                },
                firstName: {
                    type: new graphql.GraphQLNonNull(graphql.GraphQLString)
                },
                lastName: {
                    type: new graphql.GraphQLNonNull(graphql.GraphQLString)
                },
                email: {
                    type: new graphql.GraphQLNonNull(graphql.GraphQLString)
                }
            },
            resolve: (root, {
                id,
                firstName,
                lastName,
                email
            }) => {
                return new Promise((resolve, reject) => {
                    database.run('UPDATE contacts SET firstName = (?), lastName = (?), email = (?) WHERE id = (?);', [firstName, lastName, email, id], (err) => {
                        if (err) {
                            reject(err);
                        }
                        resolve(`Contact #${id} updated`);

                    });
                })
            }
        },
        deleteContact: {
            type: graphql.GraphQLString,
            args: {
                id: {
                    type: new graphql.GraphQLNonNull(graphql.GraphQLID)
                }
            },
            resolve: (root, {
                id
            }) => {
                return new Promise((resolve, reject) => {
                    database.run('DELETE from contacts WHERE id =(?);', [id], (err) => {
                        if (err) {
                            reject(err);
                        }
                        resolve(`Contact #${id} deleted`);

                    });
                })

            }
        }
    }
});

Our mutation type has three fields:

  • createContact for creating contacts;
  • updateContact for updating contacts;
  • deleteContact for deleting contacts.

All fields accept arguments which are specified in the args property and have a resolve() method that takes the passed arguments, executes the corresponding SQL operation, and then returns a Promise.

Next, create a GraphQL schema as follows:

const schema = new graphql.GraphQLSchema({
    query: queryType,
    mutation: mutationType
});

A GraphQL schema is a core concept of GraphQL and it describes the functionality available to the clients which connect to the server. We pass the query and mutation types we defined earlier to the schema.

Finally, mount the /graphql endpoint and run the Express server on the 4000 port:

app.use("/graphql", ExpressGraphQL({ schema: schema, graphiql: true}));
app.listen(4000, () => {
    console.log("GraphQL server running at http://localhost:4000.");
});

Save your index.js file and head back to your terminal. Then, run the following command to start the server:

node index.js

How to Consume the GraphQL API

Before building a client, you can use the GraphQL interface to test your API.

Go to the http://localhost:4000/graphql address and run the following mutation query:

mutation {
    createContact(firstName: "Jon", lastName: "Snow", email: "jonsnow@thenightswatch.com") {
        id,
        firstName,
        lastName,
        email
    }
}

You can update a contact with id 1 using the following mutation:

mutation {
    updateContact(id: 1, firstName: "Aegon", lastName: "Targaryen", email: "aegontargaryen@ironthrone.com")
}

And also delete this contact with id 1 using the following mutation:

mutation {
    deleteContact(id: 1)
}

Finally, you can get all contacts in the database using the following query:

query {
    contacts {
        id
        firstName
        lastName
        email
    }
}

And you can get a single contact using this query:

query {
    contact(id: 1) {
        id
        firstName
        lastName
        email
    }
}

Here, we get the contact with the id: 1.

Conclusion

In this tutorial, we’ve used Node and Express.js to create a simple GraphQL API for reading, creating, updating and deleting contacts from an SQLite database.

We’ve seen that GraphQL is both a runtime and a query language for building web APIs that has many advantages over the REST approach.

We’ve also seen how to work with basic built-in types in GraphQL and used them to create our own query and mutation types for both reading and mutating data.

Finally, we’ve seen how to use the GraphQL interface to execute queries and mutations against our API.