XperienCentral GraphQL API
The XPerienCentral GraphQL API is still in its beta phase while new functionalities are being added. This means that the API might undergo some major changes that require updating your custom implementations when upgrading.
In This Topic
Features
The GraphQL API enables the consumer of the API to retrieve any publicly available content items from XperienCentral, either by querying directly by ID or retrieving a list of content items filtered by parameters.
Current Features
- Retrieval of specific Content Items.
- Filtered and sorted list retrieval of all public Content Items.
- Support for Modular Content Items, properties, page metadata, and elements.
- Support for Custom Content items, properties, page metadata and elements.
- Retrieval Comment Sections and specific Comments.
- Retrieval of Interactive Forms.
Planned Features
- Support for read access
- Complete Interactive Forms support
- Complete Comment Section support
Configuring the GraphQL API
You can enable and configure the GraphQL API settings in the General tab of the Setup Tool under the section graphqlservice(beta)
. The API is available at the URL <server name>/web/delivery
. You can access this URL using a POST request using one of the supported REST client applications (GET requests return no value).
Authentication
It's possible to only allow specific users access to the GraphQL. This will require a valid User with an application key. You can enable authentication in the Setup by enabling the require_authentication
option.
Caching
In R44 caching was added the GraphQL API to improve the responsiveness of the API, especially for environments with a lot of content. GraphQL caches the results for the time set in the cache_expiration_time
option in the Setup. The cache does not automatically invalidate when the source has been changed. This means that changes will can take some time to propegate to the GraphQL API.
Rate Limiting
To prevent abuse you can enable rate limiting for the API by selecting the rate_limit_enabled
option. This limits the amount of request per IP address. The amount of requests one user can send before being rate limited can be set via the rate_limit_call_limit
option. This amount of requests can be send per the amount of minutes set in the rate_limit_time_frame_in_minutes
option. To prevent certain users from being rate limited the IP addresses of these user can be added to the rate_limit_ignored_ips
option.
Rate Limiting behind a Proxy
If an environment is running behind a proxy the IP addresses are obtained via the x-forwarded-for header. This requires a small change to the Tomcat/JBoss configuration, see GraphQL Rate Limiter Configuration for more information.
GraphQL Requests
The inline documentation of the API can be requested via an Introspection query. This query always requires user authentication, so you have to provide a valid appkey
with the request. Clients like Postman or Insomnia can turn the result of this query into a readable schema. Using Insomnia, you can click the schema button and the select "Show documentation". This opens a popup containing the API's documentation. In Postman you can change the request type from HTTP to GraphQL and the schema should automatically load into the Query tab.
It is strongly recommended to use the Query schema when building your GraphQL queries because it explains how the schema is structured and also contains a description of each individual query and (input)field.
Executing Your First Request
To request some properties of a specific article we need to know the GraphQL-ID of the article. This can be found in the Status Tab of the Properties Panel of the article we like to request, or it can be retrieved using one of the other queries. The query must be send as a POST request and beside the id
also requires the language
of the article we want to request. You can execute the following GraphQL query to the API using your favorite REST client application, for example Insomnia or Postman.
query { mediaItems { mediaItem(id: "79c6c381-066c-4c33-a22f-93743a48e4ec", language: nl_NL) { contentType title } } }
which will return
{ "data": { "mediaItems": { "mediaItem": { "contentType": "article", "title": "My Article about GraphQL" } } } }
Filtering and Sorting on Modular Content Properties
It is possible to filter and sort on Modular Content Properties with a Search index. The filter fields can be found directly under the filter input and the sort options are part of the enum consisting of all the sortable fields. The names of these filters and sort options are prefixed with modular so they are easy to spot. When filtering or sorting on Pages the modular content filters and sorters are applied on the modular page metadata. For modular content types the filtering and sorting is applied on the properties of the content item.
Below is a sample query where we are filtering and sorting on the Source property, which has a combined search index, and the Author property of the Blog modular template which has a unique search index.
This sample query also shows how to return Modular Content fields. There are three places where Modular Content fields can be used, namely in Modular Content Items, in Page Metadata, and in Modular Elements.
query FilteredItems { filteredItems( filter: { modularSource: { or: ["Autoweek"] } modularBlogAuthor: { not: "Steven" } } sort: [ {field: modularSource, order: desc}, {field: modularBlogAuthor, order: asc} ] first: 5 language: nl_NL ) { edges { node { id title ... on BlogModule { properties { author source } } ... on Page { pageMetaData { ... on ModularPageMetadata { blog { author source } } } content { elements { ... on ModularElement { blog { author source } } } } } } } } }
XperienCentral GraphQL Documentation
The inline documentation of the API can be explored using one of the REST clients mentioned above. Using Insomnia, you can click the schema button and the select "Show documentation". This opens a popup containing the API's documentation.
Extending the API
Any change made to XpereienCentral that affects the GraphQL schema requires the schema to be fully regenerated. This includes, but is not limited to, enabling/disabling a Content Type or making a change to a Modular Content Template. The schema is regenerated automatically upon receiving the first query after the change that affected the schema was made.
Adding Custom Queries
To add new queries to the API we have to create a new service which implements the GraphQLDeliveryApi.Provider
. A query can be added by creating a public method with the GraphQL SPQR annotations including the @GraphQLQuery
annotation.
Example
For this example let's start by creating a Service component using the Quick Start guide. This will generate a plugin for you with a service component. This will provide you with the following files in the com.gxwebmanager.helloworld.helloworldservice folder.
Activator.java
api/
package.html
HelloWorldServiceService.java
WCBConstants.java
service/
package.html
HelloWorldServiceServiceImpl.java
Now we're going to edit Activator.java
and HelloWorldServiceServiceImpl.java
. In order to be picked up by the GraphQL API the HelloWorldServiceServiceImpl.java
needs to implement the GraphQLDeliveryApi.Provider
interface. In addition any methods that should be exposed in the API need to be annotated using the GraphQL SPQR
annotations. The file below contains a complete example of the file.
HelloWorldServiceServiceImpl.java
package com.gxwebmanager.helloworld.helloworldservice; import com.gxwebmanager.helloworld.helloworldservice.api.HelloWorldServiceService; import io.leangen.graphql.annotations.GraphQLQuery; import nl.gx.webmanager.wcb.servicetype.impl.SimpleServiceComponent; import nl.gx.webmanager.wcbs.graphqlservice.api.GraphQLDeliveryApi; import java.text.DateFormat; import java.util.Date; /** * Implementation of the HelloWorldService service component. */ public class HelloWorldServiceServiceImpl extends SimpleServiceComponent implements GraphQLDeliveryApi.Provider, HelloWorldServiceService { /** * {@inheritDoc} */ @GraphQLQuery(name = "timestamp", description = "Returns the current timestamp of the server.") public String getTimeStamp() { return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(new Date()); } }
In addition the Activator also needs to be updated. The following line needs to be updated from:
definition.setInterfaceClassNames(new String[]{SimpleServiceComponent.class.getName(), HelloWorldServiceService.class.getName()});
to
definition.setInterfaceClassNames(new String[]{SimpleServiceComponent.class.getName(), GraphQLDeliveryApi.Provider.class.getName(), HelloWorldServiceService.class.getName()});
Now build the plugin and deploy it on your environment. The timestamp query will automatically be registered to the GraphQL API and should show up in your schema after a refresh.
Adding GraphQL Support to Custom Elements/Content Items/Form Fragments/Page Metadata
Custom elements, content items, form fragments and page metadata can also be returned by the GraphQL API. However this does require some additional code. If the additional code is not provided, GraphQL can still return these items but only via a generic query which can only include default fields.
Pojo Classes
In R44 we redesigned our GraphQL API implementation to use Pojo classes. This was done to simplify our implementation, make the code clearer and more readable by separating GraphQL from the rest of the code and improve the performance of the GraphQL API by caching the requested content in Pojos.
The data we expose via the GraphQL API is now stored in and accessed via these Pojo classes. This does give developers more control over what and how the data should be exposed via the GraphQL API without requiring any changes to the rest of the application.
When creating a Pojo class, there are a few important things to keep in mind. First, all data must be set creating the Pojo which means that all class variables should be set to final. Second, variables of a custom class must return the Pojo of that custom class. A lot of default XPerienCentral classes already have a Pojo which can be reused. Checkout the Javadoc for more information.
Pojo Annotation
Example
For this example we create a new custom Element named GraphQLTest (see the Quick Start guide for more details).
There are only three relevant files for us, namely:
api/GraphQLTestElement.java
is the interface in which we define the methods of our custom element.api/GraphQLTestElementPojo.java
is the Pojo file for our element, which contains the methods for the fields we want to expose via GraphQL.element/GraphQLTestElementImpl.java
is the file which contains the implementation of the interface and is contains the class where the@POJO
annotation is added.
Let’s add a method to our custom element, for example the method String getFavoriteClient()
which returns ones favorite GraphQL client. After adding the method to our interface and implementation, we also have to add it to our Pojo (if we want to expose it via the GraphQL API that is, if not we don’t have to do anything and it won’t be exposed).
public interface Browser { public String getName(); public String getVersion(); }
After creating the Browser implementation, adding the Browser
getBrowser()
method to our interface and implementation like we normally do, we now also have to create a new Pojo for the Browser
interface, which we will name BrowserPojo, and add the browser variable to the GraphQLTestElementPojo
(if we want to expose the variable via the GraphQL API that is, if we don’t want to expose it we don’t have to do anything, since that is the default behavior).
We start by creating the BrowserPojo
class. This is the class that will be used by GraphQL and thus requires the GraphQL annotations. Below is the sample code for the BrowserPojo
class.
@GraphQLType(name = "Browser", description = "An object representing a Browser.") public class BrowserPojo { private final String name; private final String version; public BrowserPojo(Browser browser) { this.name = browser.getName(); this.version browser.getVersion(); } @GraphQLQuery(description = "Returns the name of the browser.") public String getName() { return name; } @GraphQLQuery(description = "Returns the version of the browser.") public String getVersion() { return version; } }
Next we will have to make three changes to the GraphQLTestElementPojo
class:
First we have to create a new class variable where we can store our favorite client in (so it can be cached):
private final BrowserPojo browser
;Next we have to set the value of this variable in the constructor, which usually is just calling a getter, but in this case we have to convert the browser to a
BrowserPojo
and make sure it’s not NULL to prevent a NullPointerException from being thrown. So we would add the following line to the constructor:this.browser = mediaItemVersion.getBrowser() != null ? new BrowserPojo(mediaItemVersion.getBrowser()) : null;
Lastly we also have to add a new getter including the
@GraphQLQuery
annotation so the browser can be requested via the GraphQL API:@GraphQLQuery(description = "Returns the browser of the media item.") public BrowserPojo getBrowser() { return browser; }
And that’s it. We have now added support for a new custom element to our GraphQL API including a new method with a new class definition. This process is identical for custom Media Items, Page Metadata and Form Fragments. The only difference is the base Pojo class which our custom Pojo classes extend from. These being MediaItemArticleVersionPojo for custom Media Items, FormFragmentPojo for custom Form Fragments, PageMetaDataPojo for custom Page Metadata and ElementPojo form custom Elements.