Building a GraphQL API with Node JS and React

Vladimir Kopychev
Level Up Coding
Published in
11 min readNov 21, 2017

--

A hands-on tutorial about building GraphQL API on Express JS with React JS on the client side.

Introduction

This year I visited PyCon Ireland 2017, and while there I saw a very interesting presentation about the GraphQL API. GraphQL is a new query language for building APIs introduced by Facebook. It is more convenient than REST API in some cases and is supposed to replace REST in some areas. The main advantage of the GraphQL approach is that you need only one endpoint driven by a POST method instead of several endpoints and HTTP methods in REST. For example, say you have list of users, each containing a first name, last name, email and other information like country, work department, etc. And say you need to get only the main information for a user (id, first name, last name) in one case, and full information in another case. What would you do with REST? Create two endpoints?

Or say you also have a list of ‘todo’ tasks assigned to each user and you need to retrieve the user with a list of todos. With GraphQL you can directly tell the server “give me the list of users containing only ids, first names, and last names,” which looks like:

{
users {
id
first_name
last_name
}
}

Or “give me full information”:

{
users {
id
first_name
last_name
email
gender
country
department
}
}

Or “give me a list of users with todos assigned.” Or we can even define count and summary fields — which will be described later, along with a lot of other operations:

{
users {
id
first_name
last_name
todo_count
todos {
id
title
}
}
}

Also you’re able to define parameters to implement any kind of search or filtering of your data. Just like this:

{
users(country:"Ireland") {
id
first_name
last_name
}
}

At this point I started thinking about working with GraphQL on Server-side and React JS on Client side. React props and state concept works well with any kind of JSON API and therefore must work well with GraphQL. React also has the controlled forms concept that allows mapping form inputs to React state without any additional serialization. And it’s good practice to use different features of React JS while build a single-page application (SPA) with a new API query language. In this tutorial we will use:

This example has a list of Users situated in different countries and working in different departments of some organization. Each of them has his Todo list with completed and not-completed tasks. We will create a SPA with two main parts:

  • A search form to search through and display the list of all users, filtered by first name, last name, department, country.
  • A Todo List of tasks related to a selected userId accessible by ‘View Todos (x)’ link from the Users list

You can find working code here https://github.com/KilroggD/GraphQL-react. To launch it just do npm install in both client and server folders and then you can use npm start to launch either server or client or both.

As a data source we will use js files with arrays but potentially you can use some database engine, or other data source.

In this tutorial I will describe the creation of this SAP step-by-step.

Lets’s start with server-side application.

Server-side implementation

Server-side code is located here https://github.com/KilroggD/GraphQL-react/tree/master/graphsrv

Before we start creating our server-side application we need several npm packages installed — you can find package.json in the repository:

"dependencies": {
"express": "^4.16.2",
"express-graphql": "^0.6.11",
"graphql": "^0.11.7",
"lodash": "^4.17.4",
"babel-cli": "^6.24.1",
"babel-preset-node6": "^11.0.0",
"babel-register": "^6.24.1",
"nodemon": "^1.11.0"
}

Here we can see express framework, graphql extension, the lodash library — extremely useful for filtering and searching in JS arrays — and some additional package to run our server and make it understand fancy ES6 syntax.

Our entry point is our server.js file. First we import all necessary modules and define a port our server listens to, then create our express app. We also import the GraphQL schema file that will be described later.

import express from 'express';
import schema from './schema';
import graphqlHTTP from 'express-graphql';
const port = 3001;
const app = express();

After that we need to describe what our app is going to do. At first we assume that our API might be public, or that our front-end dev server could be run on a different port, so we need to allow cross-origin requests.

app.use('/graphql', (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With');
if (req.method === 'OPTIONS') {
res.sendStatus(200);
} else {
next();
}
});

Then we write four magic lines of code which do the GraphQL work:

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

Graphiql is a powerful development tool which allows you to dev-test your API without having any frontend or client applications. In other words, this bit of code is the only endpoint we need. It will process all POST requests sent to http://localhost:3001/graphql via our GraphQL schema.

And, finally, the code that launches the server itself:

const server = app.listen(port, () => {
console.log(
`\n\nExpress listen at http://localhost:${port} \n`
);
});

Now let’s define the main part of our server-side application — the Schema. It defines all rules about how our requests will be validated, how the results will be taken from the data source, which relations different entities have between each other, etc. So it basically works like a data model for our application.

First, we will import our data source files and useful lodash methods for filtering and aggregating data. Do not import the whole lodash library just for the couple of function you’re going to use (NO import _ from lodash!)

import Users from './data/users';
import Todos from './data/todos';
import find from 'lodash/find';
import filter from 'lodash/filter';
import sumBy from 'lodash/sumBy';

After that we import some GraphQL classes for type validation and creating Schema objects

import {
GraphQLInt,
GraphQLBoolean,
GraphQLString,
GraphQLList,
GraphQLObjectType,
GraphQLNonNull,
GraphQLSchema,
} from 'graphql';

Then we can define the actual data structures for entities we need. In this example we have two entities — one for users and one for todos:

const UserType = new GraphQLObjectType({
name: 'User',
description: 'Users in company',
fields: () => ({
id: {type: new GraphQLNonNull(GraphQLInt)},
first_name: {type: new GraphQLNonNull(GraphQLString)},
last_name: {type: new GraphQLNonNull(GraphQLString)},
email: {type: GraphQLString},
gender: {type: GraphQLString},
department: {type: new GraphQLNonNull(GraphQLString)},
country: {type: new GraphQLNonNull(GraphQLString)},
todo_count: {
type: GraphQLInt,
resolve: (user) => {
return sumBy(
Todos, todo => todo.userId === user.id ? 1:0
);
}
},
todos: {
type: new GraphQLList(TodoType),
resolve: (user, args) => {
return filter(
Todos, todo => todo.userId === user.id
);
}
}
})
});

Here, for all fields except todo_count and todos we have only types defined (Int, String etc.). For those two types related to another entity (todos) we define a resolve function which will, in the first case aggregate the number of todos, and in the second case just fetch all todos related to the user. Then we define similar functions for todos with relation to the user the task is assigned to.

const TodoType = new GraphQLObjectType({
name: 'Todo',
description: 'Task for user',
fields: () => ({
id: {type: new GraphQLNonNull(GraphQLInt)},
title: {type: GraphQLString},
completed: {type: new GraphQLNonNull(GraphQLBoolean)},
user: {
type: UserType,
resolve: (todo, args) => {
return find(Users, user => user.id === todo.userId);
}
}
})
});

After all data types are defined we can create a root query for our application. There are different types of queries in GraphQL, but for this example we will just focus on a very simple one which fetches data from data source. For users we will also implement filtering parameters, and for todos we will just fetch all items related to the userId given. Here’s the code for our main query:

const TodoQueryRootType = new GraphQLObjectType({
name: 'TodoAppSchema',
description: 'Root Todo App Schema',
fields: () => ({
users: {
args: {
first_name: {type: GraphQLString},
last_name: {type: GraphQLString},
department: {type: GraphQLString},
country: {type: GraphQLString},
},
type: new GraphQLList(UserType),
description: 'List of Users',
resolve: (parent, args) => {
if (Object.keys(args).length) {
return filter(Users, args);
}
return Users;
}
},
todos: {
args: {
userId: {type: GraphQLInt},
completed: {type: GraphQLBoolean},
},
type: new GraphQLList(TodoType),
description: 'List of Todos',
resolve: (parent, args) => {
if (Object.keys(args).length) {
return filter(Todos, args);
}
return Todos;
}
}
})
});

Here our main application Schema is described. It has two fields — users and todos with UserType and TodoType respectively. We can filter Users list by four possible parameters defined in args. The filter itself is implemented in a resolve function (in this case just a simple lodash filter).

The same goes for the Todos list: we can potentially filter it by userId and by completed state.

Finally, we create and export our schema object:

const schema = new GraphQLSchema({
query: TodoQueryRootType,
});
export default schema;

At this point you can already launch your sever and test it via the debug tool.

Just open http://localhost:3001/graphql in your Chrome browser and try some queries mentioned earlier in this story.

At this point the server-side is finished and we can start with the client side part.

Client side implementation

To simplify the creation of React JS application I used the create-react-app tool provided by Facebook. I also installed react router:

npm install --save react-router

Working code and package.json file are in the repository in the client folder.

You can launch the server first, then go to the client folder and run:

npm start

I will not describe all source code here, only the most relevant parts of it.

There are different approaches in React JS to working with asynchronous data fetching, ajax requests, and different APIs. This article describes ajax best practices very well.

In order to not over-complicate the example, we will go with the container approach when we have container components which deal with asynchronous calls, and presentational components which are stateless and display the UI. Before we start we’ll create a simple service-like class which will fetch the GraphQL data in order to avoid AJAX calls in our components.

ApiService.js:

class ApiService {   /**
* define base url and field schemas here
* @returns {ApiService}
*/
constructor() {
this.apiUrl = 'http://localhost:3001/graphql';
this.userFields = `{id, first_name, last_name, email, department, country, todo_count}`;
this.todoFields = `{id title completed user {first_name, last_name}}`;
}
/**
* Generic function to fetch data from server
* @param {string} query
* @returns {unresolved}
*/
async getGraphQlData(resource, params, fields) {
const query = `{${resource} ${this.paramsToString(params)} ${fields}}`;
const res = await fetch(this.apiUrl, {
method: 'POST',
mode: 'cors',
headers: new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json',
}),
body: JSON.stringify({query}),
});
if (res.ok) {
const body = await res.json();
return body.data;
} else {
throw new Error(res.status);
}
}
/**
*
* @param {object} params
* @returns {array} users list or empty list
*/
async getUsers(params = {}) {
const data = await this.getGraphQlData(
'users', params, this.userFields
);
//return users list
return data.users;
}
/**
*
* @param {object} params
* @returns {array} users list or empty list
*/
async getTodos(params = {}) {
const data = await this.getGraphQlData(
'todos', params, this.todoFields
);
//return todos list
return data.todos;
}
/**
*
* @param {object} params
* @returns {String} params
converted to string for usage in graphQL
*/
paramsToString(params) {
let paramString = '';
if (params.constructor === Object && Object.keys(params).length) {
let tmp = [];
for (let key in params) {
let paramStr = params[key];
if(paramStr !== '') {
if (typeof params[key] === 'string') {
paramStr = `"${paramStr}"`;
}
tmp.push(`${key}:${paramStr}`);
}
}
if (tmp.length) {
paramString = `(${tmp.join()})`;
}
}
return paramString;
}
}export default new ApiService();

In the constructor we define our main Url and fields we need to fetch for each entity. The main function async getGraphQlData(resource, params, fields) fetches the data from a given resource (users or todos), filtered by given params, and restricted by the given fields provided. paramsToString is a helper function used to transform objects into parameter strings ({userId: 1} -> ‘(userId:1)’).

Also, we use getUsers and getTodos as proxy functions for better readability, accurateness (getUsers to fetch users, getTodos to fetch todos), and syntactical sugar. Both functions will return an array of objects retrieved from the server or throw an Error if the request wasn’t successful. The javascript fetch API is used here.

Let’s declare our main App as a main container which deals with routing:

const App = () => {
return <Switch>
<Route exact path='/' component={UserListContainer}/>
<Route path='/todos/:userId' component={TodoListContainer}/>
</Switch>
};
export default App;

You can find a wonderful article about react router here on Medium.

Dependent on browser Url, our app will render either UserListContainer or TodoListContainer. They are both asynchronous components which deal with AJAX requests and pass processed data to their children components. Let’s focus on the UserListContainer. By default it renders two children — UserForms for searching users and UserLists with a results list.

render() {
return <div className="user">
<UserForm submitHandler={this.search} />
<UserList users={this.state.users} />
</div>;
}

It also passes a search function as a handler to our form. This async function fetches the data from the GraphQL api and stores the result in the state:

async search(params) {
try {
const users = await ApiService.getUsers(params);
this.setState({users});
} catch (e) {
console.error(`An error ${e.message} occured while searching users`);
}
}

UserForm is a controlled form which maps input values to the Form’s state. So all we need to do is to pass this state to the handler function:

handleSubmit(event) {
event.preventDefault();
return this.props.submitHandler(this.state);
}

After we submit the form our search function fetches the data and passes it into UserList, which displays the results:

We can see the request payload and response received in Chrome dev tools:

Each UserList item contains a link with a Todos count which links to the page with tasks related to that user http://localhost:3000/todos/1

This page renders TodoListContainer which uses async rendering straightaway without waiting for any form to be submitted. All components except containers and UserForm are stateless functional components used for presentational purposes.

async componentDidMount() {
const userId = parseInt(this.props.match.params.userId, 10);
try {
const todos = await ApiService.getTodos({userId});
this.setState({todos});
} catch (e) {
console.error(`An error ${e.message} occured while loading tasks for user ${userId}`);
}
}

The page also contains links which brings your back to the user search page. Note that after we go back the search form is clean because UserListContainer was unmounted after we changed both the route and its children. To keep the form state we could implement some Redux or Reflux or custom store approach, or store the state in the root App component (viable for small apps but can create a mess in bigger projects). I would suggest using stores, but that is a different story for another time. As are GraphQL mutations and the many other features and benefits of this approach for building your API’s.

Thanks for reading, I hope you found it a bit helpful and useful. =)

Check out gitconnected >

The community for developers and software engineers

--

--

Full Stack Engineer with passion for Frontend and UI solutions, PhD, coding at @udemy