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
- GraphQL (voir aussi graphql-js)
- express-graphql.
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 servermongo
: 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
- Restart the server
- Type the http://localhost:3000 URL in the browser
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 definedgraphiql=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
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
Test GraphQL
In the browser we enter http://localhost:3000/api/graphql