The CreatorCon Call for Content is officially open! Get started here.

Shivam  Gupta
ServiceNow Employee
ServiceNow Employee

What is GraphQL?

GraphQL is an open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. It's an alternative to standards like REST. It lets clients fetch exactly the data they need which makes API development faster, flexible, and easy to test. It has a single endpoint for all types of requests. 

Please read more about GraphQL basics here.

Using GraphQL on Now Platform

Please find the documentation here.

There are 4 basic components while designing GraphQL APIs on Now Platform

  1. Schema
  2. Scripted Resolver
  3. Type Resolver
  4. Resolver Mapping

Schema

Defines the structure and type of data using GraphQL Schema Definition Language (SDL). It supports Query and Mutation operations along with custom object type, input, union, interface, enum, and default scalar types (Int, Float, String, Boolean, and ID).

Scripted Resolver

Defines the data returned by each field.

These functions are available on the ResolverEnvironment object.

  • getArguments(): Returns the arguments of the previous field.
  • getSource(): Returns the parent object.

Type Resolver

Resolves interfaces and unions into concrete GraphQL types.

These functions are available on the TypeResolutionEnvironment object.

  • getArguments(): Returns the arguments of the previous field.
  • getObject(): Returns the object received from data fetcher.
  • getTypeName(): Returns the name of the interface or union type.

Resolver Mapping

Maps resolvers to fields in the schema.

 

Hey, let's build something now 

Usecase: Develop a GraphQL API to perform the below operations

  1. Get Incident Number, Short description, and Category using sys_id
  2. Close an active Incident using sys_id and add the given Resolution code, Resolution notes, and update the category if given

Solution:

Note: Please go through the documentation to become familiar with the GraphQL basics on the Now Platform

Create a new GraphQL API

  • Navigate to System Web Services -> GraphQL -> GraphQL APIs
  • Click on 'New' and fill in the below details in order to create a new GraphQL API
    • Name - Name of the schema
    • Schema namespace - The namespace (unique identifier) for the schema in the current application
    • Select relevant Security configuration. For this usecase, let's go with only 'Requires authentication
  • Click on 'Submit'

find_real_file.png

 

Build Schema

  • We see two object types Query and Mutation in the schema by default. These objects are the entry point for the operations we want to perform via the API.
  • 'Query' type is required for retrieving data.
  • 'Mutation' type is required for updating or deleting the data

Let's add the below operation in Query

type Query {
   getIncidentDetails(sysId: ID!): GetIncidentDetailsResult!
}

Let's define an Error interface that needs to be implemented by all types of Error responses

interface Error {
	errorType: String!
	errorMessage: String!
}

Define GetIncidentDetailsResult type

union GetIncidentDetailsResult = GetIncidentDetailsSuccess | GetIncidentDetailsError

Define GetIncidentDetailsSuccess

type GetIncidentDetailsSuccess {
	number: String!
	shortDescription: String!
	category: String
}

Define GetIncidentDetailsError

type GetIncidentDetailsError implements Error {
	errorType: String!
	errorMessage: String!
}

So, we are done with creating Schema for the getIncidentDetails query.

 

Here is the complete Schema as shown below

ShivamGupta_0-1682320087907.png

 

Let's create a new GraphQL Scripted resolver for the getIncidentDetails query named "Get Incident Details Resolver"

(function process( /*ResolverEnvironment*/ env) {
    var args = env.getArguments();
    var sysId = args.sysId;
    var gr = new GlideRecordSecure("incident");
    if (gr.get(sysId)) {
        return {
            number: gr.getValue("number"),
            shortDescription: gr.getValue("short_description"),
            category: gr.getValue("category")
        };
    }
    return {
        errorType: "IncidentRetrievalError",
        errorMessage: "Record sys_id is not valid"
    };
})(env);

Add a new GraphQL Resolver mapping as shown below. It tells the framework which Resolver script to execute for which query.

find_real_file.png

Create a new GraphQL Type Resolver to resolve the type of object returned in union GetIncidentDetailsResult

(function process( /*TypeResolutionEnvironment*/ env) {
    var obj = env.getObject();
    return obj.hasOwnProperty('errorType') ? 'GetIncidentDetailsError' : 'GetIncidentDetailsSuccess';
})(env);

Create a new GraphQL Type Resolver to resolve the type of concrete implementation returned in interface Error

(function process( /*TypeResolutionEnvironment*/ env) {
    var obj = env.getObject();
    var error;
    switch (obj.errorType) {
        case "IncidentRetrievalError":
            error = "GetIncidentDetailsError";
            break;
    }
    return error;
})(env);

We are done with creating Scripted Resolvers, Type Resolvers, and Resolver mappings for our first query getIncidentDetails

Let's test it out

I am using Insomnia for the demo purpose. Please feel free to use any other tool.

Success:

find_real_file.png

Error:

find_real_file.png

Now, we will build our first mutation. 

Let's add the below operation in Mutation

type Mutation {
	closeIncident(closureDetails: CloseIncidentInput!): CloseIncidentResult!
}

Define CloseIncidentInput type input

input CloseIncidentInput {
	sysId: ID!
	resolutionCode: String!
	resolutionNotes: String!
	category: String
}

Define CloseIncidentResult type

union CloseIncidentResult = CloseIncidentSuccess | CloseIncidentError

Define CloseIncidentSuccess

type CloseIncidentSuccess {
	number: String!
}

Define CloseIncidentError

type CloseIncidentError implements Error {
	errorType: String!
	errorMessage: String!
}

So, we are done with creating Schema for the closeIncident mutation.

 

Here is the complete updated Schema as shown below

ShivamGupta_3-1682318600202.png

Let's create a new GraphQL Scripted resolver for the closeIncident mutation named "Close Incident Resolver"

(function process( /*ResolverEnvironment*/ env) {
    var args = env.getArguments();
    var sysId = args.closureDetails.sysId;
    var resolutionCode = args.closureDetails.resolutionCode;
    var resolutionNotes = args.closureDetails.resolutionNotes;
    var category = args.closureDetails.category;
    var gr = new GlideRecordSecure('incident');
    if (gr.get(sysId)) {
        gr.setValue('state', 7);
        gr.setValue('close_code', resolutionCode);
        gr.setValue('close_notes', resolutionNotes);
        if (category) {
            gr.setValue('category', category);
        }
        gr.update();
        return {
            number: gr.getValue("number")
        };
    }
    return {
        errorType: "IncidentClosureError",
        errorMessage: "Record sys_id is not valid"
    };

})(env);

Add a new GraphQL Resolver mapping as shown below

find_real_file.png

Create a new GraphQL Type Resolver to resolve the type of object returned in union CloseIncidentResult

(function process(/*TypeResolutionEnvironment*/ env) {
	var obj = env.getObject();
    return obj.hasOwnProperty('errorType') ? 'CloseIncidentError' : 'CloseIncidentSuccess';
})(env);

Update the GraphQL Type Resolver of interface Error

(function process( /*TypeResolutionEnvironment*/ env) {
    var obj = env.getObject();
    var error;
    switch (obj.errorType) {
        case "IncidentRetrievalError":
            error = "GetIncidentDetailsError";
            break;
        case "IncidentClosureError":
            error = "CloseIncidentError";
            break;
    }
    return error;
})(env);

We are done with creating Scripted Resolvers, Type Resolvers, and Resolver mappings for our first mutation closeIncident

Let's test it out

Success:

find_real_file.png

Error:

find_real_file.png

I hope that you enjoyed the article. Please let me know if you have any suggestions or comments.

Comments
Mike Perkins
Tera Contributor

Following your steps, the schema does not save and errors with Schema:The field type 'getIncidentDetailsResult' is not present when resolving type 'Query' [@6:1].

 

Some additional context on how the union GetIncidentDetailsResult functions would be helpful.  I'm not sure how the two would union unless the idea is on failure there is an empty result set for success to it produces one table or the other.

Shivam  Gupta
ServiceNow Employee
ServiceNow Employee

Hi @Mike Perkins,

Thank you for trying it out. It was a typo.

Please follow the updated steps. Also added the final Schema screenshots to make it easy to follow.

 

union GetIncidentDetailsResult would help here the client to parse responses from the API in a more predictable fashion.

As there might be scenarios when the getIncidentDetails query fails due to an invalid sys_id or some security constraints, we still want to return a structurally valid and meaningful response. It would help the client to understand the root cause of failure and how it might be resolved.

 

union type of response helps us to return different types of responses based on the processing outcome.

Hope it helps!

 

Please read more about union types here.

Suyog Aptikar
Giga Guru

@shivam gupta  Thanks for writing down and explaining in detail, I am new to graphql and trying to understand more on it I have a few fundamental queries:

 

1-> When we say it is an alternative to REST, what better it has to offer than REST, or in what situation GraphQL is better than REST, if you can give a simple use case would be really helpful.

2-> What type of authentication/authorization mechanism is supported for graphQL?

Version history
Last update:
‎04-24-2023 12:15 AM
Updated by:
Contributors