Introduction

In this article, we will learn about making SharePoint Online Batch Request Call with a few lines of code using Batch Utils which I have created.

Description

For easy SharePoint List Item CRUD Operations, I have created an SPRest utility. You can find a detailed article on how to use SPRest in development over here.
If you are working with SharePoint Online, then you definitely have used SharePoint REST API Call to get data with multiple requests from the same site collection.
So, as per the client requirement, you may have to request 5, 10, 15, or any number of AJAX requests to fulfill the requirement. By doing so, you may be stuck with a performance issue for a large number of requests.
If you are using SharePoint Online, SP 2016, or SP 2019 then there is good news. Microsoft has introduced OData $batch API support for REST Call.
Using $batch REST API Call, you can make up to 100 HTTP Requests with Single Batch Request.
There are limited articles and blogs which describe how to use $batch API in SharePoint Online. I have found difficulty in configuring or writing $batch REST Call.
For developers, I have created this SharePoint Batch Utility which is easy to integrate and easy to use for the Get operation. The code is properly exaplined via commented lines.
BatchUtils.ts
  1. //Reference from Vardhman Despande blogs https://www.vrdmn.com/2016/06/sharepoint-online-get-userprofile.html
  2. //Reference from https://github.com/andrewconnell/sp-o365-rest/blob/master/SpRestBatchSample/Scripts/App.js
  3. //var arr =["https://brgrp.sharepoint.com/_api/Lists/Getbytitle('PlaceHolderList')/items(212)", "https://brgrp.sharepoint.com/_api/Lists/Getbytitle('PlaceHolderList')/items(213)", "https://brgrp.sharepoint.com/_api/Lists/Getbytitle('PlaceHolderList')/items(214)"]
  4. var BatchUtils = (() => {
  5. /**
  6. * Build the batch request for each property individually
  7. * @param endPointsToGet
  8. * @param boundryString
  9. */
  10. function buildBatchRequestBody(endPointsToGet, boundryString) {
  11. var propData = new Array();
  12. for (var i = 0; i < endPointsToGet.length; i++) {
  13. var getPropRESTUrl = endPointsToGet[i];
  14. propData.push('--batch_' + boundryString);
  15. propData.push('Content-Type: application/http');
  16. propData.push('Content-Transfer-Encoding: binary');
  17. propData.push('');
  18. propData.push('GET ' + getPropRESTUrl + ' HTTP/1.1');
  19. propData.push('Accept: application/json;odata=verbose');
  20. propData.push('');
  21. }
  22. return propData.join('\r\n');
  23. }
  24. function BuildChangeSetRequestBody(changeSetId, action, endpoint, items) {
  25. var batchContents = [];
  26. let item;
  27. // create the changeset
  28. for (let i = 0; i < items.length; i++) {
  29. item = items[i].data;
  30. //action=items[i].action;
  31. //TODO -Need to test for different action
  32. endpoint = items[i].reqUrl;
  33. batchContents.push("--changeset_" + changeSetId);
  34. batchContents.push("Content-Type: application/http");
  35. batchContents.push("Content-Transfer-Encoding: binary");
  36. batchContents.push("");
  37. if (action === "UPDATE") {
  38. batchContents.push("PATCH " + endpoint + " HTTP/1.1");
  39. batchContents.push("If-Match: *");
  40. batchContents.push("Content-Type: application/json;odata=verbose");
  41. batchContents.push("");
  42. batchContents.push(JSON.stringify(item));
  43. } else if (action === "ADD") {
  44. batchContents.push("POST " + endpoint + " HTTP/1.1");
  45. //For Insert it will return created value in json format
  46. batchContents.push('Accept: application/json;odata=verbose');
  47. batchContents.push("Content-Type: application/json;odata=verbose");
  48. batchContents.push("");
  49. batchContents.push(JSON.stringify(item));
  50. } else if (action === "DELETE") {
  51. batchContents.push("DELETE " + endpoint + " HTTP/1.1");
  52. batchContents.push("If-Match: *");
  53. }
  54. //Commented POST request line and added code for UPDATE as well
  55. // batchContents.push("POST " + endpoint + " HTTP/1.1");
  56. // batchContents.push("Content-Type: application/json;odata=verbose");
  57. // batchContents.push("");
  58. // batchContents.push(JSON.stringify(_item));
  59. batchContents.push("");
  60. // // END changeset to create data
  61. // batchContents.push("--changeset_" + changeSetId + "--");
  62. }
  63. // END changeset to create data
  64. batchContents.push("--changeset_" + changeSetId + "--");
  65. // batch body
  66. return batchContents.join("\r\n");
  67. }
  68. let BuildChangeSetRequestHeader = (batchGuid, changeSetId, batchBody) => {
  69. let batchContents = [];
  70. // create batch for creating items
  71. batchContents.push("--batch_" + batchGuid);
  72. batchContents.push(
  73. 'Content-Type: multipart/mixed; boundary="changeset_' +
  74. changeSetId +
  75. '"'
  76. );
  77. batchContents.push("Content-Length: " + batchBody.length);
  78. batchContents.push("Content-Transfer-Encoding: binary");
  79. batchContents.push("");
  80. batchContents.push(batchBody);
  81. batchContents.push("");
  82. // create request in batch to get all items after all are created
  83. ////Commented below endpoint as we are utilizing same endpoint without orderby
  84. // endpoint = _this.rootUrl +
  85. // "/_api/web/lists/getbytitle('" + listName + "')" +
  86. // '/items?$orderby=Title';
  87. // batchContents.push('--batch_' + batchGuid);
  88. // batchContents.push('Content-Type: application/http');
  89. // batchContents.push('Content-Transfer-Encoding: binary');
  90. // batchContents.push('');
  91. //COmmented below lines of code as I don't need to request GET after insertion
  92. // batchContents.push('GET ' + endpoint + ' HTTP/1.1');
  93. // batchContents.push('Accept: application/json;odata=verbose');
  94. // batchContents.push('');
  95. batchContents.push("--batch_" + batchGuid + "--");
  96. return batchContents.join("\r\n");
  97. }
  98. /**
  99. * Build the batch header containing the user profile data as the batch body
  100. * @param userPropsBatchBody
  101. * @param boundryString
  102. */
  103. function buildBatchRequestHeader(userPropsBatchBody, boundryString) {
  104. var headerData = [];
  105. headerData.push('Content-Type: multipart/mixed; boundary="batch__' + boundryString + '"');
  106. headerData.push('Content-Length: ' + userPropsBatchBody.length);
  107. headerData.push('Content-Transfer-Encoding: binary');
  108. headerData.push('');
  109. headerData.push(userPropsBatchBody);
  110. headerData.push('');
  111. headerData.push('--batch_' + boundryString + '--');
  112. return headerData.join('\r\n');
  113. }
  114. /**
  115. * Parse Batch Get Response
  116. * @param batchResponse
  117. */
  118. function parseResponse(batchResponse) {
  119. //Extract the results back from the BatchResponse
  120. var results = grep(batchResponse.split("\r\n"), function (responseLine) {
  121. try {
  122. return responseLine.indexOf("{") != -1 && typeof JSON.parse(responseLine) == "object";
  123. }
  124. catch (ex) { /*adding the try catch loop for edge cases where the line contains a { but is not a JSON object*/ }
  125. }, null);
  126. //Convert JSON strings to JSON objects
  127. return results.map(function (result) {
  128. return JSON.parse(result);
  129. });
  130. }
  131. /**
  132. * Copied grep function from jQuery JavaScript Library v1.11.3
  133. * @param elems
  134. * @param callback
  135. * @param invert
  136. */
  137. var grep = function (elems, callback, invert) {
  138. var callbackInverse,
  139. matches = [],
  140. i = 0,
  141. length = elems.length,
  142. callbackExpect = !invert;
  143. // Go through the array, only saving the items
  144. // that pass the validator function
  145. for (; i < length; i++) {
  146. callbackInverse = !callback(elems[i], i);
  147. if (callbackInverse !== callbackExpect) {
  148. matches.push(elems[i]);
  149. }
  150. }
  151. return matches;
  152. };
  153. /**
  154. * Get Uniquie boundry string for batch request identifier
  155. */
  156. function getBoundryString() {
  157. return "vrd_" + Math.random().toString(36).substr(2, 9);
  158. }
  159. var makeBatchRequests = ({ rootUrl, batchUrls, FormDigestValue }) => {
  160. if (FormDigestValue) {
  161. return internalBatch({ FormDigestValue: FormDigestValue, rootUrl, batchUrls })
  162. } else {
  163. return fetch(`${rootUrl}/_api/contextinfo`, {
  164. method: "POST",
  165. "headers": { "Accept": "application/json;odata=verbose", credentials: "include", }
  166. }).then(r => r.json()).then(r => {
  167. return internalBatch({ FormDigestValue: r.d.GetContextWebInformation.FormDigestValue, rootUrl, batchUrls })
  168. });
  169. }
  170. }
  171. var makePostBatchRequests = ({ rootUrl, batchUrls, FormDigestValue }) => {
  172. if (FormDigestValue) {
  173. return internalPostBatch({ FormDigestValue: FormDigestValue, rootUrl, batchUrls })
  174. } else {
  175. return fetch(`${rootUrl}/_api/contextinfo`, {
  176. method: "POST",
  177. "headers": { "Accept": "application/json;odata=verbose", credentials: "include", }
  178. }).then(r => r.json()).then(r => {
  179. return internalPostBatch({ FormDigestValue: r.d.GetContextWebInformation.FormDigestValue, rootUrl, batchUrls })
  180. });
  181. }
  182. }
  183. var internalPostBatch = ({ FormDigestValue, rootUrl, batchUrls }) => {
  184. //Reference from Vardhman Despande blogs https://www.vrdmn.com/2016/06/sharepoint-online-get-userprofile.html
  185. //AccountName of the user
  186. // var userAccountName = encodeURIComponent("i:0#.f|membership|[email protected]");
  187. //Collection of Endpoint url to fetch
  188. var endpointsArray = batchUrls;//["https://brgrp.sharepoint.com/_api/Lists/Getbytitle('PlaceHolderList')/items(212)", "https://brgrp.sharepoint.com/_api/Lists/Getbytitle('PlaceHolderList')/items(213)", "https://brgrp.sharepoint.com/_api/Lists/Getbytitle('PlaceHolderList')/items(214)"]
  189. //Unique identifier, will be used to delimit different parts of the request body
  190. var boundryString = getBoundryString();
  191. //Unique identifier, will be used to delimit different parts of the request body
  192. var changeSetIdString = getBoundryString();
  193. //Build Body of the Batch Request
  194. var userPropertiesBatchBody = BuildChangeSetRequestBody(changeSetIdString, "ADD", "", endpointsArray);
  195. //Build Header of the Batch Request
  196. var batchRequestBody = BuildChangeSetRequestHeader(boundryString, changeSetIdString, userPropertiesBatchBody);
  197. //Make the REST API call to the _api/$batch endpoint with the batch data
  198. console.log("==========================================================")
  199. console.log(userPropertiesBatchBody)
  200. console.log("==========================================================")
  201. console.log(batchRequestBody)
  202. console.log("==========================================================")
  203. var requestHeaders = {
  204. credentials: "include",
  205. 'X-RequestDigest': FormDigestValue, //r.d.GetContextWebInformation.FormDigestValue,
  206. 'Content-Type': `multipart/mixed; boundary="batch_${boundryString}"`
  207. };
  208. return fetch(`${rootUrl}/_api/$batch`, {
  209. method: "POST",
  210. headers: requestHeaders,
  211. body: batchRequestBody
  212. }).then(r => r.text()).then(r => {
  213. //Convert the text response to an array containing JSON objects of the results
  214. var results = parseResponse(r);
  215. //Properties will be returned in the same sequence they were added to the batch request
  216. // for (var i = 0; i < endpointsArray.length; i++) {
  217. // console.log(endpointsArray[i] + " is ", results[i]);
  218. // }
  219. return results;
  220. })
  221. }
  222. var internalBatch = ({ FormDigestValue, rootUrl, batchUrls }) => {
  223. //Reference from Vardhman Despande blogs https://www.vrdmn.com/2016/06/sharepoint-online-get-userprofile.html
  224. //AccountName of the user
  225. // var userAccountName = encodeURIComponent("i:0#.f|membership|[email protected]");
  226. //Collection of Endpoint url to fetch
  227. var endpointsArray = batchUrls;//["https://brgrp.sharepoint.com/_api/Lists/Getbytitle('PlaceHolderList')/items(212)", "https://brgrp.sharepoint.com/_api/Lists/Getbytitle('PlaceHolderList')/items(213)", "https://brgrp.sharepoint.com/_api/Lists/Getbytitle('PlaceHolderList')/items(214)"]
  228. //Unique identifier, will be used to delimit different parts of the request body
  229. var boundryString = getBoundryString();
  230. //Build Body of the Batch Request
  231. var userPropertiesBatchBody = buildBatchRequestBody(endpointsArray, boundryString);
  232. //Build Header of the Batch Request
  233. var batchRequestBody = buildBatchRequestHeader(userPropertiesBatchBody, boundryString);
  234. //Make the REST API call to the _api/$batch endpoint with the batch data
  235. var requestHeaders = {
  236. credentials: "include",
  237. 'X-RequestDigest': FormDigestValue, //r.d.GetContextWebInformation.FormDigestValue,
  238. 'Content-Type': `multipart/mixed; boundary="batch_${boundryString}"`
  239. };
  240. return fetch(`${rootUrl}/_api/$batch`, {
  241. method: "POST",
  242. headers: requestHeaders,
  243. body: batchRequestBody
  244. }).then(r => r.text()).then(r => {
  245. //Convert the text response to an array containing JSON objects of the results
  246. var results = parseResponse(r);
  247. //Properties will be returned in the same sequence they were added to the batch request
  248. // for (var i = 0; i < endpointsArray.length; i++) {
  249. // console.log(endpointsArray[i] + " is ", results[i]);
  250. // }
  251. return results;
  252. })
  253. }
  254. return { GetBatchAll: makeBatchRequests, PostBatchAll: makePostBatchRequests };
  255. })();
You can reference this file in your project. This will also work with Spfx and typescript codebase as well.

How to use this Batch Utility for making Batch API Requests in SharePoint Online

Step 1
Prepare an array of Request URLs.
  1. var arr=[
  2. "https://brgrp.sharepoint.com/_api/Lists/Getbytitle('PlaceHolderList')/items(212)" ,
  3. "https://brgrp.sharepoint.com/_api/Lists/Getbytitle('PlaceHolderList')/items(213)" ,
  4. "https://brgrp.sharepoint.com/_api/Lists/Getbytitle('PlaceHolderList')/items(214)"]
Step 2
Pass the rootUrl or SiteUrl to generate RequestDigest.
  1. var rootUrl= "https://brgrp.sharepoint.com"
Step 3
Pass the information as below and that's it. It will return the result or Request.
  1. BatchUtils.GetBatchAll({rootUrl:rootUrl,batchUrls:arr}) .then(r=>console.log(r))
You can see an example of response as per the below request.
Make SharePoint Online Batch API Request Easy In Single Line Of Code
You can find BatchUtils.ts on GitHub as well.

Conclusion

In this article, we have learned about the usage of SharePoint $batch REST API easily using BatchUtils.