Learn MOAR with Winter ’21: Composite Graph API

Follow and complete a Learn MOAR Winter ’21 for Admins or Developers trailmix by October 30 to earn a special community badge and enter for a chance to win one $200 USD Trailhead Cert voucher.

Make more powerful composite requests with the Composite Graph API

If you’re familiar with the Composite API, you know that it’s a REST API that gives you the ability to execute multiple API requests within a single call. It is great for querying and performing CRUD operations on related records. For example, you can query for an account owner, then create an account and its contacts in a single API request.

To use the API, you construct the request body in a JSON format that can be processed by Salesforce. Each of the operations is specified within the collection of composite requests within the body. Taking the E-Bikes Sample App as an example, an Account, an Order, and one or more Order Line Items can make up a single transaction.

{
    "allOrNone": true,
    "compositeRequest": [
        // Operation 1 - Upsert Account
        {
            "method": "PATCH",
            "url": "/services/data/v49.0/sobjects/Account/ExternalAcctId__c/ID12345",
            "referenceId": "Account",
            "body": {
                "Name": "Trailblazers",
                "Website": "TrailblazerOutfiters.com"
            }
        },
        // Operation 2 - Insert a new order
        {
            "method": "POST",
            "url": "/services/data/v49.0/sobjects/Order__c",
            "referenceId": "newOrder",
            "body": {
                "Account__c": "@{Account.id}"
            }
        },
        // Operation 3 - Query for Product Id
        {
            "method": "GET",
            "url": "/services/data/v49.0/query/?q=select+id+from+product__c+where+name='Dynamo X1'+limit+1",
            "referenceId": "Product"
        },
        // Operation 4 - Add product to the order
        {
            "method": "POST",
            "url": "/services/data/v49.0/sobjects/OrderItem__c",
            "referenceId": "newProduct",
            "body": {
                "Order__c": "@{newOrder.id}",
                "Product__c": "@{Product.records[0].Id}",
                "Qty_S__c": "1",
                "Price__c": "500"
            }
        }
    ]
}

Introducing the Composite Graph API

While the Composite API is already game-changing, you can take composite requests to the next level with the new Composite Graph API launching in Winter ‘21. The Composite Graph API is a huge leap forward when you need to perform CRUD operations on a large number of related sObjects. Not only does it increase the subrequest limit from 25 to 500, it also provides a number of optimizations that ensure records are processed efficiently and operations are rolled back if any steps are not completed.

Organize subrequests inside within a graph

The body of a Composite Graph API request consists of a number of graphs, each of which may contain multiple composite subrequests. You can think of each graph as its own grouping of related sObject records.

{
    "graphs": [
        {
            "graphId": "graphId",
            "compositeRequest": [
                compositeSubrequest1, 
                compositeSubrequest2, 
                ...]
        },
        {
            "graphId": "graphId2",
            "compositeRequest": [
                compositeSubrequest3, 
                compositeSubrequest4, 
                ...]
        }
    ]
}

Grouping collections of sObjects into graphs enables you to get much more done with a single API call. One big advantage of this is no longer having to manage the orchestration of the deeper, more intricate relationships that are inherent to more complex graphs. Taking the E-Bikes example from above, you can now construct multiple graphs of related sObjects to insert multiple orders within the same transaction.

In the following use case:

Graph 1

  • Upserts the Trailblazer’s account
  • Inserts multiple orders
  • Inserts products associated to the orders

Graph 2

  • Upserts the Universal Containers account
  • Inserts the main contact
  • Inserts their order
  • Inserts the product related to the order
  • Inserts a case related to the account, contact, and product
{
    "graphs": [
        {
            "graphId": "graph1",
            "compositeRequest": [
                {
                    "method": "PATCH",
                    "url": "/services/data/v49.0/sobjects/Account/ExternalAcctId__c/ID12345",
                    "referenceId": "newAccount",
                    "body": {
                        "Name": "Trailblazers",
                        "Website": "TrailblazerOutfiters.com"
                    }
                },
                {
                    "method": "POST",
                    "url": "/services/data/v49.0/sobjects/Order__c",
                    "referenceId": "newOrder1",
                    "body": {
                        "Account__c": "@{newAccount.id}"
                    }
                },
                {
                    "method": "POST",
                    "url": "/services/data/v49.0/sobjects/OrderItem__c",
                    "referenceId": "newProduct1",
                    "body": {
                        "Order__c": "@{newOrder1.id}",
                        "Product__c": {
                            "External_Id__c": "EB1213"
                        },
                        "Qty_L__c": "1",
                        "Price__c": "500"
                    }
                },
                {
                    "method": "POST",
                    "url": "/services/data/v49.0/sobjects/Order__c",
                    "referenceId": "newOrder2",
                    "body": {
                        "Account__c": "@{Account.id}"
                    }
                },
                {
                    "method": "POST",
                    "url": "/services/data/v49.0/sobjects/OrderItem__c",
                    "referenceId": "newProduct2",
                    "body": {
                        "Order__c": "@{newOrder2.id}",
                        "Product__c": {
                            "External_Id__c": "EB1213"
                        },
                        "Qty_L__c": "1",
                        "Price__c": "500"
                    }
                },
                                {
                    "method": "POST",
                    "url": "/services/data/v49.0/sobjects/Order__c",
                    "referenceId": "newOrder3",
                    "body": {
                        "Account__c": "@{Account.id}"
                    }
                },
                {
                    "method": "POST",
                    "url": "/services/data/v49.0/sobjects/OrderItem__c",
                    "referenceId": "newProduct3",
                    "body": {
                        "Order__c": "@{newOrder3.id}",
                        "Product__c": {
                            "External_Id__c": "EB1213"
                        },
                        "Qty_L__c": "1",
                        "Price__c": "500"
                    }
                }
            ]
        },
        {
            "graphId": "graph2",
            "compositeRequest": [
                {
                    "method": "PATCH",
                    "url": "/services/data/v49.0/sobjects/Account/ExternalAcctId__c/ID54321",
                    "referenceId": "newAccount",
                    "body": {
                        "Name": "Universal Containers",
                        "Website": "UniversalContainers.com"
                    }
                },
                {
                    "method": "POST",
                    "url": "/services/data/v49.0/sobjects/Contact",
                    "referenceId": "newContact",
                    "body": {
                        "First": "Laura",
                        "Last": "Smith",
                        "Email": "UniversalContainers.com",
                        "AccountId" : "@{newAccount.id}"
                    }
                },
                {
                    "method": "POST",
                    "url": "/services/data/v49.0/sobjects/Order__c",
                    "referenceId": "newOrder",
                    "body": {
                        "Account__c": "@{newAccount.id}"
                    }
                },
                {
                    "method": "POST",
                    "url": "/services/data/v49.0/sobjects/OrderItem__c",
                    "referenceId": "newProduct",
                    "body": {
                        "Order__c": "@{newOrder.id}",
                        "Product__c": {
                            "External_Id__c": "EB1895"
                        },
                        "Qty_L__c": "6",
                        "Price__c": "1000"
                    }
                },
                {
                    "method": "POST",
                    "url": "/services/data/v49.0/sobjects/Case",
                    "referenceId": "newProduct",
                    "body": {
                        "Account" : "@{newAccount.id}",
                        "Contact" : "@{newContact.id}",
                        "Order__c" : "@{newOrder.Id}",
                        "Subject" : "New Product Order Setup"
                }
            ]
        }
    ]
}

As with the Composite API, each subrequest contains a referenceId that can be used to relate records that follow the subrequest. In the example below, the Account record is upserted and the referenceId is set to Account. In the subrequest that follows, an order record is inserted and the Account__c field value is set to @{Account.Id} , which references the account that was just inserted.

{
            "graphId": "graph1",
            "compositeRequest": [
                {
                    "method": "PATCH",
                    "url": "/services/data/v49.0/sobjects/Account/ExternalAcctId__c/ID12345",
                    // Reference Id used to relate records to the account
                    "referenceId": "Account",
                    "body": {
                        "Name": "Trailblazers",
                        "Website": "TrailblazerOutfiters.com"
                    }
                },
                {
                    "method": "POST",
                    "url": "/services/data/v49.0/sobjects/Order__c",
                    // Reference Id used to relate records to the order                   
                    "referenceId": "newOrder",                    
                    "body": {
                        "Account__c": "@{Account.id}"
                    }
                },
                {
                    "method": "POST",
                    "url": "/services/data/v49.0/sobjects/OrderItem__c",
                    "referenceId": "newProduct",
                    "body": {
                        "Order__c": "@{newOrder.id}",
                        "Product__c": {
                            "External_Id__c": "EB1213"
                        },
                        "Qty_L__c": "1",
                        "Price__c": "500"
                    }
                }
            ]
        },

Getting started

Getting started with the Composite Graph API is easy; you can follow along with the Composite Graph Documentation. You can also check out the great session on Doing More With Less Code Using Salesforce APIs from TrailheaDX to hear the latest about Salesforce APIs and see a demo of the Composite Graph API.

Resources
Composite Graph Release Notes
Composite Resource Documentation
Trailhead: Rest API Basics

#LearnMOAR and share

Share what you love about the Winter ’21 Release with the #LearnMOAR hashtag. Also, don’t forget to sign up for Release Readiness Live happening on September 18, 2020.

About the author

Stephan Chandler-Garcia is a Senior Developer Evangelist at Salesforce. He focuses on application development, security, and communities. You can follow him on Twitter @stephanwcg.

No purchase necessary. Void where prohibited. Sweepstakes runs from September 14, 2020 12:00 PM PT to October 30, 2020 11:59 PM PT. Open to legal residents of the U.S. (incl. D.C.), Austria, Canada (excluding Quebec), Cyprus, France, Germany, Greece, Hungary, India, Ireland, Japan, Lithuania, Latvia, Luxembourg, Netherlands, New Zealand, Norway,
Spain, Switzerland, Ukraine, and the U.K. Must be 18+ (20+ in Japan). See Official Rules.