My name is Zhi Shen Yong and I am a software developer. Here you can find a few of the projects I have worked on in the past. Please feel free to contact me for any additional information.
In this post I go over setting up a simple Spring service with GraphQL.
First, use Spring Initializr to kickstart setting up a new Spring Boot project.
Use the settings Gradle, Java 8 (I am using Amazon Corretto 8), and Spring Boot 2.6.4.
In the build.gradle
of the new project, add a few essential dependencies:
implementation 'com.graphql-java:graphql-java:11.0'
implementation 'com.graphql-java:graphql-java-spring-boot-starter-webmvc:1.0'
implementation 'com.google.guava:guava:26.0-jre'
compileOnly "org.projectlombok:lombok:1.18.16"
annotationProcessor "org.projectlombok:lombok:1.18.16"
Set up the GraphQL schema in src/main/resources/
called schema.graphqls
.
It should contain the following code.
type Query {
parent(id: ID): Parent
}
type Parent {
id: ID
name: String
child: Child
}
type Child {
id: ID
name: String
}
We are doing a few things in this schema.
First, we’re defining the base query (or queries) that we can make to our GraphGL service.
We do this by including the name of the query, followed by the input it takes, followed by the type it returns, into type Query
.
In our project this is precisely parent(id: ID): Parent
.
We can query for a Parent
object, containing an id
, a name
, and a Child
object by making a query like this:
{
parent(id: "someId") {
id
name
}
}
We need to define the Parent
object that is returned, which is what the second block of code in the schema does.
We’ve included a Child
object in the Parent
object, which we define in the third and last block of code in the schema.graphqls
file.
Because we haven’t included a child
method in type Query
, getting a Child
object by itself from a base GraphQL query won’t be possible.
The only way we can get a Child
object will be through querying the Parent
that holds that Child
.
Next, create a folder called resolver
in src/main/java/
.
In it, create two classes called ParentResolver
and ChildResolver
.
The classes should look like this (respectively).
@Component
public class ParentResolver {
private static List<Map<String, String>> parentObjects = Arrays.asList(
ImmutableMap.of(
"id", "parent1",
"name", "PARENT1",
"childId", "child1"
),
ImmutableMap.of(
"id", "parent2",
"name", "PARENT2",
"childId", "child2"
)
);
public DataFetcher getParentByIdDataFetcher() {
return dataFetchingEnvironment -> {
String parentId = dataFetchingEnvironment.getArgument("id");
return parentObjects
.stream()
.filter(parent -> parent.get("id").equals(parentId))
.findFirst()
.orElse(null);
};
}
}
@Component
public class ChildResolver {
private static List<Map<String, String>> childObjects = Arrays.asList(
ImmutableMap.of(
"id", "child1",
"name", "CHILD1"
),
ImmutableMap.of(
"id", "child2",
"name", "CHILD2"
)
);
public DataFetcher getChildByIdDataFetcher() {
return dataFetchingEnvironment -> {
Map<String, String> parent = dataFetchingEnvironment.getSource();
String childId = parent.get("childId");
return parentObjects
.stream()
.filter(child -> child.get("id").equals(childId))
.findFirst()
.orElse(null);
};
}
}
In the first class, we are defining the method that will be called when we make the GraphQL query parent(id)
.
In either class, we have a dummy list of objects conforming to the model we gave those objects in schema.graphqls
;
we then have a method getZZZByIdDataFetcher
that essentially gets whatever id
we pass in the GraphQL query and tries to resolve it by finding it in the dummy list (hence, resolver).
Note that in ChildResolver
, since the resolver method is not called from the base query but instead called while we are expanding a Parent
object, we get the childId
not through the environment but through the Parent
object that calls the method.
Next, create a new folder called service
in src/main/java/
.
Create a Java class named GraphQLService
in that folder.
The contents of that class should be as follows:
@Service
public class GraphQLService {
private GraphQL graphQL;
@Bean
public GraphQL graphQL() {
return graphQL;
}
@Autowired
ParentResolver parentResolver;
@Autowired
ChildResolver childResolver;
@PostConstruct
public void init() throws IOException {
URL url = Resources.getResource("schema.graphqls");
String sdl = Resources.toString(url, Charsets.UTF_8);
GraphQLSchema graphQLSchema = buildSchema(sdl);
this.graphQL = GraphQL.newGraphQL(graphQLSchema).build();
}
private GraphQLSchema buildSchema(String sdl) {
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
RuntimeWiring runtimeWiring = buildWiring();
SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
.type(newTypeWiring("Query")
.dataFetcher("parent", parentResolver.getParentByIdDataFetcher())
)
.type(newTypeWiring("Parent")
.dataFetcher("child", childResolver.getChildByIdDataFetcher())
)
.build();
}
}
There are a few things happening here.
First, we’re auto-wiring the resolvers we made before into this class.
In the init
function (annotated with @PostConstruct
so it automatically runs) we create a GraphQLSchema
from the schema.graphqls
file we created earlier and use it to build our GraphQL
instance.
Notice that this init
function calls buildSchema
, which also calls buildWiring
.
In buildWiring
, we are essentially mapping queries defined in schema.graphqls
to functions we defined in the resolver classes.
newTypeWiring("Query")
shows base queries that can be run, while newTypeWiring("Parent")
shows queries that can be run to expand upon a parent
query (which returns a Parent
object);
in our case, Parent
contains a reference to a Child
object, so we need to define a wiring to let GraphQL know how to get a Child
from a Parent
.
And that’s it! That’s all it takes to set up a super simple and lightweight Spring service running GraphQL.
Build our project with ./gradlew build
; you should see this message after a while.
...
BUILD SUCCESSFUL in 2s
7 actionable tasks: 7 up-to-date
Then, run our project using ./gradlew bootRun
. If all is well, you should see this message after a few seconds.
...
2022-03-15 23:23:21.100 INFO 55237 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2022-03-15 23:23:21.117 INFO 55237 --- [ main] c.c.SampleApplication : Started SampleApplication in 3.007 seconds (JVM running for 3.554)
<==========---> 80% EXECUTING [17s]
> :bootRun
It will continue to stay at 80% EXECUTING
, which indicates that the Spring server is up and running.
To close the server, press control + c
.
GraphQL automatically creates an endpoint /graphql
for us to query although, unfortunately, there is no easily accessible UI (like Swagger) for us to look at.
Instead, we need to get something like GraphQL-Playground to help us visualize our queries.
With GraphQL-Playground open, hit the Local URL
option and enter in http://localhost:8080/graphql
.
Then, in the resulting query page, paste this into the left text box:
{
parent(id: "parent1") {
id
name
child
}
}
Once you click the Run
button, you should see this result:
{
"data": {
"parent": {
"id": "parent",
"name": "PARENT1",
"child": {
"id": "child1",
"name": "CHILD1"
}
}
}
}
You can edit the query to get parent2
instead, or add/remove whichever fields you want GraphQL to return.
That just about wraps up this post! Again, this is a super lightweight starter service showing only basic functionalities of GraphQL. There are many other things GraphQL can do and other ways of implementing them in Spring that I’ll explore in other posts. As for where to go from here?
parent(name: String): [Parent]
)