This tutorial is for those who want to build GraphQL server in Java. Some knowledge of Spring Boot and Java development is required, and while we briefly covered GraphQL, this tutorial focuses on developing a GraphQL server in Java.
GraphQL in three minutes
GraphQL is a query language that retrieves data from a server. REST, SOAP, and gRPC can be replaced in some scenarios. Let’s say we want to get the details of a book from the back end of an online marketplace.
You use GraphQL to send the following query to the server to get the details of the book with id “123” :
{
bookById(id: "book-1"){
id
name
pageCount
author {
firstName
lastName
}
}
}
Copy the code
This is not a piece of JSON(although it looks a lot like it), but rather a GraphQL query. It basically means:
- Query a book with a specific ID
- Give me the id, name, pageCount, author of that book
- For author I want to know firstName and lastName
The response is plain JSON:
{
"bookById":
{
"id":"book-1"."name":"Harry Potter and the Philosopher's Stone"."pageCount":223."author": {
"firstName":"Joanne"."lastName":"Rowling"}}}Copy the code
Static typing is one of the most important features of GraphQL: the server knows exactly what every object you want to query looks like and any client can “introspect” the server and request “schema”. Schema describes what the query might look like and what fields you can retrieve. (Note: When referring to schema, we usually mean “GraphQL Schema”, not like “JSON Schema” or “Database schema “.)
The schema for the query mentioned above is described as follows:
type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
Copy the code
This tutorial will focus on how to implement a GraphQL server with this schema in Java.
We’ve only touched on some of the basic features of GraphQL. Learn/graphql.github. IO /learn/
GraphQL Java preview
GraphQL Java is the Java(server) implementation of GraphQL. GraphQL Java Github org has several Git repositories. One of the most important is the GraphQL Java engine, which is the foundation for everything else.
The GraphQL Java engine itself only cares about executing queries. It does not handle any HTTP or JSON-related topics. Therefore, we will use the GraphQL Java Spring Boot Adapter, which exposes the API over HTTP via Spring Boot.
The main steps to create the GraphQL Java server are as follows:
- Define GraphQL Schema.
- Decide how to get the actual data you want to query.
Our example API: Get book details
Our sample application will be a simple API to get details about a particular book. This API is not exhaustive, but it will suffice for this tutorial.
Create a Spring Boot application
The easiest way to create a Spring application is to use “Spring Initializr” on start.spring. IO /.
Options:
- Gradle Project
- Java
- Spring Boot 2.1.x
For the project metadata we used:
- Group:
com.graphql-java.tutorial
- Artifact:
book-details
For dependency, we chose Web only.
Click Generate Project and you can use the Spring Boot app. All of the files and paths mentioned below are related to the Generate Project.
We added three dependencies to our project in the Dependencies section of build.gradle:
The first two were GraphQL Java and GraphQL Java Spring, and then we added Google Guava. Guava isn’t necessary, but it will make our lives a little easier.
The dependency looks like this:
dependencies {
implementation 'com. Graphql - Java: graphql - Java: 11.0' // NEW
implementation 'com. Graphql - Java: graphql - Java - spring - the boot - starter - webmvc: 1.0' // NEW
implementation : 'com. Google. Guava guava: 26.0 jre' // NEW
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Copy the code
Schema
We are creating a new file schema.graphqls under SRC /main/ Resources that contains the following:
type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
Copy the code
This schema defines a top-level field (in the Query type):bookById, which returns details about a particular book.
It also defines the type Book, which contains ID, name, pageCount, and Author. Author belongs to the author type and is defined after Book.
The domain-specific language shown above for describing schemas is called schema definition Language or SDL. More details can be found here.
Once we have the file, we need to “bring it to life” by reading the file and parsing it, then adding code to get data for it.
We are com. Graphqljava. Tutorial. Bookdetails package created a new GraphQLProvider class. The init method creates a GraphQL instance:
@Component
public class GraphQLProvider {
private GraphQL graphQL;
@Bean
public GraphQL graphQL(a) {
return graphQL;
}
@PostConstruct
public void init(a) 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) {
// TODO: we will create the schema here later }}Copy the code
We used the Guava resource to read files from the classpath and create GraphQLSchema and GraphQL instances. This GraphQL instance is exposed as a Spring Bean using the GraphQL() method with the @Bean annotation. The GraphQL Java Spring adapter will use this GraphQL instance to make our schema HTTP accessible via the default URL /GraphQL.
What we also need to do is implement the buildSchema method, which creates the GraphQLSchema instance and concatenates the code to get the data:
@Autowired
GraphQLDataFetchers graphQLDataFetchers;
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(a) {
return RuntimeWiring.newRuntimeWiring()
.type(newTypeWiring("Query")
.dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
.type(newTypeWiring("Book")
.dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
.build();
}
Copy the code
TypeDefinitionRegistry is a parsed version of a Schema file. SchemaGenerator combines TypeDefinitionRegistry with RuntimeWiring to actually generate GraphQLSchema.
BuildRuntimeWiring uses graphQLDataFetchersbean to register two Datafetchers:
- One is to retrieve a book with a specific ID.
- One is to find an author for a particular book.
The next section explains the DataFetcher and how to implement the GraphQLDataFetchersbean.
In general, the process for creating GraphQL and GraphQLSchema instances looks like this:
DataFetchers
Perhaps the most important concept of the GraphQL Java server is the Datafetcher: When a query is executed, the Datafetcher retrieves data for a field.
When GraphQL Java executes a query, it invokes the appropriate Datafetcher for each field encountered in the query. DataFetcher is a single method interface with a single type of parameter DataFetcherEnvironment:
public interface DataFetcher<T> {
T get(DataFetchingEnvironment dataFetchingEnvironment) throws Exception;
}
Copy the code
Important: Each field in the schema has a DataFetcher associated with it. If no DataFetcher is specified for a particular field, the default PropertyDataFetcher is used. We’ll discuss this in more detail later.
We’re creating a new class GraphQLDataFetchers with a sample list of books and authors.
The full implementation looks like this, which we’ll examine in detail shortly:
@Component
public class GraphQLDataFetchers {
private static List<Map<String, String>> books = Arrays.asList(
ImmutableMap.of("id"."book-1"."name"."Harry Potter and the Philosopher's Stone"."pageCount"."223"."authorId"."author-1"),
ImmutableMap.of("id"."book-2"."name"."Moby Dick"."pageCount"."635"."authorId"."author-2"),
ImmutableMap.of("id"."book-3"."name"."Interview with the vampire"."pageCount"."371"."authorId"."author-3"));private static List<Map<String, String>> authors = Arrays.asList(
ImmutableMap.of("id"."author-1"."firstName"."Joanne"."lastName"."Rowling"),
ImmutableMap.of("id"."author-2"."firstName"."Herman"."lastName"."Melville"),
ImmutableMap.of("id"."author-3"."firstName"."Anne"."lastName"."Rice"));public DataFetcher getBookByIdDataFetcher(a) {
return dataFetchingEnvironment -> {
String bookId = dataFetchingEnvironment.getArgument("id");
return books
.stream()
.filter(book -> book.get("id").equals(bookId))
.findFirst()
.orElse(null);
};
}
public DataFetcher getAuthorDataFetcher(a) {
return dataFetchingEnvironment -> {
Map<String,String> book = dataFetchingEnvironment.getSource();
String authorId = book.get("authorId");
return authors
.stream()
.filter(author -> author.get("id").equals(authorId))
.findFirst()
.orElse(null); }; }}Copy the code
The data source
We get the book and author from the static list in the class. This is just for this tutorial. It’s important to understand that GraphQL does not specify where data comes from. That’s the power of GraphQL: it can come from an in-memory static list, a database, or an external service.
Book DataFetcher
Our first method, getBookByIdDataFetcher, returns a DataFetcher implementation that takes a DataFetcherEnvironment and returns a book. In this case, that means we need to get the ID parameter from the bookById field and find the book with that particular ID.
String bookId = dataFetchingEnvironment.getArgument(“id”); “Id” is the “ID” in the bookById query field in the schema:
type Query {
bookById(id: ID): Book
}
...
Copy the code
Author DataFetcher
The second method, getAuthorDataFetcher, returns a Datafetcher that is used to get the author of a particular book. In contrast to the Book DataFetcher described earlier, we have no arguments, but a Book instance. The result of the DataFetcher from the parent field is available via getSource. This is an important concept to understand: The Datafetcher for each field in GraphQL is called in a top-down fashion, and the result of the parent field is the source attribute of the child Datafetcherenvironment.
We then get the authorId using the previously obtained book and look up the specific author in the same way we look up the specific book.
Default DataFetchers
We only implemented two Datafetchers. As mentioned above, if you do not specify one, the default PropertyDataFetcher is used. In our case, it means book.id, book.name, book.pagecount, author.id, author.firstName, and author.lastName all have a default PropertyDataFetcher associated with them.
PropertyDataFetcher tries to find properties on Java objects in a variety of ways. In the case of java.util.map, it is simply a keystroke to find the property. This is great for us because the keys mapped to book and Author are the same as the fields specified in the schema. For example, in the schema we defined for the book type, the fields pageCount and Book DataFetcher return a Map with key pageCount. Because the field name is the same as the key in the Map (” pageCount “), PropertyDateFetcher works fine.
Let’s say we have a mismatch and the Book Map has a key that is totalPages instead of pageCount. This will cause the pageCount value for each book to be null because PropertyDataFetcher cannot get the correct value. To solve this problem, you must register a new DataFetcher for book.pagecount. It looks something like this:
// In the GraphQLProvider class
private RuntimeWiring buildWiring(a) {
return RuntimeWiring.newRuntimeWiring()
.type(newTypeWiring("Query")
.dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
.type(newTypeWiring("Book")
.dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher())
// This line is new: we need to register the additional DataFetcher
.dataFetcher("pageCount", graphQLDataFetchers.getPageCountDataFetcher()))
.build();
}
// In the GraphQLDataFetchers class
// Implement the DataFetcher
public DataFetcher getPageCountDataFetcher(a) {
return dataFetchingEnvironment -> {
Map<String,String> book = dataFetchingEnvironment.getSource();
return book.get("totalPages"); }; }...Copy the code
The DataFetcher will solve this problem by looking for the correct key in the Book Map. (Again: we don’t need this in our example because we don’t have name mismatches)
Try out the API
That’s all you need to build a working GraphQL API. Start in the Spring the Boot after application, can use the API on http://localhost:8080/graphql.
The easiest way to try and explore the GraphQL API is to use GraphQL Playground’s tools. Download and run it.
Start, you will be asked to enter a URL, enter http://localhost:8080/graphql.
After that, you can query our sample API, and you should get the results we mentioned at the beginning. It should look something like this:
Complete sample source code and more information
The full project and the full source code can be found here :github.com/graphql-jav…
More information about GraphQL Java can be found in the documentation.
For any questions, we also have Spectrum Chat for discussion.
For direct feedback, you can also find us on our GraphQL Java Twitter Account.
Getting Started with GraphQL Java and Spring Boot
Start using GraphQL Java and Spring Boot
Translation: TomorJM