Why Sails hook GraphQL required?
Graphql is a best thing in this time, and SailsJS provides almost same functionality as GraphQL, but still its missing flexibility of GraphQL. Also in current time where requirement is so dynamic and creating same kind of API is frustrating, specially maintaining versioning and documentation of it. That’s were automation comes in our mind faster, no bugs and no maintenance. As titles suggests Sails hook GraphQL is extending sails functionality to support GraphQL out of the box. This blog will server as readme to use this blog as well as it will include wide range of example for GraphQL query.
Fortunately we have ORM like waterline and sequelize. So here idea is while leveraging Waterline functionality, building GraphQL query and mutation automatically based on Model definitions which were allowed access through graphql.
Again while thinking of GraphQL, Most important thing is to handle its security based on permission to user. So I will cover a bit on how we can handle GraphQL query/mutation security. Of-course its not included in to sails hook GraphQL.
Installation and usage
Install it with below command, just like any other packages we are using.
npm install graphql express-graphql graphql-tools @logisticinfotech/sails-hook-graphql
To enable it in any sailsjs model we need to add config like below
//---- api/models/User.js module.exports = { tableName: 'users', //---- Below property is used by sails hook graphql, this determines //---- if graphql query mutation is allowed or not for this model. graphql: { query: true, mutation: true }, attributes: { id: { type: 'number', autoIncrement: true, }, name: { type: 'string' }, } }
>> Generate one POST Action as below
// module.exports = { friendlyName: 'Graphql POST endpoint', description: 'All Graphql', inputs: {}, exits: { success: { description: 'All Graphql' } }, fn: async function(inputs, exits) { sails.config.graphql(this.req, this.res); //<<< Important part } };
Also make sure to add route entry : ‘POST /graphql’: { action: ‘graphql/gql’ },
After this we can check open graphql playground on : https://localhost:1337/graphql
Auto generated GraphQL Schema
Once setup done, on sails lift, It will generate schema.graphql file on root of the project and its content should be something like below:
"""CustomJson scalar type""" scalar CustomJson type user implements Node { id: String! name: String } type Mutation { createUser(id: CustomJson, name: String): user updateUser(id: CustomJson, name: String): user deleteUser(id: CustomJson, name: String): user } type Query { user(id: String!): user users(where: CustomJson, sort: String, skip: Int, limit: Int, populate: CustomJson, aggregate: CustomJson): [user] }
As we can see above, It generate basic CRUD operation query/mutations.
Graphql query examples
Normal usage
Example 1 : For more whare conditions please refer sails documentation here
{ users(where: { name: { 'contains' : "Elijah Homenick" }, id: { gt: "40", lte: "45" }, } sort: "name DESC", skip: 10, limit: 2 ){ id name username } }
Aggregate Usage
This is little customisation and extra options we can use with GraphQL as below examples, So just try them out and user appropriately.
Count
{ users(aggregate:["count"], where: {id: { gt: "40", lte: "45" }}) { count } }
Sum of specific fields
{ users(aggregate:["sum","id"], where:{id: { gt: "40", lte: "45" }}) { sum:count } }
Average of specific where condition
{ users( aggregate:["average","id"], where:{id: { gt: "40", lte: "45" }} ){ average:count } }
Populate Usage
One to one, One to Many and Many to Many is supported by Waterline model. So here is examples related to One to Many. But it support same way for Many to Many Pivot table also.
{ users(populate: [ {type: "tweets",criteria:{where:{id:20},limit:2}} ], limit: 1) { id, myname:name, tweets } }
Graphql Mutation Example
In terms of GraphQL mutation we are covering Insert, Update and Delete
mutation{ createUser( name:"test", username:"test110", email:"[email protected]" ){ id, username } } // For now we can only use id to update mutation{ updateUser(id:102, name:"test"){ name } } mutation{ deleteUser(id:102){ name } }
Handling security
This is little loose point here to handle. For this we need to update our POST GraphQL Action as below. Just make sure this is not copy/paste example, It is just for explanation on how we can implement query and user based permission check.
var graphqlHTTP = require('express-graphql'); var gql = require('graphql-tag'); module.exports = { friendlyName: 'Graphql', description: 'All Graphql', inputs: {}, exits: { success: { description: 'All Graphql' } }, fn: async function (inputs, exits) { var userid = await sails.helpers.getUserIdFromJWTToken.with({ token: this.req.headers.authorization }); var arrPermission = { "user": "accountusers.list", "users": "accountusers.list", } graphqlHTTP.getGraphQLParams(this.req).then(async params => { var obj = gql`${params.query}`; var operationName = obj.definitions[0].selectionSet .selections[0].name.value; var reqPermission = arrPermission[operationName]; let policyResponse = await sails.helpers.policyHelper .with({ policy_type: reqPermission, user_id: userid }) .tolerate("permissionError", err => { return this.res.forbidden(err.raw); }); sails.config.graphql(this.req, this.res); }); } };
Explanation
#1. I assume we are using JWT authentication and we can get current userid from it
#2. arrPermission is the permission array which mapped agains graphql operaton query/mutation, In most cases We have to manage it manually.
#3. our action is wrapped around graphqlHTTP.getGraphQLParams, Which allows us to access operation name easily.
#4. We are getting operation name first, then getting required permission to access this operation
#5. Now we have everything, we just need to check if current user has this permission or not with function like sails.helpers.policyHelper. Function is responsible to return request if user has no permission. We can build this function as per our requirement.
So this is rough idea but mostly we can write any custom logic here as per our security requirement.
Conclusion
Firstly thank you for your passions if you have reached here . I hope this will make little easier for your upcoming projects.
Note: This hook doesn’t support socket subscription yet.
Please post comment/suggestion and bug/issues.
Here I am posting testing GitHub repo, In this repo hook is directly added in api/hooks folder. Basically it’s my repo with which I have developed this hook.