Architecting Your Custom GraphQL API in ServiceNow: A Scalable Approach

Posted by admin on October 28, 2024

Learn how to design a flexible, efficient GraphQL API in ServiceNow to streamline data retrieval and enhance application performance with a well-structured approach.

Disclaimer: This custom GraphQL API schema represents my personal design approach, which aligns with my specific use cases and requirements in ServiceNow. It is by no means the only way to structure a GraphQL API, and there are various other approaches that may be more suitable depending on different needs or preferences. Readers are encouraged to explore alternative designs that might offer improved flexibility, performance, or simplicity for their unique scenarios.

My Custom GraphQL API Schema

ServiceNow GraphQL APIs provide a highly flexible and efficient way to manage and retrieve data across diverse tables and fields. Here, I’ll break down a custom GraphQL API schema that I designed specifically for my projects in ServiceNow. This schema is structured to use just two core queries, making it adaptable to any table (type) I define. By architecting it this way, I can maintain a lean codebase while offering a versatile and scalable API that minimizes redundancy and maximizes functionality.

The Design

The Schema

schema {
	query: Query
}

type Query {
	getSingle(tableName: String, queryConditions: String): base
	getMultiple(tableName: String, queryConditions: String, pagination: Pagination): [base]
	getCount(tableName: String, queryConditions: String): Int
}

type DisplayableString{
	value: String
	display_value: String
	reference_table: String
}

type DisplayableReference{
	value: String
	display_value: String
	reference_table: String
}

type DisplayableBool{
	value: Boolean
	display_value: String
}

type DisplayableInt{
	value: Int
	display_value: String
}

type DisplayableFloat{
	value: Float
	display_value: String
}

input Pagination {
	offset: Int
	limit: Int
}

interface base{
	sys_id: DisplayableString
}

type task implements base {
	sys_id: DisplayableString
  active: DisplayableBool
  assigned_to: DisplayableReference
  assignment_group: DisplayableReference
  business_duration: DisplayableString
  business_service: DisplayableReference
  calendar_duration: DisplayableString
  closed_at: DisplayableString
  closed_by: DisplayableReference
  close_notes: DisplayableString
  company: DisplayableReference
  contact_type: DisplayableString
  contract: DisplayableReference
  correlation_display: DisplayableString
  correlation_id: DisplayableString
  //additional fields here...
}

Query Capabilities


The Query type includes three core functions for interacting with ServiceNow tables:

type Query {
	getSingle(tableName: String, queryConditions: String): base
	getMultiple(tableName: String, queryConditions: String, pagination: Pagination): [base]
	getCount(tableName: String, queryConditions: String): Int
}
  1. getSingle: This function retrieves a single record from a specified table based on certain query conditions.
  2. getMultiple: This function retrieves multiple records with pagination support, allowing efficient data handling for large datasets.
  3. getCount: Returns the total count of records matching the provided conditions in a specified table.

Each query uses tableName and queryConditions to define the table to retrieve data from and the filtering conditions, creating a highly adaptable querying mechanism.

Resolvers for Each of the Query Capabilities

Repository mentioned below is using the repository pattern outlined in this blog post Designing Your ServiceNow Script Includes.

Below are two resolvers to resolve the getSingle and getMultiple queries:

getSingle

(function process( /*ResolverEnvironment*/ env) {

    var queryConditions = env.getArguments().queryConditions;
		var tableName = env.getArguments().tableName;
    return new Repository(tableName).addEncodedQuery(queryConditions).getSingle(true);

})(env);

getMultiple

(function process( /*ResolverEnvironment*/ env) {

    var queryConditions = env.getArguments().queryConditions;
		var tableName = env.getArguments().tableName;
		var pagination = env.getArguments().pagination;

    var repo = new Repository(tableName);
    repo.addEncodedQuery(queryConditions);

	if(pagination){
		repo.paginate(pagination.offset, pagination.limit);
	}

    return repo.getMultiple(true);

})(env);

getCount

(function process( /*ResolverEnvironment*/ env) {

    var aggr = new GlideAggregate(tableName);
    aggr.addEncodedQuery(env.getArguments().queryConditions);
    aggr.addAggregate('COUNT');
    aggr.query();
    aggr.next();
    return aggr.getAggregate('COUNT');

})(env);

Security


Keep in mind that both getSingle and getMultiple use the Repository script include, and by default, all data retrieved utilizes GlideRecordSecure, thereby adhering to ACLs. This protects against unauthorized data access.

Displayable Types


The schema includes various Displayable types, each designed to hold both the underlying data and its display value. These types add a display_value property, which is often used in ServiceNow to show human-readable data alongside the raw stored values.

  • DisplayableString: Holds a string value with display attributes, common for text fields.
  • DisplayableReference: Similar to DisplayableString but includes an additional reference_table attribute to point to related records in other tables.
  • DisplayableBool: Represents boolean fields with an additional display value.
  • DisplayableInt: Adds display functionality to integer fields.
  • DisplayableFloat: Used for fields requiring float values, such as currency or measurements.

Pagination Input Type

The Pagination input type is essential for managing large datasets. With offset and limit parameters, users can control the record start point and the number of records returned, ensuring optimized and manageable data retrieval.

input Pagination {
	offset: Int
	limit: Int
}

Base Interface


The base interface introduces a fundamental structure with sys_id as a required field. This sys_id represents the unique identifier for records in ServiceNow, allowing each record to inherit this structure for consistent identification.

If you notice that both getSingle and getMultiple return the base type, this setup allows for enhanced usability and consistency by only exposing these two capabilities to the front end.

interface base{
  sys_id: DisplayableString
}

To set this up in ServiceNow, create a GraphQL Type Resolver then select "base" from the "Type" dropdown. The code for this resolver below:

(function process(/*TypeResolutionEnvironment*/ env) {

    return env.getArguments().tableName;

})(env);

Defining a Task Type that Implements the Base Interface


One of the powerful aspects of this GraphQL schema is its flexibility in defining types that implement the base interface, allowing consistent querying capabilities across different data types. Here’s how the task type is defined:

type task implements base {
	sys_id: DisplayableString
  active: DisplayableBool
  assigned_to: DisplayableReference
  assignment_group: DisplayableReference
  business_duration: DisplayableString
  business_service: DisplayableReference
  calendar_duration: DisplayableString
  closed_at: DisplayableString
  closed_by: DisplayableReference
  close_notes: DisplayableString
  company: DisplayableReference
  contact_type: DisplayableString
  contract: DisplayableReference
  correlation_display: DisplayableString
  correlation_id: DisplayableString
  //additional fields here...
}

By implementing the base interface, the task type inherits the sys_id field, ensuring that each task instance has a unique identifier. This structure allows for streamlined querying with consistent fields across various types that implement base.

Example Query for a Single Task Record

To fetch a single task record, you can call the getSingle query, passing the tableName and queryConditions. For example, if you want to retrieve a single active task, your query would look like this:

Example Query for a Multiple Task Records

To fetch multiple task records, the same syntax applied with the exception of swapping out "getSingle" for "getMultiple" and adding in the pagination input.

Conclusion:


Designing a custom GraphQL API in ServiceNow empowers developers to efficiently retrieve and manage complex datasets across multiple tables, enhancing flexibility and performance. By carefully structuring the API with reusable types, dynamic query capabilities, and pagination, this architecture provides a robust foundation for scalable applications. As ServiceNow continues to evolve, a well-architected GraphQL API can adapt to changing requirements, simplifying both frontend and backend development. Embracing this approach helps create a more seamless, intuitive experience for users and developers alike, ensuring that data is accessible, manageable, and ready to support innovative solutions on the platform.

Copyright © Formcloud LLC 2024