Skip to main content
Version: 1.0.x

Getting Started with the GraphQL Framework

This guide walks you through building your first custom GraphQL endpoint using the Brightspot GraphQL Framework. By the end, you'll have a working endpoint that demonstrates the framework's core concepts.

What You'll Build

A minimal GraphQL endpoint with a single query field that returns "Hello World!" This simple example demonstrates:

  • How to extend AbstractGraphQLApiEndpoint
  • How to implement a GraphQLJavaSchemaLoader
  • How to create a SchemaContext
  • How to define types using OutputObjectTypeGenerator
  • How to define fields with data fetchers

The resulting schema:

1
schema {
2
query: Query
3
}
4
5
type Query {
6
message: String
7
}

Prerequisites

Before starting, ensure you have:

  • The GraphQL plugin dependency added to your project
  • Familiarity with the GraphQL specification
  • Understanding of Java generics and functional interfaces
  • A Brightspot project set up and running

Understanding the Components

A custom GraphQL endpoint requires four main pieces:

  1. API Endpoint - Extends AbstractGraphQLApiEndpoint and defines the URL path
  2. Schema Loader - Extends GraphQLJavaSchemaLoader and defines your schema structure
  3. Schema Context - Implements SchemaContext and provides shared functionality
  4. Type Generators - Define the structure and behavior of each GraphQL type

In our example, we'll create these as separate classes (split across tabs) to keep the code organized. In production, you'd typically place each in its own file.

Hello World Example

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.AbstractGraphQLApiEndpoint;
6
import com.psddev.graphql.GraphQLSchemaLoader;
7
8
@Recordable.DisplayName("Hello GraphQL Framework")
9
public class HelloEndpoint extends AbstractGraphQLApiEndpoint implements Singleton {
10
11
@Override
12
public Set<String> getPaths() {
13
return Set.of("/hello-graphql-framework");
14
}
15
16
@Override
17
protected GraphQLSchemaLoader getSchemaLoader() {
18
return new HelloSchemaLoader();
19
}
20
}
21

The result of the code above is a fully functioning GraphQL endpoint with the following generated schema:

1
schema {
2
query: Query
3
}
4
5
type Query {
6
message: String
7
}

Understanding What You Built

Let's break down each component and understand its role:

1. The API Endpoint (HelloEndpoint)

This class serves as the entry point for your GraphQL API:

  • Extends AbstractGraphQLApiEndpoint - Provides schema caching, schema history tracking, and request handling
  • Defines the path - The endpoint will be accessible at /graphql/hello-world
  • Returns a schema loader - Tells Brightspot how to build your schema

The AbstractGraphQLApiEndpoint handles the heavy lifting:

  • Caches the loaded schema for fast request handling
  • Tracks schema changes and versioning
  • Integrates with the GraphQL Explorer UI
  • Provides schema load error reporting

2. The Schema Loader (HelloSchemaLoader)

This class defines the structure of your GraphQL schema:

  • Specifies schema settings - Configuration like introspection, complexity limits, security rules
  • Provides a schema context - Shared state and business logic available throughout the schema
  • Defines the root query type - The entry point for all GraphQL queries

The schema loader is responsible for the schema loading lifecycle:

  1. Creates schema settings and context
  2. Builds the root query type
  3. Recursively discovers and registers all referenced types
  4. Validates the schema against GraphQL spec
  5. Returns a fully loaded, executable schema

3. The Schema Context (HelloSchemaContext)

This class provides shared functionality accessible throughout your schema:

  • Minimal implementation - Only requires access to the schema loader
  • Available everywhere - Accessible during schema load and query execution
  • Extensible - Add custom methods for shared business logic

In this example, the context is simple, but in real applications you might add:

  • Database access helpers
  • Permission checking methods
  • Shared configuration or settings
  • Utility functions used across multiple types

4. The Type Generator (HelloRootType)

This class defines the structure and behavior of the root Query type:

  • Extends OutputObjectTypeGenerator - Specialized for defining GraphQL object types
  • Specifies the source type - Uses Object since root types don't have a parent
  • Defines fields - Returns a list of fields available on this type
  • Implements data fetching - Each field specifies how to fetch its data

The type generator uses the builder pattern:

  1. Start with a scalar type (ScalarType.ofString())
  2. Convert to output field type (.toOutputFieldType())
  3. Name the field (.named("message"))
  4. Define the data fetcher (.fetching(this, source -> "Hello World!"))

Testing Your Endpoint

Once deployed, your endpoint is immediately available:

  1. Access the GraphQL Explorer - Navigate to Admin → APIs in the Brightspot CMS, find your endpoint, and click the three-dot menu → GraphQL Explorer

  2. Run a query:

    1
    query {
    2
    message
    3
    }
  3. Expected response:

    1
    {
    2
    "data": {
    3
    "message": "Hello World!"
    4
    }
    5
    }
  4. Explore introspection - The Explorer provides schema documentation, auto-complete, and query validation out of the box

Expanding Your Schema

Now that you have a working endpoint, here are common next steps:

Add More Fields

Expand your root query type with additional fields:

1
@Override
2
protected List<OutputField<SchemaContext, Object, ?>> getFields(
3
TypeLoadContext<SchemaContext> context
4
) {
5
return List.of(
6
ScalarType.ofString()
7
.toOutputFieldType()
8
.named("message")
9
.fetching(this, source -> "Hello World!"),
10
11
ScalarType.ofString()
12
.toOutputFieldType()
13
.named("version")
14
.fetching(this, source -> "1.0.0")
15
);
16
}

Add Arguments

Make your fields accept parameters:

1
ScalarType.ofString()
2
.toOutputFieldType()
3
.named("greet")
4
.withArguments(
5
ScalarType.ofString()
6
.toArgumentType()
7
.named("name")
8
.toArgument()
9
)
10
.fetching(this, (source, args) ->
11
"Hello, " + args.get("name", String.class) + "!"
12
)

Add Custom Types

Define additional types beyond the root query:

1
public class ArticleType extends OutputObjectTypeGenerator<SchemaContext, Article>
2
implements ReservedTypeGenerator {
3
4
@Override
5
protected List<OutputField<SchemaContext, Article, ?>> getFields(
6
TypeLoadContext<SchemaContext> context
7
) {
8
return List.of(
9
ScalarType.ofString()
10
.toOutputFieldType()
11
.named("title")
12
.fetching(this, Article::getTitle),
13
14
ScalarType.ofString()
15
.toOutputFieldType()
16
.named("body")
17
.fetching(this, Article::getBody)
18
);
19
}
20
}

Then reference it from your root type:

1
OutputObjectType.of(ArticleType.class)
2
.toOutputFieldType()
3
.named("article")
4
.withArguments(
5
ScalarType.ofString()
6
.toArgumentType()
7
.named("id")
8
.toArgument()
9
)
10
.fetching(this, (source, args) -> {
11
String id = args.get("id", String.class);
12
return Query.from(Article.class).where("_id = ?", id).first();
13
})

Add a Mutation Type

Enable write operations by defining a root mutation type:

1
@Override
2
protected OutputObjectType<SchemaContext, ?> getRootMutationType() {
3
return new OutputObjectTypeGenerator<SchemaContext, Object>() {
4
@Override
5
protected List<OutputField<SchemaContext, Object, ?>> getFields(
6
TypeLoadContext<SchemaContext> context
7
) {
8
return List.of(
9
// Define mutation fields here
10
);
11
}
12
}.named(TypeName.ofExact("Mutation"));
13
}

Next Steps

You now have a working GraphQL endpoint built with the framework! To deepen your understanding:

Core Concepts - Learn about schema names, contexts, data fetching patterns, and the type/field builder pattern in detail.

Type Generators - Master all seven GraphQL type kinds (type, interface, union, input, enum, scalar, directive) and advanced capabilities like field generators, type coercion, and complex data fetching.

Built-in Types - Explore the pre-built types for common use cases like UUIDs, dates, rich text, geo-spatial data, and file handling.

Security - Learn how to secure your endpoint with authentication, authorization, complexity limits, and rate throttling before going to production.