Topics

Content Delivery API


In This Guide

This section describes how to use Brightspot’s Content Delivery API (CDA).


Content Delivery API permissions

Overview of CDA permissions

In addition to the permissions described Authentication, the Content Delivery API allows for additional logic to restrict access to an endpoint.

Sites permissions

For detailed information about Sites Permissions, see Sites permissions.

Attributional client settings

If a Content Delivery API endpoint is configured to allow access to Anyone, an attributional client may be set for requests to that endpoint. Any requests to that endpoint will be treated as if they came from the attributional client, and will have the permissions that are set on that client applied to their request.

To add an attributional client to your CDA endpoint, select the endpoint from the APIs Dashboard menu and navigate to the Access field. Set the value to Anyone, then select your desired attributional client and click Save.


CDA guides

This section provides reference information about configuring and using the Content Delivery API.

Overview of the Content Delivery API

The GraphQL plugin includes a configurable Content Delivery API endpoint type. Without any additional development work, endpoints can be created in order to expose APIs for interacting with view models. The high level of configurability allows for quick and simple proof of concept tests. This endpoint type is not intended for production use because endpoints defined via project code are more consistent and easier to maintain in different environments. See Custom Content Delivery API development for more information on how to define a CDA endpoint via project code.

Configuring the Content Delivery API

  1. Click menu > Admin > APIs
  2. Create a new GraphQL Delivery API:

    • Name—used to differentiate API endpoints in the GraphQL CMS tool. It is also used to generate a default path for the API endpoint.
    • Path—appended to /graphql/management/ to create the path where the API endpoint is available.
    • Full Path (Read Only)—displays full path after applying logic for Path field.
    • Query Entry View Classes—view models and interfaces used to generate the schema and expose GraphQL Query APIs.
    • Mutation Entry View Classes—view models and interfaces used to generate the schema and expose GraphQL Mutation APIs.
    • Access—specifies entities that can access the API endpoint.

      • API Key Required—an API key from one of the endpoint’s Clients is required for access to the endpoint (see Authentication).
      • Anyone—the API endpoint is open to any entity that can access one of the Paths that it is available at.

        • Attributional Client (Optional)—The API Client that receives attribution for, and whose permissions are applied to, all requests made to this endpoint.
    • CORS Configuration—configuration of allowed origins and headers for the API endpoint (see Cross-Origin Resource Sharing).
    • Persisted Query Protocol—option to select a specific persisted query protocol implementation for the API endpoint (see Persisted queries).
    • Schema Locale—locale used during GraphQL schema documentation generation.
    • Theme—provides theme fields, front-end fields, and/or image sizes for use via the API endpoint.
    • Paths (Read Only)—all the paths the API endpoint is available at.
    • Clients (Read Only)—API clients that have access to the API endpoint.
  3. Click Save in order to expose the endpoint.

Custom Content Delivery API development

Overview of custom CDA development

For stable production functionality, it typically makes more sense to build a custom Content Delivery endpoint via code, rather than entirely through editorial configuration. An endpoint class can ensure that a fixed set of APIs are exposed with configuration that should not change between different environments.

Implementing custom CDAs

  1. Create a new Java class extending ContentDeliveryApiEndpoint.
  2. Implement required methods:
  • getPathSuffix—appended to /graphql/delivery/ to create the path where the API endpoint is available.
  • getQueryEntryFields—objects wrapping view model and interface classes used to generate the schema and expose APIs.

Example:

public class FooContentDeliveryApiEndpoint extends ContentDeliveryApiEndpoint {

    @Override
    protected String getPathSuffix() {
        return "foo"; // API endpoint is available at path '/graphql/delivery/foo'
    }

    @Override
    public List<ContentDeliveryEntryPointField> getQueryEntryFields() {
        return Arrays.asList(
               new ContentDeliveryEntryPointField(FooViewModel.class),
               new ContentDeliveryEntryPointField(BarViewModel.class));
    }
}


Using Brightspot GraphQL Preview

This guide will show you how you can still leverage Brightspot’s preview functionality even when going headless.

Configuring GraphQL Preview

In Brightspot, navigate to Menu > Admin > Sites > Settings, and select your site in the left pane.
Scroll down to the Preview cluster, and under Preview Types add a GraphQL Preview.
Add a Name for your GraphQL Preview and set the Preview URL to the URL where your application is running.
Set the Default Preview Type to the name of your GraphQL Preview and click Save.

Configuring CORS

Make sure that CORS settings are properly configured on your endpoint for the domain in which your application is running. For more information, see Cross-Origin Resource Sharing.

Configuring the front-end application

An application may only expect user input to result in resolution of a URL path to utilize in a data query. However, Brightspot’s view system will actually send your application a preview ID, instead of a path, which is used to fetch the preview data for the Brightspot user’s current content form. Therefore, your application must accept an id input for any top-level query field. Note that the id input can be used for preview IDs as well as normal record IDs.

Consider the following example GraphQL query:

query getPage($path: String) {
    Page(path: $path) {
        __typename
        ... on ArticlePage {
            headline
        }
        ... on SectionPage {
            title
        }
    }
}

To enable preview functionality, the query should be modified to accept an id input as well.

query getPage($id: ID, $path: String) {
    Page(id: $id, path: $path) {
        __typename
        ... on ArticlePage {
            headline
        }
        ... on SectionPage {
            title
        }
    }
}

The preview ID is sent to the application’s URL via an HTTP previewId parameter on a request. Consider the following example Javascript example for handling a Brightspot preview request:

const previewId = new URLSearchParams(window.location.search).get('previewId')
const variables = previewId != null ? { id: previewId } : { path: window.location.pathname }

Your application can now supply the proper variables for the GraphQL query whether it is processing a preview request or not.

Verification

Open an Article in the CMS and click the “eye” icon to open the preview pane. You should see the name of your GraphQL Preview as the selected Preview type. When you make changes to the headline field of the Article, the preview will automatically update to reflect these changes.


Customizing field arguments in Brightspot Content Delivery API

There exist use cases where the default id and path arguments exposed in a Content Delivery API (CDA) do not fit the intended functionality. Fortunately, they are flexible!

Prerequisites

For endpoint configuration: Custom Content Delivery API development or Hello Content Delivery API.

Introduction

When exposing GraphQL APIs via Brightspot’s view system, data is typically queried for by ID or path. However, there are cases in which callers may want to query by some other criteria, like a third-party ID or maybe even remove the arguments altogether, leaving object resolution up to the view model.

Background

When it comes to removing the arguments completely, we can accomplish this by using the correct generic argument for the relevant ViewModel class. There are two ways to go about this:

  • Leverage Brightspot’s Singleton interface on your view model’s model.
  • Utilize your ContentDeliveryApiEndpoint subclass as your view model’s model.

For adding custom arguments we can leverage Brightspot’s @WebParameter annotation. It allows us to treat scalar fields, such as integers, strings, and even lists of them, as an argument.Additionally, we can create custom Java annotations that are utilized to denote a field that should be transformed into an argument with a custom input type, and even populate a Java POJO, by using ContentDeliveryApiWebAnnotationProcessor.

Examples

View model with singleton model

public class Homepage extends Content implements Singleton {
    
    private String title;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

@ViewInterface
public class HomePageViewModel extends ViewModel<Homepage> {
    
    public String getTitle() {
        return model.getTitle();
    }
}

The following types are generated in the resulting schema:

type Query {
    Homepage: Homepage
}

type Homepage {
    title: String
}

Callers are now able to query for the homepage without any arguments since the system knows how to find the one and only.

API entry field

Sometimes API designers may not need to use a data model, or may want to implement their own logic to resolve the proper data model. In this case, they can just use their endpoint class as the model for the view model.

Also, as discussed earlier, they can leverage @WebParameter to expose their own scalar arguments.

Consider the following classes:

public class Article extends Content {
    
    private String headline;

    public String getHeadline() {
        return headline;
    }

    public void setHeadline(String headline) {
        this.headline = headline;
    }
}

@ViewInterface
public class ArticleViewModel extends ViewModel<Article> {

    public String getHeadline() {
        return model.getHeadline();
    }
}

@ViewInterface
public class ArticleSearchViewModel extends ViewModel<ArticleEndpoint> {

    @WebParameter
    private String search;

    @WebParameter
    private Integer limit = 5;

    public Iterable<ArticleViewModel> getArticles() {
        Query<Article> articleQuery = Query.from(Article.class);

        if (!StringUtils.isBlank(search)) {
            articleQuery.where("* matches ?", search);
        }

        final int maxLimit = 50;

        return createViews(ArticleViewModel.class, articleQuery
            .select(0, Math.min(limit, maxLimit)).getItems());
    }
}

public class ArticleEndpoint extends ContentDeliveryApiEndpoint {

    @Override
    protected String getPathSuffix() {
        return "/article";
    }

    @Override
    public List<ContentDeliveryEntryPointField> getQueryEntryFields() {
        return Collections.singletonList(new ContentDeliveryEntryPointField(ArticleSearchViewModel.class));
    }
}

The following types are generated in the resulting schema:

type Query {
    ArticleSearch(search: String, limit: Int): ArticleSearch
}

type ArticleSearch {
    articles: [Article]
}

type Article {
    headline: String
}

The @WebParameter annotated fields are assigned to their respective argument value passed in the GraphQL query, and are utilized in the view model logic. Callers are now able to query for articles via a general full-text search.

Custom input type

API designers may want to expose a structured input type and have it populate some custom type on the back end. This is easily achievable with a custom Java annotation and a ContentDeliveryApiWebAnntoationProcessor implementation.

Consider the following classes:

public class Person {

    private String name;

    private String location;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CurrentPerson {

}

public class CurrentPersonProcessor implements ContentDeliveryApiWebAnnotationProcessor<CurrentPerson> {

    @Override
    public Object getValue(ApiRequest request, Object input, Field field, CurrentPerson annotation) {
        String name = (String) CollectionUtils.getByPath(input, "name");
        String location = (String) CollectionUtils.getByPath(input, "location");

        Person person = new Person();
        person.setName(name);
        person.setLocation(location);

        return person;
    }

    @Override
    public SchemaInputType getInputType(Field field, CurrentPerson annotation) {
        return SchemaInputTypes.newInputObject("Person")
            .field("name", SchemaInputTypes.nonNullOf(SchemaInputTypes.STRING))
            .field("location", SchemaInputTypes.nonNullOf(SchemaInputTypes.STRING))
            .build();
    }
}

public class GreetingEndpoint extends ContentDeliveryApiEndpoint {

    @Override
    protected String getPathSuffix() {
        return "/greeting";
    }

    @Override
    public List<ContentDeliveryEntryPointField> getQueryEntryFields() {
        return Collections.singletonList(new ContentDeliveryEntryPointField(GreetingViewModel.class));
    }
}

@ViewInterface
public class GreetingViewModel extends ViewModel<GreetingEndpoint> {

    @CurrentPerson
    private Person person;

    public String getGreeting() {
        return "Hello " + person.getName() + " from " + person.getLocation() + "!";
    }
}

The following types are generated in the resulting schema:

type Query {
    Greeting(person: Person): Greeting
}

type Greeting {
    greeting: String
}

input Person {
    name: String!
    location: String!
}

The @CurrentPerson annotated field is assigned to its respective argument value passed in the GraphQL query, and is utilized in the view model logic.