Scripted REST API in ServiceNow
Paul Stuart
ServiceNow | CIS-CSA | CIS-ITSM | CIS-HRSD | ITIL-4 | Integrations | JavaScript |
Scenario
Let's say we are running a ServiceNow instance which is used for a variety of purposes in an organization. And, let's say also, the group and user data on that instance is our source of truth. Therefore, other third-party systems will need to interact with the SN instance to retrieve that data and align it with their system. Perhaps an organization has a SAP cloud instance and is using it for procurement and finance purposes. User and Group data are never written on the SAP side, they need to match at all times, with what is on the SN instance.
We also, do not want the 3rd party system to just make API calls to the instance at anytime and access whichever data it wants. We want to control what is being exposed, and how it is being exposed. For the purposes of this, I will not go into ACL security as its a huge topic. But yes, that is also part of this scenario, both important and useful. For this demo, I will just focus on the scripting logic and calling the newly created API externally, both in postman and the REST API explorer.
What does the 3rd-party need from the ServiceNow instance?
Part One - Create the Utility Script with Global Functions
When I create Scripted REST APIs in ServiceNow, I want the script to just be an entry point for the outside party to interact with. I do not want huge blocks of code doing various GlideRecord queries. Like any coding/development, always try and modularize the logic so it can be re-used. Loose-coupling, high-cohesion allows the script to be used for a variety of purposes that will save multiple lines of code every time I need a script elsewhere on the platform.
The Above image shows the custom Script Include and the functions that can be called. I made sure these were accessible and could be easily re-used for other purposes on the platform, perhaps in a flow or action, these can easily be called in one line of code to produce the required data.
var userUtilityFunctions = Class.create();
userUtilityFunctions.prototype = {
initialize: function() {},
getUserGroups: function(userName) {
userID = this.getUserSysId(userName);
var grGroupMember = new GlideRecord("sys_user_grmember");
grGroupMember.addQuery("user", userID);
grGroupMember.query();
var groupNames = [];
while (grGroupMember.next()) {
var groupSysId = grGroupMember.getValue("group");
var grGroup = new GlideRecord("sys_user_group");
if (grGroup.get(groupSysId)) {
var groupName = grGroup.getValue("name");
groupNames.push(groupName);
}
}
return groupNames;
},
getGroupUsers: function(groupName) {
var groupID = this.getGroupSysId(groupName);
var grGroupMember = new GlideRecord("sys_user_grmember");
grGroupMember.addQuery("group", groupID);
grGroupMember.query();
var userSysIds = [];
while (grGroupMember.next()) {
var userSysId = grGroupMember.getValue("user");
userSysIds.push(userSysId);
}
var groupUsers = [];
var grUser = new GlideRecord("sys_user");
grUser.addQuery("sys_id", "IN", userSysIds);
grUser.query();
while (grUser.next()) {
var userName = grUser.getValue("name");
groupUsers.push(userName);
}
return groupUsers;
},
getUserSysId: function(uName) {
var userGR = new GlideRecord('sys_user');
userGR.addQuery('name', uName);
userGR.query();
var sysID = 'no user sys_id found';
if (userGR.next()) {
sysID = userGR.getValue('sys_id');
return sysID;
} else {
return sysID;
}
},
getGroupSysId: function(gName) {
var groupGR = new GlideRecord('sys_user_group');
groupGR.addQuery('name', gName);
groupGR.query();
var group_sys_id = 'no group sys_id found';
if (groupGR.next()) {
group_sys_id = groupGR.getValue('sys_id');
return group_sys_id;
} else {
return group_sys_id;
}
},
getActiveGroupCount: function() {
var agg = new GlideAggregate('sys_user_group');
var total = 0;
agg.addQuery('active', true);
agg.addAggregate("COUNT");
agg.query();
if (agg.next()) {
total = agg.getAggregate("COUNT");
}
return total;
},
getGroupList: function() {
var ga = new GlideAggregate('sys_user_group');
ga.addEncodedQuery('active=true');
ga.addAggregate('GROUP_CONCAT', 'name');
ga.query();
var groupList = [];
while (ga.next()) {
var groupName = ga.getValue('name');
groupList.push(groupName);
}
return groupList;
},
type: 'userUtilityFunctions'
};
The code above saves me a lot of time when I get to the scripted REST API, as I can just focus on the request and response, I will let the Utility file do the heavy lifting for me to get the GlideRecords and do the queries. I then made sure all of these worked as expected in a background script. You can see from just a couple of lines of code I can get back the data I need:
Output
Part Two - Create the Scripted REST API
Now the functions are working fine on the ServiceNow side, its time to build the API endpoint. There are two parts when you make a scripted REST API, the first is the container, which is your service - Scripted REST Service. Then I can have many resources within that service as shown in the image earlier in the article. For this demonstration I have made one resource called getGroupUsers, which is also the name of the function in the Script Include to keep it simple.
领英推荐
You can see the automatic Base API path that gets generated. This can then be used for testing in Postman or in the REST API explorer built into ServiceNow. The code below shows that at its basic level, the script is really handling two things; a request and a response.
Important on the first line inside the main function there is the request object, which then accesses the queryParams and lastly the 'group_name' is added, this can be whichever name I choose, but its important to make it meaningful so the third party accessing the API knows what is required to be sent in the request in ServiceNow.
In my case, I want the user to simply pass in the name of the group that they want, and they will get all the users of that group returned to them in an array.
(function process( /*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {
var utils = new userUtilityFunctions();
var groupName = request.queryParams.group_name;
if (!groupName) {
response.setStatus(500);
response.setBody({
"msg": "There was no group_name in the query parameters"
});
} else {
var exists = utils.getGroupSysId(groupName);
if (exists == 'false') {
response.setStatus(500);
response.setBody({
"msg": "The Group you are requesting does not exist, please check spelling or use the group-list API for a list of names"
});
} else {
var userArray = utils.getGroupUsers(groupName);
var users = {
"status": "OK",
"users": userArray
};
response.setBody(users);
}
}
})(request, response);
Output using the API Explorer - Related link on the resource
Output in JSON from Postman
So the first line of the Scripted REST resource is important as it defines the query parameter that is needed to interact with the endpoint. I now want to catch errors and handle them gracefully. So if the the group name was misspelt, its blank or the name doesn't exist, I have made sure the appropriate response gets set. I have used the 500 server error code and set a message in the body of the response for the user. The beauty of doing this, is you can define what messages you want to send back in case of an error. Try and think of useful information and guidance you can give the user in the response body.
You can see the logic in the 'if' and 'else' statements to validates the possible scenarios when an error occurs. If after all of those are checked, the array of users are returned in the response body to the requester.
You can see the output in the images, if the correct parameter is queried in the request, the users from the defined group will be returned in an array. This is one example, and have only really operationalized one of the main functions in the Utility Class for my GET resource.
This is enough to get started, from here, there are more functions that can be used in other scripted resources under this single service of users and groups. There are also some ACLs to configure, along with how all the new REST endpoints support each other for third-parties to quickly and easily get the data they need.
Thanks so much for reading!
ServiceNow Enthusiast| CSA | CAD |ServiceNow Community Rising Star 2023-24
9 个月Well explained. Thanks for sharing.
ServiceNow Architect & Mentor | 4x MVP (2022-24) | Author of "Think Thrice Code Once" | Certified CTA, CIS, CAD, CSA | ITSM & Agile Expert | Empowering Professionals Through Technomonk
1 年Great article! Scripted REST API in ServiceNow allows for customization and flexibility in presenting data to third parties. Thanks for sharing!