AEM with GraphQL

The future for AEM Headless?

Rest - GraphQL: A burger comparison

What is GraphQL?

The most popular image to describe GraphQL is propably the burger image. Normaly the image shows a smaller burger for GraphQL, because GraphQL is best used to request exactly what you need and nothing more. But we are hungry, so we request a tripple burger - because we have the freedom to do it.

 

And that's the beauty about GraphQL compared to REST API: We are no longer sole depending on what the API gives us - we can create our own API (of course with limitations). So let's see how GraphQL can be used in AEM and what advantages it has over Assets API or the normal Sling Model Exporter way with components.

 

In AEM, GraphQL is only working with Content Fragments at the moment, which have a structure defined by the Content Fragment Models.

Quick Setup in AEM

Let's start with the basic setup to see how simple the configuration is.

 

Prerequisite

  • You need AEM as a Cloud Service or at least AEM Version 6.5.10 or higher
  • A project under Tools > General > Configuration Browser is created (in our case it's called "my-project")
  • A Content Fragment Model and a Content Fragment is avialable under this project

 

1) Define a GraphQL endpoint under Tools > Assets > GraphQL

Tools - Assets - GraphQL
create endpoint for graphql

2) Define the GraphQL Properties for your Content Fragment Model under Tools > Assets > Content Fragment Models

graphql content fragment properties

3) That's bascally it. Now you can already open a Content Fragment of that Model and you should see the GraphQL JSON output structure. If you open the network tab in the development console of your browser, you can see the GraphQL request with the query.

graphql json output of content fragment

Create your first GraphQL Query

For testing purposes, we send our first request to the AEM author. You need to send a post request with authentication header to the endpoint address on author instance. For example "/content/_cq_graphql/my-project/endpoint.json" for endpoint "my-project"

The body of the request contains the query. For example a query to request a single article with headline and text.

{
  articleByPath(_path: "/content/dam/my-project/articles/our-test-article") {
    item {
      headline
      content {
          plaintext
      }
    }
  }
}

The result will look like this

{
    "data": {
        "articleByPath": {
            "item": {
                "headline": "Our Test Article",
                "content": {
                    "plaintext": "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum."
                }
            }
        }
    }
}

Now if we want to request also the images of the article we send this request

{
  articleByPath(_path: "/content/dam/my-project/articles/our-test-article") {
    item {
      headline
      content {
          plaintext
      }
      images {
        ... on ImageRef{
            type 
            _path 
            _authorUrl 
            _publishUrl 
            width 
            height 
            mimeType
        }
      }
    }
  }
}

And get this result

{
    "data": {
        "articleByPath": {
            "item": {
                "headline": "Test Headline",
                "content": {
                    "plaintext": "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum."
                },
                "images": [
                    {
                        "type": "image",
                        "_path": "/content/dam/my-project/articles/our-test-article/image1.jpg",
                        "_authorUrl": "http://localhost:4502/content/dam/my-project/articles/our-test-article/image1.jpg",
                        "_publishUrl": "http://localhost:4503/content/dam/my-project/articles/our-test-article/image1.jpg",
                        "width": 1284,
                        "height": 1605,
                        "mimeType": "image/jpeg"
                    },
                    {
                        "type": "image",
                        "_path": "/content/dam/my-project/articles/our-test-article/image2.jpg",
                        "_authorUrl": "http://localhost:4502/content/dam/my-project/articles/our-test-article/image2.jpg",
                        "_publishUrl": "http://localhost:4503/content/dam/my-project/articles/our-test-article/image2.jpg",
                        "width": 1284,
                        "height": 1605,
                        "mimeType": "image/jpeg"
                    }
                ]
            }
        }
    }
}

Security and caching with Persistent Queries

We now have explored how we can send a POST request with a GraphQL query to AEM author, but how can we secure this query to not give more information to the public than allowed and also cache the result?

 

To achieve this we need to use the so called Persistent Queries.

After preparing a query with a POST request, it can be persistet, so it can be requested with a GET request that can be cached by HTTP caches or a Content Delivery Network (CDN). This is required, as POST queries are usually not cached, and giving out all the schema information public is not recommended.

 

Create Persistent Queries

1) Enable "GraphQL Persistent Queries" for your project/configuration under General > Configuration Browser > "my-project" > Properties

graphql persistent queries property in configuration

2) Persist the query by sending a PUT request to the desired endpoint with this URL

  • /graphql/persist.json/my-project/get-simple-article

 

("get-simple-article" can be any name for your query)

 

Example Query with a parameter

query GetSimpleArticleByPath($apath: String!) {
  articleByPath(_path: $apath) {
    item {
      headline
      content {
          plaintext
      }
    }
  }
}

To update the query send a POST request. More on that can be found in the Adobe documentation

 

Now the query is stored under /conf/my-project and can also be included in a package or backup.

 

graphql persistent query in crx

The query can now be requested via GET request and the result will be cached depending on the dispatcher settings.

 

Use this URL for the GET request

  • /graphql/execute.json/my-project/get-simple-article;apath=%2Fcontent%2Fdam%2Fmy-project%2Ftest-article

 

Don't forget to replicate the query under /conf/my-project/settings/graphql/persistentQueries to make the persistent query available on the publish instance.

 

When to use GraphQL

 

We now have a basic understanding what GraphQL looks like and how it can be used in AEM. But as with all technologies it needs the right use case.

 

GraphQL offers many possiblities, and especially with Apps or Single Page Applications (SPA) it can bring the flexibility that most developers wish for.

 

Some other scenarios are

  • Dynamic web components
  • Rapid Prototyping 
  • Multiple output channels with different frontend technologies
  • Content Management Purposes in combination with Assets HTTP API

 

Example for SPA with GraphQL in AEM

https://github.com/adobe/aem-guides-wknd-graphql

 

Limitations

 

In the backend, AEM translates the GraphQL queries to SQL2 queries. In case we have complex GraphQL queries, we are fully depending on AEM to produce performant SQL2 queries for us. This can lead to slow performance, if not looked at carefully.

GraphQL in AEM is quite new and it brings a lot of new possibilites, especially for fast prototyping. For big data structures it can reach limitations in regards of how data can be requested.

 

The Alternatives for Headless Content in AEM

 

Assets HTTP API

 

The Assets API of AEM is a well established and quite powerfull API to request Content Fragments and other Assets as JSON Data. But the strong side of Assets API is the possibility to add, edit and delete data. When requesting data it is restricted to simple listing of folder contents or when requesting Content Fragments only the whole Content Fragment can requested and not parts of it.

 

In short, use Assets HTTP API if

 

  • You are fine getting all data, even data that you don't need
  • Caching is no big topic for you
  • You need an API to store, update, delete data and not only request it

 

Sling Model Exporter (Components/Experience Fragments)

 

This is the "normal" AEM way. You create pages on the pages components and then write your Sling Model Exporter java code to export the data that you need. You can also use the Adobe Core Components and include Content Fragments into your pages quite simple.

 

In short, use Sling Model Exporter if you

 

  • are fine with using "export pages" that need to be created and published separatly from the Content Fragments to update cache etc.
  • want to write and deploy code for custom/special requests

 

What the future holds

 

Adobe is pushing AEM with GraphQL and in the documentation we can read 

 

The AEM GraphQL API offers total control on the JSON output, and is an industry standard for querying content.
Moving forward, AEM is planning to invest in the AEM GraphQL API.

Source

 

This is a very good sign and we are excited what the future will bring for AEM Headless with GraphQL.

 

Further information

 

More information on GraphQL with AEM

https://experienceleague.adobe.com/docs/experience-manager-65/assets/extending/graphql-api-content-fragments.html?lang=en

 

Example Queries

https://experienceleague.adobe.com/docs/experience-manager-cloud-service/content/headless/graphql-api/sample-queries.html?lang=en