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:
1schema {2query: Query3}45type Query {6message: String7}
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:
- API Endpoint - Extends
AbstractGraphQLApiEndpointand defines the URL path - Schema Loader - Extends
GraphQLJavaSchemaLoaderand defines your schema structure - Schema Context - Implements
SchemaContextand provides shared functionality - 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
- API Endpoint
- Schema Loader
- Schema Context
- Type Generator
18@Recordable.DisplayName("Hello GraphQL Framework")9public class HelloEndpoint extends AbstractGraphQLApiEndpoint implements Singleton {1011@Override12public Set<String> getPaths() {13return Set.of("/hello-graphql-framework");14}1516@Override17protected GraphQLSchemaLoader getSchemaLoader() {1819}20}21
1678910@Override11protected void initialize() {1213}1415@Override16public GraphQLSchemaSettings getSettings() {17return GraphQLSchemaSettings.newBuilder().build();18}1920@Override2122return context;23}2425@Override262728.named(TypeName.ofExact("Query"));29}30}31
13public class HelloSchemaContext implements SchemaContext {45678this.loader = loader;9}1011@Override1213return loader;14}15}16
18910@Override111213) {14return List.of(15ScalarType.ofString()16.toOutputFieldType()17.named("message")18.fetching(this, source -> "Hello World!")19);20}21}22
The result of the code above is a fully functioning GraphQL endpoint with the following generated schema:
1schema {2query: Query3}45type Query {6message: String7}
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:
- Creates schema settings and context
- Builds the root query type
- Recursively discovers and registers all referenced types
- Validates the schema against GraphQL spec
- 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
Objectsince 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:
- Start with a scalar type (
ScalarType.ofString()) - Convert to output field type (
.toOutputFieldType()) - Name the field (
.named("message")) - Define the data fetcher (
.fetching(this, source -> "Hello World!"))
Testing Your Endpoint
Once deployed, your endpoint is immediately available:
-
Access the GraphQL Explorer - Navigate to Admin → APIs in the Brightspot CMS, find your endpoint, and click the three-dot menu → GraphQL Explorer
-
Run a query:
1query {2message3} -
Expected response:
1{2"data": {3"message": "Hello World!"4}5} -
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@Override2protected List<OutputField<SchemaContext, Object, ?>> getFields(3TypeLoadContext<SchemaContext> context4) {5return List.of(6ScalarType.ofString()7.toOutputFieldType()8.named("message")9.fetching(this, source -> "Hello World!"),1011ScalarType.ofString()12.toOutputFieldType()13.named("version")14.fetching(this, source -> "1.0.0")15);16}
Add Arguments
Make your fields accept parameters:
1ScalarType.ofString()2.toOutputFieldType()3.named("greet")4.withArguments(5ScalarType.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:
1public class ArticleType extends OutputObjectTypeGenerator<SchemaContext, Article>2implements ReservedTypeGenerator {34@Override5protected List<OutputField<SchemaContext, Article, ?>> getFields(6TypeLoadContext<SchemaContext> context7) {8return List.of(9ScalarType.ofString()10.toOutputFieldType()11.named("title")12.fetching(this, Article::getTitle),1314ScalarType.ofString()15.toOutputFieldType()16.named("body")17.fetching(this, Article::getBody)18);19}20}
Then reference it from your root type:
1OutputObjectType.of(ArticleType.class)2.toOutputFieldType()3.named("article")4.withArguments(5ScalarType.ofString()6.toArgumentType()7.named("id")8.toArgument()9)10.fetching(this, (source, args) -> {11String id = args.get("id", String.class);12return Query.from(Article.class).where("_id = ?", id).first();13})
Add a Mutation Type
Enable write operations by defining a root mutation type:
1@Override2protected OutputObjectType<SchemaContext, ?> getRootMutationType() {3return new OutputObjectTypeGenerator<SchemaContext, Object>() {4@Override5protected List<OutputField<SchemaContext, Object, ?>> getFields(6TypeLoadContext<SchemaContext> context7) {8return List.of(9// Define mutation fields here10);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.