13.01.2018
Spring Boot - GraphQL
This article explains how to add a GraphQL interface to a Spring Boot application. An existing application with data model, repository and services is used and extended so that the data in the database can be accessed using GraphQL. The starting point is the following application: ItemStore The finished application has a GraphQL interface that can be accessed with cURL or Postman.
GraphQL Libraries
First, the following dependencies are added to the pom file of the Spring Boot application:
<dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-spring-boot-starter</artifactId> <version>3.6.0</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-java-tools</artifactId> <version>3.2.0</version> </dependency>
GraphQL Schema
The next step is to describe the data model and queries in a GraphQL schema.
schema { query : Query } type Query { allItems: [Item] item(id: Long): Item itemByDescription(description: String): [Item] } type Item { id : Long description: String location: String formattedDate: String }
Three queries are defined under Query
. One that returns all items (allItems
) and one (item(id: Long)
) that uses the id of an Item
to search for the item. In the third query (itemByDescription(description: String)
), items are searched based on their description.
The square brackets indicate that a list is expected as the return value.
At the end the Item
is described with all its attributes and data types. It should be noted that the date has the type String
, because the datatype date is not supported. More on this later in the article.
GraphQL Fetcher
Next, so-called DataFetchers are created, which serve to get the data from the database. In general, a DataFetcher is required for each query defined in the GraphQL schema.
ItemDataFetcher
For the Query item(id: Long)
package org.hameister.service; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import org.hameister.model.Item; import org.hameister.repository.ItemRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * Created by hameister on 12.01.18. */ @Component public class ItemDataFetcher implements DataFetcher<Item> { @Autowired ItemRepository itemRepository; @Override public Item get(DataFetchingEnvironment dataFetchingEnvironment) { Long id = dataFetchingEnvironment.getArgument("id"); return itemRepository.findOne(id); } }
ItemByDescriptionFetcher
For the Query itemByDescription(description: String)
package org.hameister.service; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import org.hameister.model.Item; import org.hameister.repository.ItemRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; /** * Created by hameister on 13.01.18. */ @Component public class ItemByDescriptionFetcher implements DataFetcher<List<Item>>{ @Autowired private ItemRepository itemRepository; @Override public List<Item> get(DataFetchingEnvironment environment) { String description = environment.getArgument("description"); return itemRepository.findByDescription(description); } }
ItemListDataFetcher
For the Query allItems
package org.hameister.service; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import org.hameister.model.Item; import org.hameister.repository.ItemRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; /** * Created by hameister on 12.01.18. */ @Component public class ItemListDataFetcher implements DataFetcher<List<Item>> { @Autowired ItemRepository itemRepository; @Override public List<Item> get(DataFetchingEnvironment dataFetchingEnvironment) { return itemRepository.findAll(); } }
GraphQL Service
The DataFetchers are used by the GraphQL service to retrieve and prepare the data from the database:
package org.hameister.service; import graphql.GraphQL; import graphql.schema.GraphQLSchema; import graphql.schema.idl.RuntimeWiring; import graphql.schema.idl.SchemaGenerator; import graphql.schema.idl.SchemaParser; import graphql.schema.idl.TypeDefinitionRegistry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.io.File; import java.io.IOException; /** * Created by hameister on 12.01.18. */ @Service public class GraphQLService { @Value("classpath:items.graphql") Resource schemaResource; @Autowired private ItemDataFetcher itemDataFetcher; @Autowired private ItemListDataFetcher allItemFetcher; @Autowired private ItemByDescriptionFetcher itemByDescriptionFetcher; private GraphQL graphQL; @PostConstruct public void loadGraphQLSchema() throws IOException { File schema = schemaResource.getFile(); TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser().parse(schema); RuntimeWiring runtimeWiring = initWiring(); GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry,runtimeWiring); graphQL = GraphQL.newGraphQL(graphQLSchema).build(); } private RuntimeWiring initWiring() { return RuntimeWiring.newRuntimeWiring() .type("Query", typeWiring -> typeWiring .dataFetcher("allItems", allItemFetcher) .dataFetcher("itemByDescription", itemByDescriptionFetcher) .dataFetcher("item", itemDataFetcher)).build(); } public GraphQL getGraphQL() { return graphQL; } }
The service reads the GraphQL schema from the file items.graphql
and initializes the DataFetchers and creates a GraphQL object that the controller uses to perform the queries.
REST Controller
Finally, a REST controller is created using a POST method against which the queries are executed by Postman or cURL.
package org.hameister.controller; import graphql.ExecutionResult; import org.hameister.service.GraphQLService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; /** * Created by hameister on 12.01.18. */ @RestController public class GraphQLItemController { @Autowired GraphQLService graphQLService; @PostMapping(value = "/graphql/items") public ResponseEntity<Object> allItems(@RequestBody String query) { ExecutionResult result = graphQLService.getGraphQL().execute(query); return new ResponseEntity<>(result, HttpStatus.OK); } }
Date of the Item
In the GraphQL schema, the data type for the date was String
, because GraphQL does not support the LocalDate
data type. For a formatted date to be returned as a String in a query, a small change must be made to the class Item
.
package org.hameister.model; import javax.persistence.*; import java.time.LocalDate; /** * Created by hameister on 24.12.16. */ @Entity @Table(name = "Item") public class Item { public Item() { } public Item(String description, String location, LocalDate itemdate) { this.description = description; this.location = location; this.itemdate = itemdate; } public Item(Long id,String description, String location, LocalDate itemdate) { this.id=id; this.description = description; this.location = location; this.itemdate = itemdate; } @Id @GeneratedValue Long id; @Column(name = "description") private String description; @Column(name = "location") private String location; @Column(name = "itemdate") private LocalDate itemdate; private transient String formattedDate; // Getter and setter public String getFormattedDate() { return getItemdate().toString(); } }
For the sake of simplicity, the transient variable formattedDate
has simply been added and a getter for the variable.
The project structure should look like this after the changes.

When starting the application, there is another REST endpoint under: localhost:8080/graphql/items
This can be used, for example, with Postman:

On the screenshot you can see that in the POST request all three queries are sent at once.
The application then delivers the following response:
{ "errors": [], "data": { "itemByDescription": [ { "id": 1, "description": "Tasse", "location": "Regal A" }, { "id": 3, "description": "Tasse", "location": "Regal A" } ], "item": { "location": "Regal A", "description": "Teller", "formattedDate": "2016-12-09" }, "allItems": [ { "id": 1, "location": "Regal A", "description": "Tasse", "formattedDate": "2016-12-08" }, { "id": 2, "location": "Regal A", "description": "Teller", "formattedDate": "2016-12-09" }, { "id": 3, "location": "Regal A", "description": "Tasse", "formattedDate": "2016-12-10" } ] }, "extensions": null }
The complete source code can be found on Github at ItemStore in the branch graphQL.