Node - GraphQL

Simple implementation of a REST/GraphQL/MongoDB API

Frequently, in meetings or in IT blogs, I hear people who talk about with GraphQL, a different way of REST to create WS API. In this post, we’ll see how to develop very simple GraphQL endpoints and their equivalents in REST.
Let’s play with code.

Source code of this article

You can find all the source code in my GitLab repository.

Prerequisites

In addition to

we will also use

Node.js installation

For Windows 10.
Download the msi file for Windows of the current LTS version (v8.11.1).
Run the setup with the add to PATH option (node, npm and PATH).
Then you can test your setup in your command prompt:

C:\>node --version
v8.11.1

C:\>npm --version
5.6.0


   ╭─────────────────────────────────────╮
   │                                     │
   │   Update available 5.6.0 → 5.8.0    │
   │       Run npm i npm to update       │
   │                                     │
   ╰─────────────────────────────────────╯


C:\>

You can update npm as mentionned:

C:\>npm i npm

Corporate proxy setup

If needed, in other words if you have a corporate proxy to access internet, you have to setup npm to force it to pass through this one:

C:\>npm config set proxy http://user:pwd@proxy-url:proxy-port/
C:\>npm config set https-proxy http://user:pwd@proxy-url:proxy-port/

MongoDB installation

For Windows 10.
Download the current version (v3.6.3).
Run the setup with the complete option.
With regedit, add C:\Program Files\MongoDB\Server\3.6\bin to Path (HKEY_CURRENT_USER).
Restart the session.
In command prompt, we can test like this:

C:\>mongod --version
db version v3.6.3
MongoDB reminders

MongoDB manages documents (data, lines by analogy to relational databases) in collections (tables by analogy to relational databases).
There is no relationship (keys by analogy to relational databases) between documents (NoSQL).
The two main installed binaries are:

  • mongod: database server
  • mongo: mongodb shell

By default, mongod starts on port 27017.
Before we can start the mongod server, we have to create the data directoy C:\data\db used by it. We can also use the --dbpath parameter to specifiy another directory.

Project creation

Node.js / npm initialisation

We create the directory C:\node-rest-graphql-hello for our new project an initialize a node.js project in it (package.json file creation):

C:\node-rest-graphql-hello>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (rest-graphql-hello) node-rgh
version: (1.0.0)
description: Demonstration of a node rest and graphql api
entry point: (index.js) server.js
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to C:\node-rest-graphql-hello>\package.json:

{
  "name": "node-rgh",
  "version": "1.0.0",
  "description": "Simple REST and GraphQL API engined by a Node.js/MongoDB backend",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this ok? (yes)

Git initialisation

Create the remot Git repository (on GitLab for instance) : node-rest-graphql-hello.

Intialisation

With the Git BASH or the command prompt

C:\node-rest-graphql-hello>git init
User

With the Git BASH and for a local configuration

$ git config --local user.name "your name"
$ git config --local user.email "your.email@yourwebmail.com"
Remote repository
$ git remote add origin git@gitlab.com:your-gitlab-user/your-gitlab-project.git
Commit and push
  • local commit

    $ git add .
    $ git commit -m "init commit"
    
  • remote push

    $ git push -u origin master
    

We can now modify our local code and push it on our remote repository.

Mandatory npm modules import

In our project directory C:\node-rest-graphql-hello.

Express (HTTP and routing server)

Command prompt

C:\node-rest-graphql-hello>npm install express --save

Mongoose (MongoDB access)

Command prompt

C:\node-rest-graphql-hello>npm install mongoose --save

GraphQL

Command prompt

C:\node-rest-graphql-hello>npm install express-graphql graphql --save

package.json file’s content

At this step, we must have a package.json file which looks like the following one

{
  "name": "node-rgh",
  "version": "1.0.0",
  "description": "Simple REST and GraphQL API engined by a Node.js/MongoDB backend",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.3",
    "express-graphql": "^0.6.12",
    "graphql": "^0.13.2",
    "mongoose": "^5.0.13",
    "npm": "^5.8.0"
  }
}

Implementation

In our project directory C:\node-rest-graphql-hello.

HTTP server start-up

Create

We create the following server.js file

'use strict';

/* npm module express */
const express = require('express');
const app = express();

/* let's start ! */
var proxyPort = 3000;
app.listen(proxyPort, function () {
  console.log('API REST and GrapQL server is listening on port:', proxyPort);
});

Test

Command prompt

C:\node-rest-graphql-hello>npm start

> node-rgh@1.0.0 start C:\node-rest-graphql-hello
> node server.js

API REST and GrapQL server is listening on port: 3000

First REST API method

Modify

We modify the server.js file like this

'use strict';

/* npm module express */
const express = require('express');
const app = express();

/* our first REST API method */
app.get('/', function(req, res) {
    res.status(200).json({"message" : "this is our first REST API method, server is running"});
    return;
  });  

/* let's start ! */
var proxyPort = 3000;
app.listen(proxyPort, function () {
  console.log('API REST and GrapQL server is listening on port:', proxyPort);
});

Test

Then the browser displays the following message

{"message":"this is our first REST API method, server is running"}

Connection to the mongodb server

Modify

We modify the server.js file like this

'use strict';

/* settings */
const proxyPort = 3000;
const mongooseURL = 'mongodb://localhost:27017/nrghDB';

/* npm modules */
const express = require('express');
const mongoose = require('mongoose');

const app = express();

/* our first REST API method */
app.get('/', function (req, res) {
  res.status(200).json({ 'message': 'this is our first REST API method, server is running' });
  return;
});

/* let's start ! */
// == 1 ==
mongoose.connect(mongooseURL, function (error) {
  if (error) {
    console.log('FAILED : Unable to connect to MongoDB [%s]', mongooseURL);
    console.log('ABORTED : API REST and GrapQL server not started');
    process.exit(0);
  }
  else {
    console.log('SUCCEED : Connected to MongoDB [%s]', mongooseURL);

    // == 2 == 
    app.listen(proxyPort, function () {
      console.log('SUCCEED : API REST and GrapQL server started on port [%d]', proxyPort);
    });
  }
});

Mongo server restart

Command prompt

C:\node-rest-graphql-hello>mongod
Test

Command prompt

C:\node-rest-graphql-hello>npm start

> node-rgh@1.0.0 start C:\node-rest-graphql-hello
> node server.js

SUCCEED : Connected to MongoDB [mongodb://localhost:27017/nrghDB]
SUCCEED : API REST and GrapQL server started on port [3000]

First GraphQL request

In the following paragraphs we will define our endpoints with GraphQL Schema Language.
Of course, you can also define them with code.

Modify

For clarity, we add two express routers, one for REST anf one another for GraphQL. We create the GraphQL schema with a simple hello query and its resolver (javascript function with the same name in root).
We modify the server.js file like this

'use strict';

/* settings */
const proxyPort = 3000;
const mongooseURL = 'mongodb://localhost:27017/nrghDB';

/* npm modules */
const express = require('express');
const mongoose = require('mongoose');
const graphqlHTTP = require('express-graphql');
const graphql = require('graphql');

const app = express();

/* we add two api routers */
var restApiRouter = express.Router();
var graphqlApiRouter = express.Router();
app.use('/api/rest', restApiRouter);
app.use('/api/graphql', graphqlApiRouter);

/* our first REST API method */
restApiRouter.get('/', function (req, res) {
  res.status(200).json({ 'hello': 'this is our first REST API method, server is running' });
  return;
});

/* our first GraphQL API query */
// == 1 == create GraphQL schema
let schema = graphql.buildSchema(`
type Query {
  hello: String
  }
  `);
// == 2 == create our "hello" resolver
var root = {
  hello: () => { return 'this is our first GraphQL API method, server is running'; }
};

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

/* let's start ! */
// == 1 ==
mongoose.connect(mongooseURL, function (error) {
  if (error) {
    console.log('FAILED : Unable to connect to MongoDB [%s]', mongooseURL);
    console.log('ABORTED : API REST and GraphQL server not started');
    process.exit(0);
  }
  else {
    console.log('SUCCEED : Connected to MongoDB [%s]', mongooseURL);

    // == 2 == 
    app.listen(proxyPort, function () {
      console.log('SUCCEED : API REST and GrapQL server started on port [%d]', proxyPort);
    });
  }
});

Restart the server

Command prompt

C:\node-rest-graphql-hello>npm start

> node-rgh@1.0.0 start C:\node-rest-graphql-hello
> node server.js

SUCCEED : Connected to MongoDB [mongodb://localhost:27017/nrghDB]
SUCCEED : API REST and GrapQL server started on port [3000]
Test
  • in our browser, we type http://localhost:3000/api/graphql?query={hello} (also possible with POST with a REST Client) The following message is displayed

    {"data":{"hello":"this is our first GraphQL API method, server is running"}}
    
  • or we can type http://localhost:3000/api/graphql and add the following json request {hello}. Indeed, as we have defined graphiql=true the test application is displayed in the browser like this

READING a mongo’s document with REST (GET) and GraphQL (Query)

Now we will add the possibility to get existing products.
For that, we will create

  • a REST method of GET type
  • a GraphQL method of Query type
Modify the source code

We modify the server.js file like this

'use strict';

/* settings */
const proxyPort = 3000;
const mongooseURL = 'mongodb://localhost:27017/nrghDB';

/* npm modules */
const express = require('express');
const mongoose = require('mongoose');
const graphqlHTTP = require('express-graphql');
const graphql = require('graphql');

const app = express();

/* we add two api routers */
var restApiRouter = express.Router();
var graphqlApiRouter = express.Router();
app.use('/api/rest', restApiRouter);
app.use('/api/graphql', graphqlApiRouter);

/* create a mongoose schema */
var productSchema = mongoose.Schema({
  name: String,
  price: Number,
  desc: String
});

/* create a mongoose model */
var productModel = mongoose.model('Product', productSchema);

/* remove function : returns a promise */
var removeAllProducts = function () {
  console.log('removing all products');
  return productModel.remove(); // promise (see mongoose doc)
};

/* save function : returns a promise */
var saveProduct = function (pProduct) {
  console.log('saving new product');
  let newProduct = new productModel();
  newProduct.name = pProduct.name;
  newProduct.price = pProduct.price;
  newProduct.desc = pProduct.desc;
  return newProduct.save(); // promise (see mongoose doc)
};

/* find function : returns a promise */
var listProducts = function (query) {
  console.log('listing products', query);
  let execQuery = query ? query : {};    
  return productModel.find(execQuery, { '_id': 0, '__v': 0 }).exec(); // promise (see mongoose doc)
};

/* our first REST API method */
restApiRouter.get('/', function (req, res) {
  res.status(200).json({ 'message': 'this is our first REST API method, server is running' });
  return;
});

/* get products list : REST API method */
restApiRouter.get('/products', async function (req, res) {
  let products = [];
  try {
    products = await listProducts();
    res.status(200).json(products);
  } catch (err) {
    res.status(500).json(err); // displays error content : dangerous for security reason : only for demo purpose
  }
  return;
});

/* our GraphQL API */
// == 1 == create GraphQL schema
let schema = graphql.buildSchema(`
type Product {
  name: String!
  price: Int
  desc: String
}

type Query {
  message: String,
  products(name: String): [Product]
  }
  `);
// == 2 == our resolvers
var root = {
  message: () => { return 'this is our first GraphQL API method, server is running'; },  
  products: async ({name}) => {
    return await listProducts(name ? {'name': name} : {});
  }
};

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

/* let's start ! */
// == 1 ==
mongoose.connect(mongooseURL, function (error) {
  if (error) {
    console.log('FAILED : Unable to connect to MongoDB [%s]', mongooseURL);
    console.log('ABORTED : API REST and GraphQL server not started');
    process.exit(0);
  }
  else {
    console.log('SUCCEED : Connected to MongoDB [%s]', mongooseURL);

    // == 2 == 
    app.listen(proxyPort, async function () {
      console.log('SUCCEED : API REST and GrapQL server started on port [%d]', proxyPort);

      // == 3 ==
      try { await removeAllProducts(); }
      catch (err) { console.log(err); }

      try { await saveProduct({ 'name': 'desktop', 'price': 1000, 'desc': 'gamer desktop' }); }
      catch (err) { console.log(err); }
      try { await saveProduct({ 'name': 'laptop', 'price': 1500, 'desc': 'gamer laptop' }); }
      catch (err) { console.log(err); }
    });
  }
});

Restart the server

Command prompt

C:\node-rest-graphql-hello>npm start

> node-rgh@1.0.0 start C:\node-rest-graphql-hello
> node server.js

SUCCEED : Connected to MongoDB [mongodb://localhost:27017/nrghDB]
SUCCEED : API REST and GrapQL server started on port [3000]
removing all products
saving new product
saving new product
Test REST

In our browser we enter the following address http://localhost:3000/api/rest/products
Then the content of the mongo’s collection is displayed (json array of two documents)

[
  {"name":"desktop","price":1000,"desc":"gamer desktop"},
  {"name":"laptop","price":1500,"desc":"gamer laptop"}
]
Test GraphQL

In our browser we enter the following address http://localhost:3000/api/graphql

  • retrieve all documents

  • retrieve only one document

WRITING a mongo’s document with REST (POST) and GraphQL (Mutation)

Now we will add the possibility to create a new product.
For that, we will create

  • a REST method of POST type (as opposed to GET type for reading)
  • a GraphQL method of Mutation type (as opposed to Query type for reading)
Modify the code

We modify the server.js file like this

'use strict';

/* settings */
const proxyPort = 3000;
const mongooseURL = 'mongodb://localhost:27017/nrghDB';

/* npm modules */
const express = require('express');
const mongoose = require('mongoose');
const graphqlHTTP = require('express-graphql');
const graphql = require('graphql');

const app = express();

/* to support JSON-encoded and URL-encoded bodies */
app.use(express.json());
app.use(express.urlencoded({'extended': true}));

/* we add two api routers */
var restApiRouter = express.Router();
var graphqlApiRouter = express.Router();
app.use('/api/rest', restApiRouter);
app.use('/api/graphql', graphqlApiRouter);

/* ============================== */
/* ==== MONGOOSE DEFINITIONS ==== */
/* ============================== */
/* create a mongoose schema */
var productSchema = mongoose.Schema({
  name: String,
  price: Number,
  desc: String
});

/* create a mongoose model */
var productModel = mongoose.model('Product', productSchema);

/* remove function : returns a promise */
var removeAllProducts = function () {
  console.log('removing all products');
  return productModel.remove(); // promise (see mongoose doc)
};

/* save function : returns a promise */
var saveProduct = function (pProduct) {
  console.log('saving new product', JSON.stringify(pProduct));
  let newProduct = new productModel();
  newProduct.name = pProduct.name;
  newProduct.price = pProduct.price;
  newProduct.desc = pProduct.desc;  
  return newProduct.save(); // promise (see mongoose doc)
};

/* find function : returns a promise */
var listProducts = function (pQuery) {
  console.log('listing products');
  let query = pQuery ? pQuery : {};
  let projection = { '_id': 0, '__v': 0 };
  return productModel.find(query, projection).exec(); // promise (see mongoose doc)
};

/* ============================== */
/* ==== API REST DEFINITIONS ==== */
/* ============================== */
/* our first REST API method */
restApiRouter.get('/', function (req, res) {
  res.status(200).json({ 'message': 'this is our first REST API method, server is running' });
  return;
});

/* get products list : REST API method */
restApiRouter.get('/products', async function (req, res) {
  let products = [];
  try {
    products = await listProducts();
    res.status(200).json(products);
  } catch (err) {
    res.status(500).json(err); // displays error content : dangerous for security reason : only for demo purpose
  }
  return;
});

/* create a product : REST API method */
restApiRouter.post('/products', async function (req, res) {
  try {
    let product = await saveProduct(req.body);
    res.status(200).json(product);
  } catch (err) {
    res.status(500).json(err); // displays error content : dangerous for security reason : only for demo purpose
  }
  return;
});

/* ============================== */
/* == API GRAPHQL DEFINITIONS  == */
/* ============================== */
/* our GraphQL API */
// == 1 == create GraphQL schema
let schema = graphql.buildSchema(`
type Product {
  name: String!
  price: Int
  desc: String
}

input ProductInput {
  name: String!
  price: Int
  desc: String
}

type Query {
  message: String,
  products(name: String): [Product]
}

type Mutation  {
  saveProduct(input: ProductInput): Product
}  
`);

// == 2 == our resolvers
var root = {
  message: () => { return 'this is our first GraphQL API method, server is running'; },
  products: async ({ name }) => {
    return await listProducts(name ? { 'name': name } : {});
  },
  saveProduct: async ({ input }) => {
    return await saveProduct(input);
  },
};

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

/* ============================== */
/* ====== STARTING WEB PROXY ==== */
/* ============================== */
/* let's start ! */
// == 1 ==
mongoose.connect(mongooseURL, function (error) {
  if (error) {
    console.log('FAILED : Unable to connect to MongoDB [%s]', mongooseURL);
    console.log('ABORTED : API REST and GraphQL server not started');
    process.exit(0);
  }
  else {
    console.log('SUCCEED : Connected to MongoDB [%s]', mongooseURL);

    // == 2 == 
    app.listen(proxyPort, async function () {
      console.log('SUCCEED : API REST and GrapQL server started on port [%d]', proxyPort);

      // == 3 ==
      try { await removeAllProducts(); }
      catch (err) { console.log(err); }

      try { await saveProduct({ 'name': 'desktop', 'price': 1000, 'desc': 'gamer desktop' }); }
      catch (err) { console.log(err); }
      try { await saveProduct({ 'name': 'laptop', 'price': 1500, 'desc': 'gamer laptop' }); }
      catch (err) { console.log(err); }
    });
  }
});

Restart the server

Command prompt

C:\node-rest-graphql-hello>npm start

> node-rgh@1.0.0 start C:\node-rest-graphql-hello
> node server.js

SUCCEED : Connected to MongoDB [mongodb://localhost:27017/nrghDB]
SUCCEED : API REST and GrapQL server started on port [3000]
removing all products
saving new product {"name":"desktop","price":1000,"desc":"gamer desktop"}
saving new product {"name":"laptop","price":1500,"desc":"gamer laptop"}
Test REST

With our preferred REST Client

  • creation of un new product
Test GraphQL

In the browser we enter http://localhost:3000/api/graphql

  • creation of un new product

  • it has been created

node  api  rest  graphql  mongo 
comments powered by Disqus