Using GraphQL in a Spring Boot Application
11 CommentsYou might have heard about GraphQL and how Facebook uses GraphQL in their mobile applications. In this blog, I will show you how to implement GraphQL in a Spring Boot application and let’s see what kind of magic does GraphQL provides.
Why GraphQL?
If you do not know about GraphQL then you are in the right place. GraphQL is a query language for REST API endpoints. GraphQL isn’t tied to any specific database or storage engine. Instead, GraphQL is backed by your existing code and data.
The main advantage of using GraphQL are:
- No need to create multiple API (Application Programming Interface) endpoints in an application unlike in REST where we expose multiple endpoints to retrieve data like this.
https://localhost:8080/person https://localhost:8080/person/{id}
- Using GraphQL, we get the exact data we need or request. This is unlike in REST implementation, where we make an HTTP GET call to get a JSON response even if we are looking at the values for a few attributes. For example, when we query a REST API, we get the complete response in JSON format like below even we require only the id and name
{"id": "100","name": "Vijay","age":34"city": "Faridabad","gender": "Male"}
- Integrating front-end applications (like mobile apps) with GraphQL are fast & responsive over REST API’s
In this blog, we will see how to build a Spring Boot application to store books. We then integrate the same application and query for books using GraphQL.
Note: The complete source code of this tutorial is available on GitHub and its URL will be shared at the end of this blog. We will focus more on key classes/files in this application with their explanation.
Let’s start developing our Spring Boot application with GraphQL. I am using IntelliJ IDEA Ultimate. However, you may use any IDE of your choice.
Creating the Application
Visit Spring Initializr or use IntelliJ IDEA Ultimate to generate a Spring Boot application with dependencies like Web, HSQLDB, Spring Boot 2.1.4. It will be a Maven project with JDK 1.8.
The generated POM is this.
<modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> </parent> <artifactId>springboot.graphql.app</artifactId> <name>springboot-graphql-app</name> <description>Demo project for Spring Boot with Graph QL</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <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> </dependencies>
Adding an API Endpoint
Let us start with a BookController
and add a POST request handler, like this.
package graphqlapp.controller; import graphqlapp.service.GraphQLService; import graphql.ExecutionResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/rest/books") @RestController public class BookController { private static Logger logger = LoggerFactory.getLogger(BookController.class); private GraphQLService graphQLService; @Autowired public BookController(GraphQLService graphQLService) { this.graphQLService=graphQLService; } @PostMapping public ResponseEntity<Object> getAllBooks(@RequestBody String query){ logger.info("Entering getAllBooks@BookController"); ExecutionResult execute = graphQLService.getGraphQL().execute(query); return new ResponseEntity<>(execute, HttpStatus.OK); } }
Adding a Model Class
Next, we will add a model class to represent a book. We will name it Book
The code of the model class is this.
package graphqlapp.model; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Table @Entity public class Book { @Id private String isn; private String title; private String publisher; private String publishedDate; private String[] author; public Book() { } public Book(String isn, String title, String publisher, String publishedDate, String[] author) { this.isn = isn; this.title = title; this.publisher = publisher; this.publishedDate = publishedDate; this.author = author; } public String getIsn() { return isn; } public void setIsn(String isn) { this.isn = isn; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getPublisher() { return publisher; } public void setPublisher(String publisher) { this.publisher = publisher; } public String getPublishedDate() { return publishedDate; } public void setPublishedDate(String publishedDate) { this.publishedDate = publishedDate; } public String[] getAuthor() { return author; } public void setAuthor(String[] author) { this.author = author; } }
Creating a Book Repository
The repository of this example extends JpaRepository
, like this.
package graphqlapp.repository; import graphqlapp.model.Book; import org.springframework.data.jpa.repository.JpaRepository; public interface BookRepository extends JpaRepository<Book, String> { }
Adding a GraphQL Schema
Next, we will write a GraphQL schema, namedbooks.graphql
in our resource
folder.
schema{ query:Query } type Query{ allBooks: [Book] book(id: String): Book } type Book{ isn:String title:String publisher:String author:[String] publishedDate:String }
This is a very important file and is the backbone of GraphQL. Here, we define a schema, which you can relate with a Query. We also need to tell the type of query which is triggered by any front-end applications.
In this example, we have shown two types:
- When a user queries all the books (by using
allBooks
) then the application will return an array ofBook
. - When a user queries for a specific book by passing the
id
, then the application will return aBook
object.
Adding a GraphQL Service
Next, we need to add a GraphQL service. Lets’ name it as GraphQLService
.
package graphqlapp.service; import graphqlapp.model.Book; import graphqlapp.repository.BookRepository; import graphqlapp.service.datafetcher.AllBooksDataFetcher; import graphqlapp.service.datafetcher.BookDataFetcher; 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.slf4j.Logger; import org.slf4j.LoggerFactory; 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; import java.util.stream.Stream; @Service public class GraphQLService { private static Logger logger = LoggerFactory.getLogger(GraphQLService.class); private BookRepository bookRepository; private AllBooksDataFetcher allBooksDataFetcher; private BookDataFetcher bookDataFetcher; @Value("classpath:books.graphql") Resource resource; private GraphQL graphQL; @Autowired public GraphQLService(BookRepository bookRepository, AllBooksDataFetcher allBooksDataFetcher, BookDataFetcher bookDataFetcher) { this.bookRepository=bookRepository; this.allBooksDataFetcher=allBooksDataFetcher; this.bookDataFetcher=bookDataFetcher; } @PostConstruct private void loadSchema() throws IOException { logger.info("Entering loadSchema@GraphQLService"); loadDataIntoHSQL(); //Get the graphql file File file = resource.getFile(); //Parse SchemaF TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser().parse(file); RuntimeWiring runtimeWiring = buildRuntimeWiring(); GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry, runtimeWiring); graphQL = GraphQL.newGraphQL(graphQLSchema).build(); } private void loadDataIntoHSQL() { Stream.of( new Book("1001", "The C Programming Language", "PHI Learning", "1978", new String[] { "Brian W. Kernighan (Contributor)", "Dennis M. Ritchie" }), new Book("1002","Your Guide To Scrivener", "MakeUseOf.com", " April 21st 2013", new String[] { "Nicole Dionisio (Goodreads Author)" }), new Book("1003","Beyond the Inbox: The Power User Guide to Gmail", " Kindle Edition", "November 19th 2012", new String[] { "Shay Shaked" , "Justin Pot" , "Angela Randall (Goodreads Author)" }), new Book("1004","Scratch 2.0 Programming", "Smashwords Edition", "February 5th 2015", new String[] { "Denis Golikov (Goodreads Author)" }), new Book("1005","Pro Git", "by Apress (first published 2009)", "2014", new String[] { "Scott Chacon" }) ).forEach(book -> { bookRepository.save(book); }); } private RuntimeWiring buildRuntimeWiring() { return RuntimeWiring.newRuntimeWiring() .type("Query", typeWiring -> typeWiring .dataFetcher("allBooks", allBooksDataFetcher) .dataFetcher("book", bookDataFetcher)) build(); } public GraphQL getGraphQL(){ return graphQL; } }
When the Spring Boot application runs, the Spring Framework calls the @PostConstruct
method. The code inside the @PostConstruct
method will load the books into the HQL database.
In the buildRuntimeWiring()
method of this service class, we are doing a runtime wiring with two data fetchers: allBook
and book
. The names allBook
and book
defined here must match with the types defined in the GraphQL file that we already created
Creating the Data Fetchers
Each type in the GraphQL schema has a corresponding data fetcher.
We need to write two separate data fetcher classes for the allBooks
and Book
types that we defined in the schema.
The data fetcher class for the allBooks
type is this.
package graphqlapp.service.datafetcher; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import graphqlapp.model.Book; import graphqlapp.repository.BookRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; @Component public class AllBooksDataFetcher implements DataFetcher<List<Book>> { private BookRepository bookRepository; @Autowired public AllBooksDataFetcher(BookRepository bookRepository) { this.bookRepository=bookRepository; } @Override public List<Book> get(DataFetchingEnvironment dataFetchingEnvironment) { return bookRepository.findAll(); } }
The data fetcher class for the Book
type is this.
package graphqlapp.service.datafetcher; import graphql.schema.DataFetcher; import graphqlapp.model.Book; import graphqlapp.repository.BookRepository; import graphql.schema.DataFetchingEnvironment; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class BookDataFetcher implements DataFetcher<Book> { private BookRepository bookRepository; @Autowired public BookDataFetcher(BookRepository bookRepository){ this.bookRepository = bookRepository; } @Override public Book get(DataFetchingEnvironment dataFetchingEnvironment) { String isn = dataFetchingEnvironment.getArgument("id"); return bookRepository.findById(isn).orElse(null); } }
Running the Application
I am running this application on port 9002
and not on the default 8080
port. Therefore, I have the following property in the application.properties
file.
server.port=9002
With this, our Spring Boot GraphQL application is ready. Let’s run our Spring Boot application and test it using the Postman tool.
Notice here that we have just a single endpoint, http://localhost:9002/rest/books
Let’s query for multiple datasets with this single endpoint. To do this lets open Postman and add the following input query in the request body.
Input 1: Here we are querying for a specific book whose id
is 1001
and we want only the title
in its response. Along with it, we are querying for allBooks
and expecting that response will contain isn
, title
, author
,
publishedDate.publisher and
{ book(id:"1001"){ title } allBooks{ isn title author publisher publishedDate } }
Output 1: The response for both queries is this.
{ "errors": [], "data": { "book": { "title": "The C Programming Language" }, "allBooks": [ { "isn": "1001", "title": "The C Programming Language", "author": [ "Brian W. Kernighan (Contributor)", "Dennis M. Ritchie" ], "publisher": "PHI Learning", "publishedDate": "1978" }, { "isn": "1002", "title": "Your Guide To Scrivener", "author": [ "Nicole Dionisio (Goodreads Author)" ], "publisher": "MakeUseOf.com", "publishedDate": " April 21st 2013" }, { "isn": "1003", "title": "Beyond the Inbox: The Power User Guide to Gmail", "author": [ "Shay Shaked", "Justin Pot", "Angela Randall (Goodreads Author)" ], "publisher": " Kindle Edition", "publishedDate": "November 19th 2012" }, { "isn": "1004", "title": "Scratch 2.0 Programming", "author": [ "Denis Golikov (Goodreads Author)" ], "publisher": "Smashwords Edition", "publishedDate": "February 5th 2015" }, { "isn": "1005", "title": "Pro Git", "author": [ "Scott Chacon" ], "publisher": "by Apress (first published 2009)", "publishedDate": "2014" } ] }, "extensions": null }
Input 2: Lets’ query again for the title and author of a specific book by ID.
{ book(id:"1001"){ title author } }
Output 2: The output is this. We get the title
and the author
for the book whose id
is 1001
.
{ "errors": [], "data": { "book": { "title": "The C Programming Language", "author": [ "Brian W. Kernighan (Contributor)", "Dennis M. Ritchie" ] } }, "extensions": null }
Input 3: Lets’ query for allBooks
for their title
, isn
, author
, publishedDate
and publisher
details
{ allBooks{ isn title author publisher publishedDate } }
Output 3: The output is this.
{ "errors": [], "data": { "allBooks": [ { "isn": "1001", "title": "The C Programming Language", "author": [ "Brian W. Kernighan (Contributor)", "Dennis M. Ritchie" ], "publisher": "PHI Learning", "publishedDate": "1978" }, { "isn": "1002", "title": "Your Guide To Scrivener", "author": [ "Nicole Dionisio (Goodreads Author)" ], "publisher": "MakeUseOf.com", "publishedDate": " April 21st 2013" }, { "isn": "1003", "title": "Beyond the Inbox: The Power User Guide to Gmail", "author": [ "Shay Shaked", "Justin Pot", "Angela Randall (Goodreads Author)" ], "publisher": " Kindle Edition", "publishedDate": "November 19th 2012" }, { "isn": "1004", "title": "Scratch 2.0 Programming", "author": [ "Denis Golikov (Goodreads Author)" ], "publisher": "Smashwords Edition", "publishedDate": "February 5th 2015" }, { "isn": "1005", "title": "Pro Git", "author": [ "Scott Chacon" ], "publisher": "by Apress (first published 2009)", "publishedDate": "2014" } ] }, "extensions": null }
So, that is the beauty of using GraphQL over REST API. Here we get exactly what we are looking for and not just the complete bunch of JSON response will all the attributes values in it.
You can download the complete source code of this post from GitHub.
Kondulu
Wow, very nicely explained about the GraphQL.
I thought REST is the best till now but after reading this article, I would definitely try to implement GraphQL with my AngularJS app.
Good luck Author
Younes
very nice article. Do you have any experience about integrating graphQL with swagger in spring to document GraphQL APIs?
tries
nice! graphql ‘s types cotains datetime ? how can it query with “between “? thanks!
MT
Thank you. Great example. Question: how do you bypass the services created by the references in the pom file: graphql-spring-boot-starter, graphiql-spring-boot-starter? I’d like to connect to GraphQL running in a local Docker Container or another environment?
venkata
Thank you for example. How to enable GraphiQL for testing for this example?
Brian Quinn
Nice example. I wonder how GraphQL is primarily used at the present time. I’m used to having repository tools do CRUD operations. I think this tool would be an alternate option to Entity Framework or Hibernate. I guess its a matter of building custom a custom schema and query/mutations versus repository tool mapping and commands. I don’t know I’m still sorting out what GraphQL is.
Drakonoved
See alternative variant using Spring webmvc and data-jpa: https://drakonoved.org/blog/2020/04/03/simple-graphql-implementation.html
andrews
From Where database is getting connected?
sv
Showing error at
File file = resource.getFile(); in GraphQLService.java… The method getFile() is undefined for the type Resource
GV
You can modify to:
File file = ResourceUtils.getFile(“classpath:”);
Dilip
Hi,
Facing one issue when I am querying through graphiql tool.Same error when I am hitting rest endpoint in postman:
{
“timestamp”: “2021-03-23T21:06:41.333+00:00”,
“status”: 403,
“error”: “Forbidden”,
“message”: “”,
“path”: “/graphql”
}