codeflood logo

Using the Experience Edge GraphQL API

Experience Edge is a SaaS offering from Sitecore which allows users to publish content and retrieve it via a globally available GraphQL API. The service takes on the burden of scalability so you don't have to. It provides a buffer between your content delivery applications (statically generated sites (SSG), server side rendered sites (SSR), kiosk applications, mobile applications, etc) and your headless CMS (Content Hub or Sitecore XM/XP) which allows the CMS to go offline for upgrades or maintenance without losing the ability to serve dynamic content.

There are 2 GraphQL APIs available for each source system; the Preview API and the Delivery API (XM docs: Delivery API, Content Hub docs: Delivery API). Only the Delivery API is exposed from Experience Edge itself. A Preview API is exposed by each source system. This post will be focusing on the Delivery API (the GraphQL API exposed by Experience Edge).

The Preview API provides access to unpublished and unapproved content, and is hosted from the source system directly. This API can be used to preview what content will look like once it's published to Experience Edge and the Delivery API is used. The Preview API is not designed to perform as well as the Delivery API and lacks the scalability and caching that the Delivery API contains.

Authentication

The Experience Edge REST APIs are all authenticated using JWT bearer authentication, which requires an OAuth client credentials flow to obtain the JWT before calling the API. There's a chance users will be calling the Delivery API from their live website to load dynamic content. In this case, JWT bearer authentication would not be secure, as you'd need to expose the client ID and client secret to the live website, allowing any public user to view the credentials. Additionally, the overhead of the OAuth client credentials flow is considered by some to be a little cumbersome. So we've used API keys to authentication to the Delivery API instead.

API keys are created using the Token API, which itself is authenticated using JWT bearer authentication. If you're using Content Hub as the source system, you can create API keys through the UI.

Create an API key

The Token API (XM docs: Token API, Content Hub docs: Token API) is used to create API keys. Specifically, the create endpoint. The Token API is a REST API and we need a JWT to call it.

So first things first, we need to perform an OAuth client credentials flow to obtain a JWT to call the API. Both the XM documentation (Request a JWT for Experience Edge XM using OAuth) and the Content Hub documentation (Generate a JSON Web Token (JWT)) include walkthroughs detailing how to do this. The documentation uses curl to make the web request to the OAuth authority. For my example, I'm going to use PowerShell 7. I've included the full command here, including the client credentials for my Edge tenant so you can see a full example. Don't worry, I've already deleted this Edge tenant, so these secrets are useless now.

Client credentials are sensitive data. You should never expose your own client credentials in this manner.

$body = ConvertTo-Json @{
    client_id = "H0mGjN3ZiL1u3f4AzVkD01FqlWOpqGex"
    client_secret = "O8UTj5ctmyfyW7OPhzLc4vht0ZK9KDaYw9K7tFd3hl9tWnNEftY0Cxh5jTyAPA-W"
    grant_type = "client_credentials"
    audience = "https://delivery.sitecore.cloud/adn-20220721"
}

$response = Invoke-RestMethod -Uri "https://auth.sitecorecloud.io/oauth/token" -Method Post `
    -ContentType "application/json" -Body $body
ConvertTo-Json $response

Invoking this command yields the following JSON response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InpnbnhyQk9IaXJ0WXp4dnl1WVhNZyJ9...",
  "expires_in": 86400,
  "token_type": "Bearer"
}

The JWT is contained in the access_token property. I've truncated the token in the above example so it formats nicely here.

Pay attention to the expires_in property of the response. This property contains the number of seconds before the token will expire and a new JWT will need to be obtained. You can cache the JWT for this duration.

Now I can call the Token API and create an API key. For this request I need to provide some details in the body.

  • CreatedBy: This is an identifier of the user creating the API key.
  • Label: This is a description about what the API key will be used for, to help differentiate API keys.
  • Scopes: These are used to control where the API key can be used, and which content the key is allowed to access.

I'll talk about scopes in more detail in another blog post. For now, we'll just add 2 scopes to the API key: content-#everything# so the API key can query for any content, and audience-delivery so the API key can be used on the Delivery API.

$body = ConvertTo-Json @{
    createdBy = "[email protected]"
    label = "Public website"
    scopes = ("content-#everything#", "audience-delivery")
}

$headers = @{
    Authorization = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InpnbnhyQk9IaXJ0WXp4dnl1WVhNZyJ9..."
}

Invoke-RestMethod -Uri "https://edge.sitecorecloud.io/api/apikey/v1" -Method Post `
    -ContentType "application/json" -Headers $headers -Body $body

Invoking this command will result in a new API key being created and it's token being returned in the response:

VzFyb0MyY3d3VkljR3NPd1VueHYzM2RsUTZhK0owK2VKa3BPNWFyOUZwWT18YWRuLTIwMjIwNzIx

Using the Content Hub UI

If you're using Content Hub as the source system, there is a UI available to create API keys. The Content Hub documentation Create an API key from the UI covers how to create API keys using the UI.

API Keys UI in Content Hub

To create an API key allowing access to all content, use the API Keys page on the settings page. This is documented in Create a key from the Manage page.

To create an API key which is limited to a Content Collection, create the API key from the menu of the Content Collection you want to limit the key to. This is documented in Create a key from a content collection.

Calling the Delivery API

Now that we have an API key, we can make a request to the Delivery API and start executing GraphQL queries. The GraphQL schema varies based on the system publishing into Experience Edge. For this example, I'm using a Sitecore XM tenant, so the GraphQL schema matches that of the GraphQL schema exposed by the Sitecore GraphQL API which was first introduced with JSS.

In the request, the API key is included in either the sc_apikey or X-GQL-Token headers.

$body = ConvertTo-Json @{
    query = '{
        item(language:"en", path:"/sitecore/content/home") {
            id
            name
            displayName
        }
    }'
}

$headers = @{
    sc_apikey = "VzFyb0MyY3d3VkljR3NPd1VueHYzM2RsUTZhK0owK2VKa3BPNWFyOUZwWT18YWRuLTIwMjIwNzIx"
}

$response = Invoke-RestMethod -Uri "https://edge.sitecorecloud.io/api/graphql/v1" -Method Post `
    -ContentType "application/json" -Headers $headers -Body $body
ConvertTo-Json $response

The response is JSON data with the structure matching that of the input query.

{
  "data": {
    "item": {
      "id": "110D559FDEA542EA9C1C8A5DF7E70EF9",
      "name": "Home",
      "displayName": "Home"
    }
  }
}

GraphQL best practices

GraphQL is quite a mature language and a number of best practices have evolved as people have embraced this technology. The following tips will help make your queries cleaner and easier to maintain.

Separate variables from the query

GraphQL queries can be parameterized by using variables, allowing you to easily adjust the values of arguments in the query without having the change the query text. This allows you to reuse the same query text verbatim across multiple queries.

Let's take the previous query I ran and separate out the item path as a variable path. First I need to define the variable at the start of the query. In my previous query exmaple, I ommitted the query keyord from the start of the query. When using variables however, we need to use the query keyword so we can define the variables as arguments to the query. Also note that in GraphQL a string type is defined using a capitol S. In the query I can reference this variable by preceding the variable name with a dollar sign: $path.

query($path: String) {
    item(language: "en", path: $path) {
        id
        name
        displayName
    }
}

Now when executing the query I also need to pass the variables in the body as a separate property.

$body = ConvertTo-Json @{
    query = 'query($path: String) {
        item(language: "en", path: $path) {
            id
            name
            displayName
        }
    }'
    variables = @{
        path = "/sitecore/content/home"
    }
}

$headers = @{
    sc_apikey = "VzFyb0MyY3d3VkljR3NPd1VueHYzM2RsUTZhK0owK2VKa3BPNWFyOUZwWT18YWRuLTIwMjIwNzIx"
}

$response = Invoke-RestMethod -Uri "https://edge.sitecorecloud.io/api/graphql/v1" -Method Post `
    -ContentType "application/json" -Headers $headers -Body $body
ConvertTo-Json $response

Use fragments

One great thing about GraphQL is you don't over fetch data you don't need. Unlike in a REST API where you retrieve an entire object, in GraphQL you specify the exact properties of the entities you want back. Although this makes things faster, it can become cumbersome when having to repeat common sets of property names. Let's consider the following GraphQL query:

query($path: String) {
  item(language: "en", path: $path) {
    id
    name
    template {
      name
    }
    children {
      results {
        id
        name
        template {
          name
        }
      }
    }
  }
}

See there how I've repeated the id, name and template.name properties? I can instead define a fragment which includes those properties:

fragment common on Item {
  id
  name
  template {
    name
  }
}

And then I can use that fragment in my query:

query($path: String) {
  item(language: "en", path: $path) {
    ...common
    children {
      results {
        ...common
      }
    }
  }
}

fragment common on Item {
  id
  name
  template {
    name
  }
}

Now if I wanted to fetch some additional specific common field for each of those items, I can simply update the fragment:

query($path: String) {
  item(language: "en", path: $path) {
    ...common
    children {
      results {
        ...common
      }
    }
  }
}

fragment common on Item {
  id
  name
  displayName
  template {
    name
    id
  }
}

Conclusion

Although these APIs are already documented for XM and Content Hub, it can often be helpful to have an additional explanation to demonstrate how to use the APIs. Hopefully I've given you enough information to get started using the Experience Edge GraphQL API.

Comments

Leave a comment

All fields are required.