Create own GraphQL Spring Boot Starter
By Christian Abegg
December 9, 2024 • 17min read
In this article, we’ll create a Spring Boot starter which will make a GraphQL endpoint available to all applications where the starter is included.
We will cover a similar functionality as the info
endpoint of the actuator starter: displaying the applications version. But instead of a REST API, we’ll provide a GraphQL endpoint.
The resulting code is available at https://github.com/abeggchr/shared-graphql-spring-boot-starter.
Why should I create my own starter?
Spring Boot starters typically contain everything to get started with a given technology (such as spring-boot-starter-web
).
This convenience is the primary reason for using a starter.
However, Spring Boot starters can also serve as a way to share functionality accross multiple applications (e.g., spring-boot-starter-actuator
), such as displaying the version information (as described in this article)
, tracking frontend logs
, or loading customer data.
With a Spring Boot starter,
we can avoid re-implementing the same logic repeatedly in each consuming application.
Of cours, a Spring Boot starter is just one option to achieve that goal. It can be especially useful when other options , like shared services or regular libraries, are not feasible in a given context.
1. Set up a Spring Boot application
Create the Spring application which will later consume the starter. Start from where the Spring’s “Building a GraphQL service” left off and copy the completed
solution into our application
folder.
shared-graphql-spring-boot-starter└ application
Start the application, open http://localhost:8080/graphiql?path=/graphql
, and check that you can execute GraphQL queries like:
query bookDetails { bookById(id: "book-1") { id name }}
2. Set up a Spring Boot starter
Create an empty Spring Boot starter project alongside the application.
shared-graphql-spring-boot-starter├ application└ graphql-info-spring-boot-starter
Either create your Java project structure with the IDE or use Spring Initializr.
For the name, consider Springs recommendation to use the following pattern: {own-name}-spring-boot-starter
.
Ensure you have the necessary dependencies for both Spring Boot and Spring for GraphQL in your build file (i.e. build.gradle.kts
)
plugins { java `maven-publish`}
group = "org.example"version = "1.0-SNAPSHOT"
// to match the applications java versionjava { toolchain { languageVersion = JavaLanguageVersion.of(17) }}
repositories { mavenCentral()}
dependencies { compileOnly("org.springframework.boot:spring-boot-starter-parent:3.3.1") compileOnly("org.springframework.boot:spring-boot-starter-graphql:3.3.1")
annotationProcessor("org.springframework.boot:spring-boot-autoconfigure-processor:3.3.1")}
3. Create a controller in the starter
Create a controller in the starter that handles GraphQL queries. Make sure you use the @QueryMapping
annotation for GraphQL query handlers.
package org.example;
import org.springframework.graphql.data.method.annotation.QueryMapping;import org.springframework.stereotype.Controller;
@Controllerpublic class GraphQLController {
@QueryMapping public String info() { return "version=0.0.1"; // hard coded for now, we'll make it configurable later }}
4. Add auto-configuration class
Next, create an auto-configuration class that will automatically configure the GraphQLController
bean when the starter is included in a Spring Boot application.
package org.example;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;
@Configurationpublic class GraphQLStarterAutoConfiguration {
@Bean @ConditionalOnMissingBean public GraphQLController graphQLController() { return new GraphQLController(); }}
This class ensures that the GraphQLController
bean is instantiated and registered when the starter is present in the application classpath.
The @ConditionalOnMissingBean
annotation makes sure, the bean will get loaded by Spring only if there is no other bean of this type present in the context.
Note that it is best-practice to split the auto-configuration part of the starter and the implementation into different projects. The directory structure would look as follows. For the sake of simplicity, we are using a single project here.
shared-graphql-spring-boot-starter├ application└ starter ├ graphql-info-spring-boot-autoconfigure └ graphql-info-spring-boot-starter
5. Register the auto-configuration class
To ensure that Spring Boot picks up the auto-configuration class, you need to register it. Create a file at src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
and add the fully qualified name of the auto-configuration class:
org.example.GraphQLStarterAutoConfiguration
Note that in older Spring versions, the spring.factories
file was used for that purpose.
Refer to the Spring documentation for more information about creating your own auto-configuration.
6. Add a GraphQL schema to the starter
Place the schema file in the src/main/resources/graphql
folder. For example, create a schema.graphqls
file:
extend type Query { info: String}
Spring will automatically merge the starters schema with the applications schema when the application starts up.
Note the extend
keyword. Without that keyword, Spring would try to merge two different Query
types resulting in an error like errors=['Query' type [@14:1] tried to redefine existing 'Query' type [@1:1]]
. With extend
you can avoid this error.
7. Enable automatic schema merging in consuming application
Set the application property spring.graphql.schema.locations
to classpath*:graphql/**/
in the consuming applications application.properties
file, so that schema files are detected in all dependencies dependencies. The default value is classpath:graphql/**/
(without *
) and does not merge schemas.
spring.graphql.schema.locations=classpath*:graphql/**/
8. Use the starter in the consuming application
Once your starter is ready, publish it to a Maven repository (or use a local Maven install), and include it in the build.gradle
file of the consuming application.
implementation 'org.example:graphql-info-spring-boot-starter:1.0-SNAPSHOT'
With this setup, when the consuming application starts, it will have access to the /graphql
endpoint with the info
query automatically available.
9. Query the GraphQL endpoint
Start the application, open http://localhost:8080/graphiql?path=/graphql
and check that the following query returns the hard-coded value version=0.0.1
.
query info { info}
11. Configuratively set the version
You can allow users of your starter to customize the endpoint behavior via application.properties
. For example, you can inject the version number from the application.properties
by:
- creating a
@ConfigurationProperties
annotated class which defines the properties key (i.e.info.app.version
) - configuring the new class to be injectable by modifying
GraphQLStarterAutoConfiguration
- configuring the version value in
application.properties
in the consuming application - injecting the version value to the
GraphQLController
GraphQLStarterProperties.java
package org.example;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(value = "info.app")public class GraphQLStarterProperties { private String version;
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }}
GraphQLStarterAutoConfiguration.java
:
package org.example;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;
@Configuration@EnableConfigurationProperties(GraphQLStarterProperties.class)public class GraphQLStarterAutoConfiguration {
@Autowired private GraphQLStarterProperties properties;
@Bean @ConditionalOnMissingBean public GraphQLController graphQLController() { return new GraphQLController(properties); }}
application.properties
:
info.app.version=1.0.0
GraphQLController.java
:
package org.example;
import org.springframework.graphql.data.method.annotation.QueryMapping;import org.springframework.stereotype.Controller;
@Controllerpublic class GraphQLController {
private final GraphQLStarterProperties properties;
public GraphQLController(GraphQLStarterProperties properties) { this.properties = properties; }
@QueryMapping public String info() { return "version=%s".formatted(this.properties.getVersion()); }}
The graphql query info
now returns the configured value 1.0.0
.
12. Add parameters
When you add additional query or mutation methods to the starters schema.graqphql
and GraphQLController
,
you might run into parameter-related errors.
In that case, adding the -parameters
compiler argument to the starters build script helps,
as described in the [Spring 6.x release notes]{https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x#parameter-name-retention}.
tasks.withType<JavaCompile>() { options.compilerArgs.add("-parameters")}
13. Troubleshooting
To get more insight into the GraphQL schema loading and query mapping process, you can enable debug-level logging in the consuming applications application.properties
:
logging.level.org.springframework.graphql=DEBUG
Summary
We created a Spring Boot starter project which adds a GraphQL endpoint to the Spring application when used. The starter project can now be extended with additional functionality or more means to configure its functionality.
References
- Spring > Build Systems > Starters
- Spring > Creating Your Own Auto-configuration
- Spring > Creating Your Own Auto-configuration > Creating Your Own Starter
Credits
Photo by Janko Ferlič on Unsplash