Using GraphQL in a Spring Boot Application

Using GraphQL in a Spring Boot Application

2 Comments

You 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.

Spring Boot                 GraphQL

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:

  1. 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}

     

  2. 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"}

     

  3. 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 BookControllerand 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 [email protected]");
        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.graphqlin our resourcefolder.

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 of Book.
  • When a user queries for a specific book by passing the id, then the application will return a Book 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 [email protected]");
        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: allBookand book. The names  allBookand 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, publisher and publishedDate.

{
   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 allBooksfor 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.

 

 

About SFG Contributor

Staff writer account for Spring Framework Guru

    You May Also Like