How to deliver headless content through GraphQL API and Content Fragments? | AEM(Adobe Experience Manager)
Adobe Experience Manager (AEM) Content Fragments allow you to design, create, curate and publish page-independent content. They allow you to prepare content ready for use in multiple locations/over multiple channels.
GraphQL is a query language and server-side runtime for application programming interfaces (APIs) that prioritizes giving clients exactly the data they request and no more. GraphQL is designed to make APIs fast, flexible, and developer-friendly.
AEM as a Cloud Service and AEM 6.5.10.0+ version supports GraphQL API to expose the Content Fragment to enable the headless content experience. AEM as a Cloud Service and AEM 6.5.10.0 versions enable the GraphQL runtime platform to expose the Content Fragments through GraphQL API.
Using the GraphQL API in AEM enables the efficient delivery of Content Fragments to JavaScript clients in headless CMS implementations:
- Avoiding iterative API requests as with REST,
- Ensuring that delivery is limited to the specific requirements,
- Allowing for bulk delivery of exactly what is needed for rendering as the response to a single API query.
GraphiQL Interface:
AEM GraphQL implementation provides a standard GraphQL interface to directly input, and test queries. The interface should be installed separately, the interface can be downloaded from Adobe software-distribution - https://experience.adobe.com/#/downloads/content/software-distribution/en/aemcloud.html?package=/content/software-distribution/en/details.html/content/dam/aemcloud/public/aem-graphql/graphiql-0.0.6.zip, install the latest version of the interface
GraphiQL interface is bound to the global endpoint (and does not work with other endpoints for specific Sites configurations) - the global endpoint includes all the site-specific configurations(the models created under site-specific conf folders).
You may receive the below error while executing the API's in the AEM Author
{
"message": "Failed to execute 'text' on 'Response': body stream already read",
"stack": "TypeError: Failed to execute 'text' on 'Response': body stream already read\n at http://localhost:4502/content/graphiql.html:81:33\n at async Object.onRun (https://unpkg.com/graphiql/graphiql.min.js:1:533801)"
}
The actual root cause was the CSRF filter blocking the requests in AEM Author, the path white listing looks to be not enabled while upgrading from 6.5.8.0 to 6.5.10.0(but it worked for me while upgrading from 6.5.0.0 to 6.5.10.0), the whitelisting is already enabled in AEM as a Cloud Service, ensure the blow paths are whitelisted in the CSRF filter.
Configuration Browsers - Enable Content Fragment Model/GraphQL Persistent Queries:
Enable the Content Fragment Model/GraphQL Persistent Queries through configuration browser for the global sites configurations and the site-specific configurations.
Tools -- General -- Configuration Browser -- Select Global folder -- Properties, ensure Content Fragment Models and GraphQL Persistent Queries are selected
Enable Content Fragment Model(Model your content):
Model your content structure using AEM Content Fragment Models and Content Fragments.
Let us now enable the content fragment model that should be used to generate Content Fragments that will be exposed to the external system through GraphQL API for consuming the content.
Tools -- Assets -- Content Fragment Models, Select the global or Site-Specific folder(I am creating under the site-specific folder) and create a new Model. Ensure the model is enabled, GraphQL schema will be generated only for the models that are enabled
GraphQL tab displays the API details
Open the model and enable the required fields - additional data types e.g Content Reference, Fragment Reference, JSON Object and etc can be used if required while structuring the Content Fragment Model. The referenced Model(Fragment) data can also be fetched through the parent GraphQL API e.g. if Employee Model references Person Model, the associated Person data can be fetched through Employee GraphQL API.
The default Cache configurations can be modified if required through Content Fragment Model properties
GraphQL Endpoints:
AEM endpoint helps to access the GraphQL schema, also send GraphQL queries and receive the response.
Define the API endpoint by going to Tools -- Assets -- GraphQL
The global endpoint will be defined by default(enable if missing), define the site-specific endpoints if required
(global endpoint expose all the models if required site-specific endpoints can be enabled but the whitelisting and other configurations should be enabled for site-specific endpoints)
The endpoint should be published to make it available to the publisher. Ensure the appropriate security permissions are set up while enabling the GraphQL API endpoints in publishers.
GraphQL Schema:
GraphQL is a strongly typed API, which means that data must be clearly structured and organized by type.
The GraphQL specification provides a series of guidelines on how to create a robust API for interrogating data on a certain instance. To do this, a client needs to fetch the Schema, which contains all the types necessary for a query.
In AEM, the schema is generated based on the Content Fragment Model, the schema is flexible. This means that it is auto-generated each and every time a Content Fragment Model is created, updated, or deleted. The data schema caches are also refreshed when you update a Content Fragment Model.
The schema can be accessed through endpoint API - http://localhost:4502/content/cq:graphql/global/endpoint.GQLschema
Also, schema and schema docs can be fetched through the GraphQL interface(additional fields can be added if required)
{
__schema {
types {
name
description
}
}
}
The schema errors can be fetched through http://localhost:4502/content/cq:graphql/global/endpoint.schemaerrors
Create Content Fragments:
Now create the required Content Fragments under the appropriate asset folder(e.g mysite) with the required data, select the Content Fragment Model created in the earlier step.
Ensure the Content Fragment Models are enabled for the asset folders(e.g mysite)
Enter the required data and save it, the model should be published to enable the access with publish server.
Content Fragments now enables preview for GrapQL data, the preview will work only if the site-specific endpoint is registered.
Register the site-specific endpoint to preview the content
Now you should be able to preview the data
You should be able to copy the direct GraphQL data URL - http://localhost:4502/content/dam/mysite/person1.cfm.gql.json ( this will expose the Content Fragment Data as JSON)
Fetching the Data:
Now the data can be queried based on the GraphQL API's defined in the schema - http://localhost:4502/content/cq:graphql/global/endpoint.GQLschema
"""
Get a single `PersonModel`, specified by its path and optional variation
"""
personByPath(_path: String!, variation: String): PersonModelResult! @fetcher(name : "sites/default", source : "personByPath")
"""
"""
Get multiple `PersonModel` objects
"""
personList(filter: PersonModelFilter, variation: String, _locale: String): PersonModelResults! @fetcher(name : "sites/default", source : "personList")
"""
Fetch the person details by path:
The personByPath API can be used to fetch content fragment specific data(the Content Fragment created based on Person Model) by specifying the content fragment path, modify the attributes(I am fetching firstName, lastName, and email) based on your needs
query{
personByPath(_path:"/content/dam/mysite/person1"){
item {
firstName
lastName
email
}
}
}
Fetch Person Lists:
The personList API can be used to fetch a list of Content Fragments created using the Person Model.
query{
personList{
items {
_path
firstName
lastName
email
}
}
}
Filter based on expression:
The filters can be specified to fetch specific content fragments from the list of content fragments by specifying filter expressions.
query {
personList(filter: {
firstName: {
_expressions: [
{
value: "Test1"
_operator: EQUALS_NOT
}
]
}
}) {
items {
firstName
lastName
email
}
}
}
For the Multiline Text fields, the below properties can be used to fectch the different format data
html
plaintext
markdown
json
query{
personByPath(_path:"/content/dam/mysite/person1"){
item {
firstName
lastName
email
additionalDetails
{
html
markdown
plaintext
json
}
}
}
}
The metadata and variation details can also be fetched, also other expressions can be used to filter the data - refer to https://github.com/AdobeDocs/experience-manager-cloud-service.en/blob/master/help/assets/content-fragments/content-fragments-graphql-samples.md#content-fragment-structure-graphql for more query operations and fields.
The GraphQL variables and Directives can be used to filter the data, refer https://experienceleague.adobe.com/docs/experience-manager-cloud-service/assets/admin/graphql-api-content-fragments.html?lang=en for more details.
Persisted Queries (Caching):
Prepare query with a POST request, it can be executed with a GET request and the responses can be cached by HTTP caches or a CDN. The Persisted queries help to improve the overall performance and also reduce the load on AEM servers.
Create Persisted Query(PUT) - may be the easy option is to copy the curl command from the GraphQL interface and modify
The persisted query can be created by sending the PUT request to http://localhost:4502/graphql/persist.json/<Site Name>/<Query Name>
I am using the basic authentication for the demo but the token-based authentication should be used for AEM as a Cloud Service.
curl -u admin:admin -X PUT 'http://localhost:4502/graphql/persist.json/mysite/person' \
-H 'Content-Type: application/json' \
--data-raw '{"query":"query{\n personByPath(_path:\"/content/dam/mysite/person1\"){\n item { \n firstName\n lastName\n email\n }\n }\n}","variables":null}'
The output will provide a short path that can be used to execute the query e.g /mysite/person
Execute Persisted Query - the persisted query can be executed by sending get request to
http://localhost:4502/graphql/execute.json/<Query Shortname>
curl -u admin:admin -X GET http://localhost:4502/graphql/execute.json/mysite/person
The existing persisted queries can be modified by sending the POST request to http://localhost:4502/graphql/persist.json/<Site Name>/<Query Name>
curl -u admin:admin -X POST 'http://localhost:4502/graphql/persist.json/mysite/persion' \
-H 'Content-Type: application/json' \
--data-raw '{"query":"query{\n personByPath(_path:\"/content/dam/mysite/person1\"){\n item { \n firstName\n lastName\n email\n }\n }\n}","variables":null}'
The max cache header value can be specified while creating the persisted queries - this will help us to cache the query result for an extended time
curl -u admin:admin -X PUT 'http://localhost:4502/graphql/persist.json/mysite/persion' \
-H 'Content-Type: application/json' \
--data-raw '{"query":"query{\n personByPath(_path:\"/content/dam/mysite/person1\"){\n item { \n firstName\n lastName\n email\n }\n }\n}","variables":null,"cache-control":{"max-age":300}}'
The persisted queries are stored under /conf/<Site Name>/settings/graphql/persistedQueries
The list of Persisted queries can be fetched through /graphql/list.json endpoint - curl -u admin:admin -X GET http://localhost:4502/graphql/list.json
To delete the existing query, send the DELETE request to the /graphql/persist.json/<Short Path> - curl -u admin:admin -X DELETE http://localhost:4502/graphql/persist.json/mysite/test
Refer to https://experienceleague.adobe.com/docs/experience-manager-cloud-service/assets/admin/graphql-api-content-fragments.html?lang=en for more details on persisted queries
Consume GraphQL API Data from External System:
The external system can retrieve the Content Fragment data by sending the POST request to /content/_cq_graphql/global/endpoint.json GraphQL API endpoint(copy the curl request from GraphQL interface UI)
I am using the basic authentication for the demo but the token-based authentication should be used for AEM as a Cloud Service(Let me explore and share this later). Every Content Fragment model can be accessed through a global endpoint but individual endpoints can be enabled if required.
curl -u admin:admin 'http://localhost:4502/content/_cq_graphql/global/endpoint.json' \
-H 'Content-Type: application/json' \
--data-raw '{"query":"query{\n personByPath(_path:\"/content/dam/mysite/person1\"){\n item { \n firstName\n lastName\n email\n }\n }\n}","variables":null}'
The CORS and Referrer filter should be configured to enable the access for external systems(for the demo I am showing the OSGI console but enable through code - AEM as a Cloud service won't provide access to OSGi console, ready only view is provided through Developer console - refer https://www.albinsblog.com/2021/10/developer-console-and-adobe-io-cli-for-cloud-manager-to-debug-aem-as-a-cloud-service.html for more details)
Enable the external domains as Allowed Origins and the below paths as Allowed Paths through Adobe Granite Cross-Origin Resource Sharing Policy
/content/_cq_graphql/global/endpoint.json // everything should be accessible through global, individual endpoints can be enabled if required
/graphql/execute.json/.* // endpoint to execute the persisted queries
The CORS Filter should be enabled while testing thorugh localhost SPA application, also ensure the Authorization token is whitelisted.
Referrer filter must be configured to allow access from external hosts.
Now the external system will be able to fetch the Content Fragment data from AEM through GraphQL API queries.
The GraphQL API of AEM provides a powerful query language to expose data of Content Fragments to downstream applications to support headless content sharing with external systems. Content Fragment models define the data schema that is used by Content Fragments. Whenever a Content Fragment Model is created or updated, the schema is translated and added to the “graph” that makes up the GraphQL API.
The GraphiQL IDE allows you to quickly test and refine the queries and data returned. GraphiQL also provides easy access to the documentation, making it easy to learn and understand what methods are available.
The AEM GraphQL API currently not supporting some of the GraphQL API features such as Mutations, Subscriptions, and Paging/Sorting, the API is read-only and the Asset API should be used to perform the data management operations.
Great explanation and informative.
ReplyDeleteThanks,
Debal
I am using the basic authentication for the demo but the token-based authentication should be used for AEM as a Cloud Service -We are using aem 6.5 . Can you explain how we can used token based authentication for graphql api by third party application for aem 6.5
ReplyDeleteHi, It's a nice article. Is it possible to create persist query in AEM through backend
ReplyDeleteHi, thanks for this tutorial. I'm having the issue mentioned above.
ReplyDelete"message": "Failed to execute 'text' on 'Response': body stream already read"
I whitelisted the urls in the CSRF filter, but I'm still getting the error.
I'm using AEM 6.5.14
Is there anything else I can do to fix this issue?