Skip to main content

Getting Started with the GCA

note

This guide is intended for developers who want to build a GraphQL endpoint on Brightspot. For information on consuming an existing GraphQL API, see the GraphQL Explorer documentation.

The most common way to get started with GraphQL in Brightspot is with the GraphQL Content API (GCA). It automatically generates a complete GraphQL API from your content types using code as configuration for the types, fields, and functionality you want to expose. This guide will get you up and running with the GCA. This guide assumes you're familiar with the GraphQL specification. If you're new to GraphQL, you may want to check out the Learn GraphQL documentation first.

Installation

First, add the GraphQL plugin dependency to your Brightspot project. This provides the base set of APIs and tools (e.g. GraphQL Explorer) needed to work with GraphQL in Brightspot.

Requirements
Requires Brightspot 4.8 or later.
<dependency>
<groupId>com.brightspot.graphql</groupId>
<artifactId>graphql</artifactId>
<version>1.0.5</version>
</dependency>

Next, include the GCA, which will make the GCAEndpoint and GCASchemaSettings APIs available to your project.

<dependency>
<groupId>com.brightspot.graphql</groupId>
<artifactId>gca</artifactId>
<version>1.0.5</version>
</dependency>

Your first GCA endpoint

1
import java.util.Set;
2
3
import com.psddev.dari.db.Recordable;
4
import com.psddev.dari.db.Singleton;
5
import com.psddev.graphql.gca.GCAEndpoint;
6
import com.psddev.graphql.gca.GCASchemaSettings;
7
8
@Recordable.DisplayName("My First GCA Endpoint")
9
public class MyFirstGCAEndpoint extends GCAEndpoint implements Singleton {
10
11
@Override
12
public Set<String> getPaths() {
13
return Set.of("/my-first-gca");
14
}
15
16
@Override
17
protected GCASchemaSettings getSchemaSettings() {
18
return GCASchemaSettings.newBuilder()
19
.build();
20
}
21
}

That's it! Upon compiling and deploying your project you will have a fully spec-compliant GraphQL API accessible at http://localhost/my-first-gca, albeit with a mostly empty schema. With just this code, you can explore the schema in the GraphQL Explorer, start furnishing API keys for access, and with the analytics plugin installed, the system will automatically collect usage statistics which are viewable in the Analytics tab of the endpoint configuration page.

Add a data model

Now let's add a content type to expose through your API. We'll create a simple Event type that represents an event with registration capabilities.

1
import java.util.Date;
2
3
import com.psddev.cms.db.Content;
4
5
public class Event extends Content {
6
7
@Indexed
8
private String name;
9
10
private String description;
11
12
@Indexed
13
private Date eventDate;
14
15
@Indexed
16
private String location;
17
18
@Indexed
19
private Integer maxAttendees;
20
21
@Indexed
22
private Integer registeredAttendees;
23
24
@Indexed
25
private Double ticketPrice;
26
27
// Getters and setters
28
public String getName() {
29
return name;
30
}
31
32
public void setName(String name) {
33
this.name = name;
34
}
35
36
public String getDescription() {
37
return description;
38
}
39
40
public void setDescription(String description) {
41
this.description = description;
42
}
43
44
public Date getEventDate() {
45
return eventDate;
46
}
47
48
public void setEventDate(Date eventDate) {
49
this.eventDate = eventDate;
50
}
51
52
public String getLocation() {
53
return location;
54
}
55
56
public void setLocation(String location) {
57
this.location = location;
58
}
59
60
public Integer getMaxAttendees() {
61
return maxAttendees;
62
}
63
64
public void setMaxAttendees(Integer maxAttendees) {
65
this.maxAttendees = maxAttendees;
66
}
67
68
public Integer getRegisteredAttendees() {
69
return registeredAttendees;
70
}
71
72
public void setRegisteredAttendees(Integer registeredAttendees) {
73
this.registeredAttendees = registeredAttendees;
74
}
75
76
public Double getTicketPrice() {
77
return ticketPrice;
78
}
79
80
public void setTicketPrice(Double ticketPrice) {
81
this.ticketPrice = ticketPrice;
82
}
83
}
84

Next, tell the GCA to include this type in your schema by adding it to your schema settings:

1
import java.util.Set;
2
3
import com.psddev.dari.db.Recordable;
4
import com.psddev.dari.db.Singleton;
5
import com.psddev.graphql.gca.GCAEndpoint;
6
import com.psddev.graphql.gca.GCASchemaSettings;
7
8
@Recordable.DisplayName("My First GCA Endpoint")
9
public class MyFirstGCAEndpoint extends GCAEndpoint implements Singleton {
10
11
@Override
12
public Set<String> getPaths() {
13
return Set.of("/my-first-gca");
14
}
15
16
@Override
17
protected GCASchemaSettings getSchemaSettings() {
18
return GCASchemaSettings.newBuilder()
19
.readonlyEntryClass(Event.class)
20
.build();
21
}
22
}

With this change, your schema now includes an Event type with all its fields, and you can query for those events with a query like:

1
query EventQuery {
2
Query {
3
Records(from: {type: Event}) {
4
pageInfo {
5
offset
6
limit
7
count
8
}
9
items {
10
__typename
11
_id
12
... on Event {
13
name
14
description
15
eventDate
16
location
17
maxAttendees
18
registeredAttendees
19
}
20
}
21
}
22
}
23
}
24

Enable write operations

By default, content types added with readonlyEntryClass are in fact read-only. To save, update and delete content, use mutableEntryClass instead which adds a mutation operation to the schema. You then need to specify which write actions are allowed. The example below includes them all, but you can also selectively add only those you need.

1
import java.util.Set;
2
3
import com.psddev.dari.db.Recordable;
4
import com.psddev.dari.db.Singleton;
5
import com.psddev.graphql.gca.GCAEndpoint;
6
import com.psddev.graphql.gca.GCASchemaSettings;
7
8
@Recordable.DisplayName("My First GCA Endpoint")
9
public class MyFirstGCAEndpoint extends GCAEndpoint implements Singleton {
10
11
@Override
12
public Set<String> getPaths() {
13
return Set.of("/my-first-gca");
14
}
15
16
@Override
17
protected GCASchemaSettings getSchemaSettings() {
18
return GCASchemaSettings.newBuilder()
19
.readonlyEntryClass(Event.class)
20
.mutableEntryClass(Event.class)
21
.contentActionTypesAll()
22
// .contentActionType(SaveActionDefinition.class)
23
// .contentActionType(ArchiveActionDefinition.class)
24
.build();
25
}
26
}

You can write GraphQL mutations to modify content like in the "save" example below:

1
mutation EventSave($args: RecordSaveActionInput!) {
2
Content {
3
Action {
4
Save {
5
Record(args: $args) {
6
state {
7
__typename
8
_id
9
... on Event {
10
name
11
}
12
}
13
}
14
}
15
}
16
}
17
}
18

See the GCA Mutations documentation for more details on mutation capabilities, including other write operations, dealing with referenced and embedded records, transactions, and more.

Add a ViewModel

Note

You've now hit a critical milestone in your GraphQL journey. It is at this point where you should seriously consider the use case you are solving for. If you refer back to the GraphQL plugin overview where it covers the various use cases for the GCA, how you configure your endpoint from here will depend heavily on those decisions. What we've done so far is great for managing ingestion and migrations where you expose all the read and write capabilities of Brightspot but would not want it to be accessible to the public. With a little more configuration, you can lock down the fields and write operations to power a frontend. However, for enterprise content delivery use cases, leveraging Brightspot's View System is a better long-term approach. You have full control over the content and shape of data available to consumers. You can also centralize business logic on the server to power multichannel delivery so that each consumer is not burdened by applying the same logic and transformations everywhere.

ViewModels allow you to add computed fields and API-specific logic without modifying your underlying data model. This is valuable because:

  1. Separation of concerns - Business data models stay focused on persistence while API presentation logic lives separately
  2. API evolution - Add new computed fields without database migrations or changing your data model
  3. Multiple representations - Different ViewModels can expose the same data model differently for different APIs
  4. Security - Conceal sensitive data from your underlying data model

Let's create an EventViewModel that adds both presentation logic (formatting) and business logic (availability calculations):

1
import java.text.NumberFormat;
2
import java.text.SimpleDateFormat;
3
import java.time.Instant;
4
import java.time.temporal.ChronoUnit;
5
import java.util.Locale;
6
7
import com.psddev.cms.view.ViewInterface;
8
import com.psddev.cms.view.ViewModel;
9
10
@ViewInterface("EventView")
11
public class EventViewModel extends ViewModel<Event> {
12
13
public String getFormattedEventDate() {
14
if (model.getEventDate() == null) {
15
return null;
16
}
17
SimpleDateFormat formatter = new SimpleDateFormat(
18
"EEEE, MMMM d, yyyy 'at' h:mm a",
19
Locale.US
20
);
21
return formatter.format(model.getEventDate());
22
}
23
24
public String getFormattedPrice() {
25
if (model.getTicketPrice() == null) {
26
return "Free";
27
}
28
NumberFormat formatter = NumberFormat.getCurrencyInstance(Locale.US);
29
return formatter.format(model.getTicketPrice());
30
}
31
32
public Integer getAvailableSpots() {
33
if (model.getMaxAttendees() == null) {
34
return null;
35
}
36
int registered = model.getRegisteredAttendees() != null ? model.getRegisteredAttendees() : 0;
37
return Math.max(0, model.getMaxAttendees() - registered);
38
}
39
40
public Boolean getIsSoldOut() {
41
Integer available = getAvailableSpots();
42
return available != null && available == 0;
43
}
44
45
public Long getDaysUntilEvent() {
46
if (model.getEventDate() == null) {
47
return null;
48
}
49
Instant now = Instant.now();
50
Instant eventInstant = model.getEventDate().toInstant();
51
return ChronoUnit.DAYS.between(now, eventInstant);
52
}
53
}
54

Now register the ViewModel with your endpoint:

1
import java.util.Set;
2
3
import com.psddev.dari.db.Recordable;
4
import com.psddev.dari.db.Singleton;
5
import com.psddev.graphql.gca.GCAEndpoint;
6
import com.psddev.graphql.gca.GCASchemaSettings;
7
8
@Recordable.DisplayName("My First GCA Endpoint")
9
public class MyFirstGCAEndpoint extends GCAEndpoint implements Singleton {
10
11
@Override
12
public Set<String> getPaths() {
13
return Set.of("/my-first-gca");
14
}
15
16
@Override
17
protected GCASchemaSettings getSchemaSettings() {
18
return GCASchemaSettings.newBuilder()
19
.mutableEntryClass(Event.class)
20
.contentActionTypesAll()
21
.entryViewClass(EventViewModel.class)
22
.build();
23
}
24
}

With the ViewModel in place, you can now query the Event and fetch the computed fields.

1
query GetEventView($_id: UUID) {
2
Get {
3
Record(with: {_id: $_id}) {
4
View(of: {type: EventView}) {
5
... on Record__EventView {
6
Response {
7
data {
8
__typename
9
_id
10
availableSpots
11
daysUntilEvent
12
formattedEventDate
13
formattedPrice
14
isSoldOut
15
}
16
}
17
}
18
}
19
}
20
}
21
}
22

Notice how the ViewModel keeps your Event data model clean and raw data hidden, while the computed fields handle presentation formatting and derived business data for your API consumers. The View System provides a flexible way to transform your content models into different representations suitable for different use cases.

Add custom business logic

While GCA automatically generates standard schema types and operations to fetch them, you are beholden to those formats. In addition, there are times when you need business logic not directly tied to a specific model. You can choose to avoid the overhead that comes with writing a ViewModel or writing the query syntax required to fetch the data. You can instead include arbitrary Java methods directly in your schema.

Imagine your UI needs to display a count of upcoming events in the user's location. You can define a static method that returns the count, without creating or modifying your Event model or ViewModel.

1
import java.util.Date;
2
3
import com.psddev.dari.db.Query;
4
5
public final class EventQueries {
6
7
/**
8
* Gets the number of upcoming events in the given location.
9
*
10
* @param location The location of the event.
11
* @return The event count.
12
*/
13
public static long getUpcomingEventsCount(String location) {
14
Query<Event> query = Query.from(Event.class)
15
.where("eventDate >= ? && location = ?", new Date(), location);
16
17
return query.count();
18
}
19
}
20

Next, register the helper class with your endpoint configuration.

1
import java.util.Set;
2
3
import com.psddev.dari.db.Recordable;
4
import com.psddev.dari.db.Singleton;
5
import com.psddev.graphql.gca.GCAEndpoint;
6
import com.psddev.graphql.gca.GCASchemaSettings;
7
import com.psddev.graphql.gca.settings.OperationType;
8
9
@Recordable.DisplayName("My First GCA Endpoint")
10
public class MyFirstGCAEndpoint extends GCAEndpoint implements Singleton {
11
12
@Override
13
public Set<String> getPaths() {
14
return Set.of("/my-first-gca");
15
}
16
17
@Override
18
protected GCASchemaSettings getSchemaSettings() {
19
return GCASchemaSettings.newBuilder()
20
.mutableEntryClass(Event.class)
21
.contentActionTypesAll()
22
.entryViewClass(EventViewModel.class)
23
.staticMethods(OperationType.QUERY, EventQueries.class)
24
.build();
25
}
26
}

Now, you can invoke the method directly from GraphQL using the Execute operation like so:

1
query EventQueries($location: String) {
2
Execute {
3
EventQueries {
4
getUpcomingEventsCount(location: $location)
5
}
6
}
7
}
8

Static methods provide a flexible way to add custom business logic to your GraphQL schema with a succinct syntax that's easy for consumers to understand. See the GCA Static Methods documentation for more advanced customization options.

Note

It is completely possible, and often preferable, to achieve the same result through the GCA's Query operation or through a custom ViewModel / method accessed from the Get operation. When to use one or the other is a matter of use case and preference. This topic is covered in more detail in later sections.

Next steps

Congratulations! You've built a complete GraphQL API with:

  • Content types that map your business domain to GraphQL types
  • Mutations for creating and updating content
  • ViewModels that add computed fields and presentation logic
  • Custom Methods that implement specialized business logic

You're ready to dive deeper on the various features and functionality that GraphQL and the GCA have to offer. Here are some recommended topics to serve as next steps.