Play it your way: POC on GraphQL
GraphQL+Express+PostgreSQL+MongoDB
GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data. GraphQL isn’t tied to any specific database or storage engine and is instead backed by your existing code and data.
Until now, we have seen what is GraphQL in the article Introduction: Overview of GraphQL and how to use GraphQL query language to query data from GraphQL runtime using GraphiQL user interface in the article GraphQL: The Query Language.
In this session, we will set up the GraphQL runtime using GraphQL+Express+PostgreSQL+MongoDB
To set up a GraphQL server utilizing two different database connections namely PostgreSQL and MongoDB, we will be creating a NodeJs server using the Express framework.
Let’s start by installing dependencies.
Install Dependencies:
npm install express express-graphql graphql mongodb pg
express: NodeJs Framework
express-graphql: GraphQL module for integration with Express
graphql: GraphQL lib
mongodb: MongoDB node module
pg: PostgreSQL node module
Setup GraphQL-Express Server
Basic Express server setup is all we need:
// ./index.js
const app = require('express')();
const Schema = require('./schema');
const graphqlHTTP = require('express-graphql');
const PORT = process.env.PORT || 3000;app.use('/graphql', graphqlHTTP({ schema: Schema, graphiql: true}));app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
You might be wondering about the Schema here, let’s find out what it is next.
GraphQL Schema
There are basically two types of APIs in GraphQL
- query: These are data fetching API’s.
- mutation: These are the ones that will have side effects i.e. modify data.
The GraphQL object which defines and differentiates between these two API’s is GraphQLSchema
// ./schema/index.js
const { GraphQLSchema } = require('graphql');
const RootQueryType = require('./rootquerytype');
const RootMutationType= require('./rootmutationtype');module.exports = new GraphQLSchema({
query: RootQueryType,
mutation: RootMutationType
});
To define the query
and mutation
APIs, GraphQL provides a helper class GraphQLObjectType
GraphQLObjectType
The GraphQLObjectType
details the query
and mutation
API definitions.
new GraphQLObjectType({
name: 'TypeName',
description: 'Type Description',
fields: {
hello: {
type: GraphQLString,
description: 'API description',
resolve: () => 'world'
}
}
});
The name
, description
exposes the documentation details in GraphiQL interface and fields
key holds all the query
or mutation
API path keys. This, in turn, defines the resolver function which will have code either to return query
or mutate
the data in the database. The type
defines the return type of the API.
Before we write a query or mutation query let’s create database connections.
Database Connections
We are going to demonstrate a GraphQL server with multiple database connections ie. PostgreSQL and MongoDB.
Here we are going to use PostgreSQL for user data and MongoDB to contain the users count.
Create on database and table in PostgreSQL.
create database graphql_test;
# connect to the graphql_test databasecreate table users(
id integer generated by default as identity,
first_name character varying
);
insert into users(first_name) values('test_user_1'); #dummy data
MongoDB will create the database on the fly.
Let’s update the GraphQL-Express to create and consume the PostgreSQL and MongoDB connections.
Here we are considering both the databases are running on your machine and on their respective default ports.
// ./index.js
const app = require('express')();
const pg = require('pg');
const { MongoClient } = require('mongodb');
const Schema = require('./schema');
const graphqlHTTP = require('express-graphql');
const PORT = process.env.PORT || 3000;const pgPool = new pg.Pool({
user: 'postgres',
host: 'localhost',
database: 'graphql_test',
password: 'xxxxx'
});MongoClient.connect('mongodb://localhost:27017/graphqltest',
(err, mongoClient) => {
app.use('/graphql', graphqlHTTP({
schema: Schema,
graphiql: true,
context: {
pgPool : pgPool,
mongo : mongoClient.db("graphql-training")
}
})
);
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
});
We have passed the database connections to the context
option which provides a way to share objects throughout the application resolver function. Now in query and mutation API resolver functions, we will consume those connections.
GraphQL Query API
// ./schema/rootquerytype.js
const { GraphQLString, GraphQLInt, GraphQLList, GraphQLObjectType } = require('graphql');const RootQueryType = new GraphQLObjectType({
name: 'RootQueryType',
description: 'This holds all the query APIs',
fields: {
users: {
type: new GraphQLList(UserType),
description: 'Handler for get users list',
resolve: async ( obj, args, context) => {
const { pgPool } = context;
pgPool.query(`select * from users`, [])
.then(res => res.rows);
}
},
usersCount: {
type: GraphQLInt,
description: 'Handler for get usersCount',
resolve: async ( obj, args, context) => {
const { mongo } = context;
return mongo.collection('metrics').findOne({key :
'userCount'}).then(res => res.value);
}
}
}
});
module.exports = RootQueryType;// ./schema/types/UserType.jsconst UserType = new GraphQLObjectType({
name: 'UserType',
description: 'User object type',
fields:{
id: {
type : GraphQLInt,
resolve: (obj)=> obj.id
},
firstName:{
type: GraphQLString,
resolve: (obj)=> obj.first_name
}
}
})module.exports = UserType;
The resolver function accepts three arguments:
- obj: base object reference.
- args: Arguments passed for the query path.
- context: Context object passed down from the index.js file which holds the database connection objects.
The users
field in RootQueryType
holds the object with keys:
- type: return type of the resolver function.
- resolve: resolver function to fetch the users list.
GraphQL Mutation API
// ./schema/rootmutaiontype.js
const { GraphQLString, GraphQLObjectType } = require('graphql');const RootMutationType = new GraphQLObjectType({
name: 'RootMutationType',
description: 'this holds all the mutation APIs',
fields: {
user: {
type: UserType,
args: { input:{type : UserArg}},
description: 'Handler for create user',
resolve: async ( obj, {input}, context) => {
const { pgPool, mongo } = context;
const user= await pgPool.query(`insert into users(fist_name)
values($1) returning *`,[input.firstname])
await mongo.collection('metrics')
.update({ key : 'userCount' },{ $inc: { value: 1 }},{
upsert: 1 });
return user
}
}
}
});
module.exports = RootMutationType;//./schema/types/UserArg.jsconst { GraphQLString, GraphQLInputObjectType } = require("graphql");let UserArg = new GraphQLInputObjectType({
name: 'UserArg',
fields:{
firstname: { type : GraphQLString }
}
})
module.exports = UserArg;
Same as the Query API, we need to implement a resolver function to add new users and update the count.
For accepting input arguments, we have defined input
of type UserArg
which is of GraphQLInputObjectType
which accepts firstname
of GraphQLString
type.
Conclusion
And that’s it! Now you know how to set up the GraphQL server with multiple connections and consume it.
Source code: https://github.com/rahulpawarglobant/graphql-training