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
- //Reference from Vardhman Despande blogs https://www.vrdmn.com/2016/06/sharepoint-online-get-userprofile.html
- //Reference from https://github.com/andrewconnell/sp-o365-rest/blob/master/SpRestBatchSample/Scripts/App.js
- //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)"]
- var BatchUtils = (() => {
- /**
- * Build the batch request for each property individually
- * @param endPointsToGet
- * @param boundryString
- */
- function buildBatchRequestBody(endPointsToGet, boundryString) {
- var propData = new Array();
- for (var i = 0; i < endPointsToGet.length; i++) {
- var getPropRESTUrl = endPointsToGet[i];
- propData.push('--batch_' + boundryString);
- propData.push('Content-Type: application/http');
- propData.push('Content-Transfer-Encoding: binary');
- propData.push('');
- propData.push('GET ' + getPropRESTUrl + ' HTTP/1.1');
- propData.push('Accept: application/json;odata=verbose');
- propData.push('');
- }
- return propData.join('\r\n');
- }
- function BuildChangeSetRequestBody(changeSetId, action, endpoint, items) {
- var batchContents = [];
- let item;
- // create the changeset
- for (let i = 0; i < items.length; i++) {
- item = items[i].data;
- //action=items[i].action;
- //TODO -Need to test for different action
- endpoint = items[i].reqUrl;
- batchContents.push("--changeset_" + changeSetId);
- batchContents.push("Content-Type: application/http");
- batchContents.push("Content-Transfer-Encoding: binary");
- batchContents.push("");
- if (action === "UPDATE") {
- batchContents.push("PATCH " + endpoint + " HTTP/1.1");
- batchContents.push("If-Match: *");
- batchContents.push("Content-Type: application/json;odata=verbose");
- batchContents.push("");
- batchContents.push(JSON.stringify(item));
- } else if (action === "ADD") {
- batchContents.push("POST " + endpoint + " HTTP/1.1");
- //For Insert it will return created value in json format
- batchContents.push('Accept: application/json;odata=verbose');
- batchContents.push("Content-Type: application/json;odata=verbose");
- batchContents.push("");
- batchContents.push(JSON.stringify(item));
- } else if (action === "DELETE") {
- batchContents.push("DELETE " + endpoint + " HTTP/1.1");
- batchContents.push("If-Match: *");
- }
- //Commented POST request line and added code for UPDATE as well
- // batchContents.push("POST " + endpoint + " HTTP/1.1");
- // batchContents.push("Content-Type: application/json;odata=verbose");
- // batchContents.push("");
- // batchContents.push(JSON.stringify(_item));
- batchContents.push("");
- // // END changeset to create data
- // batchContents.push("--changeset_" + changeSetId + "--");
- }
- // END changeset to create data
- batchContents.push("--changeset_" + changeSetId + "--");
- // batch body
- return batchContents.join("\r\n");
- }
- let BuildChangeSetRequestHeader = (batchGuid, changeSetId, batchBody) => {
- let batchContents = [];
- // create batch for creating items
- batchContents.push("--batch_" + batchGuid);
- batchContents.push(
- 'Content-Type: multipart/mixed; boundary="changeset_' +
- changeSetId +
- '"'
- );
- batchContents.push("Content-Length: " + batchBody.length);
- batchContents.push("Content-Transfer-Encoding: binary");
- batchContents.push("");
- batchContents.push(batchBody);
- batchContents.push("");
- // create request in batch to get all items after all are created
- ////Commented below endpoint as we are utilizing same endpoint without orderby
- // endpoint = _this.rootUrl +
- // "/_api/web/lists/getbytitle('" + listName + "')" +
- // '/items?$orderby=Title';
- // batchContents.push('--batch_' + batchGuid);
- // batchContents.push('Content-Type: application/http');
- // batchContents.push('Content-Transfer-Encoding: binary');
- // batchContents.push('');
- //COmmented below lines of code as I don't need to request GET after insertion
- // batchContents.push('GET ' + endpoint + ' HTTP/1.1');
- // batchContents.push('Accept: application/json;odata=verbose');
- // batchContents.push('');
- batchContents.push("--batch_" + batchGuid + "--");
- return batchContents.join("\r\n");
- }
- /**
- * Build the batch header containing the user profile data as the batch body
- * @param userPropsBatchBody
- * @param boundryString
- */
- function buildBatchRequestHeader(userPropsBatchBody, boundryString) {
- var headerData = [];
- headerData.push('Content-Type: multipart/mixed; boundary="batch__' + boundryString + '"');
- headerData.push('Content-Length: ' + userPropsBatchBody.length);
- headerData.push('Content-Transfer-Encoding: binary');
- headerData.push('');
- headerData.push(userPropsBatchBody);
- headerData.push('');
- headerData.push('--batch_' + boundryString + '--');
- return headerData.join('\r\n');
- }
- /**
- * Parse Batch Get Response
- * @param batchResponse
- */
- function parseResponse(batchResponse) {
- //Extract the results back from the BatchResponse
- var results = grep(batchResponse.split("\r\n"), function (responseLine) {
- try {
- return responseLine.indexOf("{") != -1 && typeof JSON.parse(responseLine) == "object";
- }
- catch (ex) { /*adding the try catch loop for edge cases where the line contains a { but is not a JSON object*/ }
- }, null);
- //Convert JSON strings to JSON objects
- return results.map(function (result) {
- return JSON.parse(result);
- });
- }
- /**
- * Copied grep function from jQuery JavaScript Library v1.11.3
- * @param elems
- * @param callback
- * @param invert
- */
- var grep = function (elems, callback, invert) {
- var callbackInverse,
- matches = [],
- i = 0,
- length = elems.length,
- callbackExpect = !invert;
- // Go through the array, only saving the items
- // that pass the validator function
- for (; i < length; i++) {
- callbackInverse = !callback(elems[i], i);
- if (callbackInverse !== callbackExpect) {
- matches.push(elems[i]);
- }
- }
- return matches;
- };
- /**
- * Get Uniquie boundry string for batch request identifier
- */
- function getBoundryString() {
- return "vrd_" + Math.random().toString(36).substr(2, 9);
- }
- var makeBatchRequests = ({ rootUrl, batchUrls, FormDigestValue }) => {
- if (FormDigestValue) {
- return internalBatch({ FormDigestValue: FormDigestValue, rootUrl, batchUrls })
- } else {
- return fetch(`${rootUrl}/_api/contextinfo`, {
- method: "POST",
- "headers": { "Accept": "application/json;odata=verbose", credentials: "include", }
- }).then(r => r.json()).then(r => {
- return internalBatch({ FormDigestValue: r.d.GetContextWebInformation.FormDigestValue, rootUrl, batchUrls })
- });
- }
- }
- var makePostBatchRequests = ({ rootUrl, batchUrls, FormDigestValue }) => {
- if (FormDigestValue) {
- return internalPostBatch({ FormDigestValue: FormDigestValue, rootUrl, batchUrls })
- } else {
- return fetch(`${rootUrl}/_api/contextinfo`, {
- method: "POST",
- "headers": { "Accept": "application/json;odata=verbose", credentials: "include", }
- }).then(r => r.json()).then(r => {
- return internalPostBatch({ FormDigestValue: r.d.GetContextWebInformation.FormDigestValue, rootUrl, batchUrls })
- });
- }
- }
- var internalPostBatch = ({ FormDigestValue, rootUrl, batchUrls }) => {
- //Reference from Vardhman Despande blogs https://www.vrdmn.com/2016/06/sharepoint-online-get-userprofile.html
- //AccountName of the user
- // var userAccountName = encodeURIComponent("i:0#.f|membership|[email protected]");
- //Collection of Endpoint url to fetch
- 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)"]
- //Unique identifier, will be used to delimit different parts of the request body
- var boundryString = getBoundryString();
- //Unique identifier, will be used to delimit different parts of the request body
- var changeSetIdString = getBoundryString();
- //Build Body of the Batch Request
- var userPropertiesBatchBody = BuildChangeSetRequestBody(changeSetIdString, "ADD", "", endpointsArray);
- //Build Header of the Batch Request
- var batchRequestBody = BuildChangeSetRequestHeader(boundryString, changeSetIdString, userPropertiesBatchBody);
- //Make the REST API call to the _api/$batch endpoint with the batch data
- console.log("==========================================================")
- console.log(userPropertiesBatchBody)
- console.log("==========================================================")
- console.log(batchRequestBody)
- console.log("==========================================================")
- var requestHeaders = {
- credentials: "include",
- 'X-RequestDigest': FormDigestValue, //r.d.GetContextWebInformation.FormDigestValue,
- 'Content-Type': `multipart/mixed; boundary="batch_${boundryString}"`
- };
- return fetch(`${rootUrl}/_api/$batch`, {
- method: "POST",
- headers: requestHeaders,
- body: batchRequestBody
- }).then(r => r.text()).then(r => {
- //Convert the text response to an array containing JSON objects of the results
- var results = parseResponse(r);
- //Properties will be returned in the same sequence they were added to the batch request
- // for (var i = 0; i < endpointsArray.length; i++) {
- // console.log(endpointsArray[i] + " is ", results[i]);
- // }
- return results;
- })
- }
- var internalBatch = ({ FormDigestValue, rootUrl, batchUrls }) => {
- //Reference from Vardhman Despande blogs https://www.vrdmn.com/2016/06/sharepoint-online-get-userprofile.html
- //AccountName of the user
- // var userAccountName = encodeURIComponent("i:0#.f|membership|[email protected]");
- //Collection of Endpoint url to fetch
- 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)"]
- //Unique identifier, will be used to delimit different parts of the request body
- var boundryString = getBoundryString();
- //Build Body of the Batch Request
- var userPropertiesBatchBody = buildBatchRequestBody(endpointsArray, boundryString);
- //Build Header of the Batch Request
- var batchRequestBody = buildBatchRequestHeader(userPropertiesBatchBody, boundryString);
- //Make the REST API call to the _api/$batch endpoint with the batch data
- var requestHeaders = {
- credentials: "include",
- 'X-RequestDigest': FormDigestValue, //r.d.GetContextWebInformation.FormDigestValue,
- 'Content-Type': `multipart/mixed; boundary="batch_${boundryString}"`
- };
- return fetch(`${rootUrl}/_api/$batch`, {
- method: "POST",
- headers: requestHeaders,
- body: batchRequestBody
- }).then(r => r.text()).then(r => {
- //Convert the text response to an array containing JSON objects of the results
- var results = parseResponse(r);
- //Properties will be returned in the same sequence they were added to the batch request
- // for (var i = 0; i < endpointsArray.length; i++) {
- // console.log(endpointsArray[i] + " is ", results[i]);
- // }
- return results;
- })
- }
- return { GetBatchAll: makeBatchRequests, PostBatchAll: makePostBatchRequests };
- })();
How to use this Batch Utility for making Batch API Requests in SharePoint Online
Step 1
Prepare an array of Request URLs.
- 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)"]
Step 2
Pass the rootUrl or SiteUrl to generate RequestDigest.
- var rootUrl= "https://brgrp.sharepoint.com"
Pass the information as below and that's it. It will return the result or Request.
- BatchUtils.GetBatchAll({rootUrl:rootUrl,batchUrls:arr}) .then(r=>console.log(r))
You can see an example of response as per the below request.

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.

Ano MepaniPosted Dec 16, 2020, 12:43 PM
I suggesting you to use updated and easy spohelper utility https://anomepani.github.io/posts/spohelper-sharepoint-online-rest-api-crud-operation-utility-with-example/
Sebastian RamirezPosted Dec 16, 2020, 7:16 AM
Hello, I solved the problem, however when I sent an update request the server retutrn says that parameter __metadata does not exist in function GetById
Sebastian RamirezPosted Dec 15, 2020, 9:58 PM
Could you explainme how to reference the file in typescript? when I call it, this error appears: Uncaught ReferenceError: BatchUtils is not defined
Sebastian RamirezPosted Dec 15, 2020, 9:27 PM
It is requesting a formdigest param, help
Krishnakumar MPosted Jul 19, 2020, 2:40 AM
It was a very useful article did my job very easily. Thank you for Sharing