Wrap REST API with GraphQL in Spring Boot

Introduction

This post will show you how to wrap REST API with GraphQL in Spring Boot framework. So here we will create a Java based GraphQL server. When you come to know that GraphQL has advantages over REST API and you want to start using GraphQL but you are stuck with legacy REST APIs, then this tutorial may help you to wrap your existing REST API with GraphQL. Hence you don’t need to rewrite your existing REST APIs into GraphQL but you can utilize them with GraphQL.

GraphQL is an open source query language introduced by Facebook in the year of 2015. I would not explain here about GraphQL as Facebook has already explained in details and can be found here. You will also find the differences between REST and GraphQL.

You may also like to read Wrap REST API with GraphQL in Node.js.

In this Spring Boot application we will also build a GraphQL IDE or Playground for performing query and mutation on GraphQL API. GraphQL IDE is similar to Postman or REST client but GraphQL IDE is used for GraphQL API, whereas Postman or REST client is used for REST API.

You can use any technology for implementing GraphiQL server. Here we will use Spring Boot and Java to implement GraphQL server.

Prerequisites

Knowledge on GraphQL, REST, Spring Boot and Java

Eclipse Neon, Spring Boot 2.1.6, Java 1.8, GraphQL 11.0

Go through Spring Boot Data JPA CRUD Example

Example with Source Code

Please go through the following sections to wrap REST API with GraphQL in Spring Boot application.

As I mentioned the URL in prerequisite section on Spring Boot Data JPA CRUD Example, so I will assume that you have similar kind of existing REST API for performing basic CRUD operations.

Creating Project

Create a gradle based project in Eclipse and the project structure may look to the below image:

wrap rest api with graphql

Updating Build Script

We will update the default generated build.gradle script to include the required dependencies for our Spring Boot based GraphQL application.

The last two dependencies are for GraphQL – one is for using GraphQL with Java and another one for using GraphQL with Spring Boot.

Also notice I have used implementation instead of compile, so you can read here about implementation in Gradle.

buildscript {
	ext {
		springBootVersion = '2.1.6.RELEASE'
	}
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
    }
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
repositories {
    mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
	implementation("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
	implementation('com.graphql-java:graphql-java:11.0')
    implementation('com.graphql-java:graphql-java-spring-boot-starter-webmvc:1.0')
}

Identifying REST Endpoints

The first task is to identify the existing REST end-points and the example we are using here Spring Boot Data JPA CRUD Example has the following endpoints:

GET /websites
GET /webste/{id}
POST /website
PUT /website
DELETE /website

The endpoint – GET /websites – lists all the available website information from the server.

The endpoint – GET /website/{id} – lists the available website information for a given website id from the server.

The endpoint – POST /website – adds a new website information to the server. The website information is passed in the body as a JSON payload.

The endpoint – PUT /website – updates an existing website information to the server. The website information is passed in the body as a JSON payload.

The endpoint – DELETE /website – deletes an existing website information from the server. The website information is passed in the body as a JSON payload.

Identifying Data Model and Creating VO Class

Next task is to identify the data model of the existing REST API and we know that the existing REST API Spring Boot Data JPA CRUD Example has below information for a particular website:

id, name, url

The id indicates the Id of the website, name indicates the name of the website and url indicates the URL of the website.

So we can create corresponding VO class that will help us to easily convert to JSON payload or vice-versa.

Below is the VO class that will represent a website information.

package spring.boot.graphql.rest.wrapper.vo;
public class Website {
	private Integer id;
	private String name;
	private String url;
	public Website() {
	}
	public Website(Integer id, String name, String url) {
		this.id = id;
		this.name = name;
		this.url = url;
	}
	//getters and setters
}

Defining GraphQL Schema

We are creating a new file schema.graphql in src/main/resources with the following content:

type Query {
  websites: [Website!]!
  website(id: ID!): Website
}
type Mutation {
  addWebsite(name: String!, url: String!): String!
  updateWebsite(id: ID!, name: String!, url: String!): String!
  deleteWebsite(id: ID!): String!
}
type Website {
  id: ID
  name: String
  url: String
}

This schema defines top level fields (in the type Query): websites which returns the details of all available websites and website that returns the details of a particular website for a given id.

It also defines the Website which has fields id, name and url.

The Domain Specific Language shown above which is used to describe a schema is called Schema Definition Language or SDL. More details about it can be found here.

We know that Query is used to query GraphQL server for fetching or reading data.

We also need to save new website information, update existing website information or delete existing website information. Therefore we used Mutation to perform create, update and delete operations.

In the above Query, we have used [] and it means it will return a list of websites.

Notice we have also used ! to denote that the parameter is required. If you make a parameter required and if you do not pass value or get value from server on this parameter then GraphQL will throw errors.

Creating GraphQL DataFetcher

The most important concept for a GraphQL Java server is a DataFetcher that fetches the Data for one field when a query is executed.

While GraphQL Java is executing a query, it calls the appropriate DataFetcher for each field it encounters in query. A DataFetcher is an Interface with a single method, taking a single argument of type DataFetcherEnvironment:

public interface DataFetcher<T> {
    T get(DataFetchingEnvironment dataFetchingEnvironment) throws Exception;
}

Every field from the schema has a DataFetcher associated with. If you don’t specify any DataFetcher for a specific field, then the default PropertyDataFetcher is used.

Now we will create a class GraphQLDataFetcher that will fetch data from REST API using Spring’s RestTemplate.

If you look at the methods in the below class, then you will find that any method returns a DataFetcher implementation which takes a DataFetcherEnvironment and returns an appropriate object.

package spring.boot.graphql.rest.wrapper.provider;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import graphql.schema.DataFetcher;
import spring.boot.graphql.rest.wrapper.vo.Website;
@Component
public class GraphQLDataFetcher {
	private final String REST_URL = "http://localhost:8080/website";
	@Autowired
	private RestTemplate restTemplate;
	public DataFetcher<List<Website>> getWebsiteList() {
		return dataFetchingEnvironment -> {
			return restTemplate
					.exchange(REST_URL, HttpMethod.GET, null, new ParameterizedTypeReference<List<Website>>() {
					}).getBody();
		};
	}
	public DataFetcher<Website> getWebsiteById() {
		return dataFetchingEnvironment -> {
			String id = dataFetchingEnvironment.getArgument("id");
			return restTemplate.getForObject(REST_URL + "/" + id, Website.class);
		};
	}
	public DataFetcher<String> addWebsite() {
		return dataFetchingEnvironment -> {
			String name = dataFetchingEnvironment.getArgument("name");
			String url = dataFetchingEnvironment.getArgument("url");
			Website website = new Website();
			website.setName(name);
			website.setUrl(url);
			HttpHeaders headers = new HttpHeaders();
			headers.setContentType(MediaType.APPLICATION_JSON);
			HttpEntity<Website> entity = new HttpEntity<>(website, headers);
			return restTemplate.postForObject(REST_URL, entity, String.class);
		};
	}
	public DataFetcher<String> updateWebsite() {
		return dataFetchingEnvironment -> {
			String id = dataFetchingEnvironment.getArgument("id");
			String name = dataFetchingEnvironment.getArgument("name");
			String url = dataFetchingEnvironment.getArgument("url");
			Website website = new Website(Integer.parseInt(id), name, url);
			HttpHeaders headers = new HttpHeaders();
			headers.setContentType(MediaType.APPLICATION_JSON);
			HttpEntity<Website> entity = new HttpEntity<>(website, headers);
			return restTemplate.exchange(REST_URL, HttpMethod.PUT, entity, new ParameterizedTypeReference<String>() {
			}).getBody();
		};
	}
	public DataFetcher<String> deleteWebsite() {
		return dataFetchingEnvironment -> {
			String id = dataFetchingEnvironment.getArgument("id");
			Website website = new Website();
			website.setId(Integer.parseInt(id));
			HttpHeaders headers = new HttpHeaders();
			headers.setContentType(MediaType.APPLICATION_JSON);
			HttpEntity<Website> entity = new HttpEntity<>(website, headers);
			return restTemplate.exchange(REST_URL, HttpMethod.DELETE, entity, new ParameterizedTypeReference<String>() {
			}).getBody();
		};
	}
}

Creating GraphQL Provider

We had create GraphQL schema, now it’s also important to create a GraphQL provider class to read and parse the schema file for performing query and mutation.

In the below code the init() method creates GraphQL instance.

We use Spring Resource to read the file from our classpath, then create a GraphQLSchema and GraphQL instance. This GraphQL instance is exposed as a Spring Bean via the graphQL() method annotated with @Bean. The GraphQL Java Spring adapter will use that GraphQL instance to make our schema available via HTTP on the default url /graphql.

We implement the buildSchema() method which creates the GraphQLSchema instance and wires in code to fetch data.

TypeDefinitionRegistry is the parsed version of our schema file. SchemaGenerator combines the TypeDefinitionRegistry with RuntimeWiring to actually make the GraphQLSchema.

buildRuntimeWiring uses the dataFetcher bean to actually register DataFetchers for performing Query and Mutation.

package spring.boot.graphql.rest.wrapper.provider;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
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 graphql.schema.idl.TypeRuntimeWiring;
@Component
public class GraphQLProvider {
	private GraphQL graphQL;
	@Autowired
	private GraphQLDataFetcher dataFetcher;
	@PostConstruct
	public void init() throws IOException {
		final Resource resource = new ClassPathResource("schema.graphql");
		String sdl = null;
		try {
			sdl = new String(Files.readAllBytes(resource.getFile().toPath()), StandardCharsets.UTF_8);
		} catch (IOException e) {
			e.printStackTrace();
		}
		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(TypeRuntimeWiring.newTypeWiring("Query").dataFetcher("websites", dataFetcher.getWebsiteList()))
				.type(TypeRuntimeWiring.newTypeWiring("Query").dataFetcher("website", dataFetcher.getWebsiteById()))
				.type(TypeRuntimeWiring.newTypeWiring("Mutation").dataFetcher("addWebsite", dataFetcher.addWebsite()))
				.type(TypeRuntimeWiring.newTypeWiring("Mutation").dataFetcher("updateWebsite",
						dataFetcher.updateWebsite()))
				.type(TypeRuntimeWiring.newTypeWiring("Mutation").dataFetcher("deleteWebsite",
						dataFetcher.deleteWebsite()))
				.build();
	}
	@Bean
	public GraphQL graphQL() {
		return graphQL;
	}
}

Creating Main Class

We need to create a main class with @SpringBootApplication annotation that identifies as Spring Boot application and deploys into embedded Tomcat server.

package spring.boot.graphql.rest.wrapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "spring.boot.graphql.rest.wrapper")
public class SpringGraphqlServerApp {
	public static void main(String[] args) {
		SpringApplication.run(SpringGraphqlServerApp.class, args);
	}
}

Changing Server Port

Change the default server port from 8080 to 9000 as our existing REST API already running on port 8080.

The below line is put into application.properties file.

server.port=9000

Enough coding! Let’s test our application.

Testing the Application

Make sure that your application Spring Boot Data JPA CRUD Example is running on server.

Now run the above main class, your application will be deployed into embedded tomcat server on port 9000.

Your GraphQL endpoint, by default, will point to http://localhost:9000/graphql.

Using the above URL you can only execute Query but you won’t be able to execute Mutation.

So if you want to execute Query using the default GraphQL URL then you can execute below URLs for fetching all available websites and a particular website, respectively:

http://localhost:9000/graphql?query=%7Bwebsites%7Bid%20name%20url%7D%7D
http://localhost:9000/graphql?query=%7Bwebsite%28id:1%29%7Bid%7D%7D

The above URLs are encoded ones because HTTP request won’t allow you to use some characters. The equivalent human readable URLs would be as follows:

http://localhost:9000/graphql?query={websites{id name url}}
http://localhost:9000/graphql?query={website(id:1){id}}

Therefore, we need GraphQL IDE or Playground that will help us to execute Query and Mutations with our standard input formats. The next section explains how to create a GraphQL IDE or Playgroud.

Creating GraphQL IDE

Create an index.html file under src/main/resources/static folder.

Obviously the below file is not created by me.

<!--
 *  Copyright (c) 2019 GraphQL Contributors
 *  All rights reserved.
 *
 *  This source code is licensed under the license found in the
 *  LICENSE file in the root directory of this source tree.
-->
<!DOCTYPE html>
<html>
  <head>
    <style>
      body {
        height: 100%;
        margin: 0;
        width: 100%;
        overflow: hidden;
      }
      #graphiql {
        height: 100vh;
      }
    </style>
    <!--
      This GraphiQL example depends on Promise and fetch, which are available in
      modern browsers, but can be "polyfilled" for older browsers.
      GraphiQL itself depends on React DOM.
      If you do not want to rely on a CDN, you can host these files locally or
      include them directly in your favored resource bunder.
    -->
    <script src="//cdn.jsdelivr.net/es6-promise/4.0.5/es6-promise.auto.min.js"></script>
    <script src="//cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script>
    <script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
    <script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
    <!--
      These two files can be found in the npm module, however you may wish to
      copy them directly into your environment, or perhaps include them in your
      favored resource bundler.
     -->
    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/graphiql@0.11.2/graphiql.css" />
	<script src="//cdn.jsdelivr.net/npm/graphiql@0.11.2/graphiql.js"></script>
  </head>
  <body>
    <div id="graphiql">Loading...</div>
    <script>
      /**
       * This GraphiQL example illustrates how to use some of GraphiQL's props
       * in order to enable reading and updating the URL parameters, making
       * link sharing of queries a little bit easier.
       *
       * This is only one example of this kind of feature, GraphiQL exposes
       * various React params to enable interesting integrations.
       */
      // Parse the search string to get url parameters.
      var search = window.location.search;
      var parameters = {};
      search.substr(1).split('&').forEach(function (entry) {
        var eq = entry.indexOf('=');
        if (eq >= 0) {
          parameters[decodeURIComponent(entry.slice(0, eq))] =
            decodeURIComponent(entry.slice(eq + 1));
        }
      });
      // if variables was provided, try to format it.
      if (parameters.variables) {
        try {
          parameters.variables =
            JSON.stringify(JSON.parse(parameters.variables), null, 2);
        } catch (e) {
          // Do nothing, we want to display the invalid JSON as a string, rather
          // than present an error.
        }
      }
      // When the query and variables string is edited, update the URL bar so
      // that it can be easily shared
      function onEditQuery(newQuery) {
        parameters.query = newQuery;
        updateURL();
      }
      function onEditVariables(newVariables) {
        parameters.variables = newVariables;
        updateURL();
      }
      function onEditOperationName(newOperationName) {
        parameters.operationName = newOperationName;
        updateURL();
      }
      function updateURL() {
        var newSearch = '?' + Object.keys(parameters).filter(function (key) {
          return Boolean(parameters[key]);
        }).map(function (key) {
          return encodeURIComponent(key) + '=' +
            encodeURIComponent(parameters[key]);
        }).join('&');
        history.replaceState(null, null, newSearch);
      }
      // Defines a GraphQL fetcher using the fetch API. You're not required to
      // use fetch, and could instead implement graphQLFetcher however you like,
      // as long as it returns a Promise or Observable.
      function graphQLFetcher(graphQLParams) {
        // When working locally, the example expects a GraphQL server at the path /graphql.
        // In a PR preview, it connects to the Star Wars API externally.
        // Change this to point wherever you host your GraphQL server.
        const isDev = !window.location.hostname.match(/(^|\.)netlify\.com$|(^|\.)graphql\.org$/)
        const api = isDev ? '/graphql' : 'https://swapi.graph.cool/'
        return fetch(api, {
          method: 'post',
          headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(graphQLParams),
          credentials: 'include',
        }).then(function (response) {
          return response.text();
        }).then(function (responseBody) {
          try {
            return JSON.parse(responseBody);
          } catch (error) {
            return responseBody;
          }
        });
      }
      // Render <GraphiQL /> into the body.
      // See the README in the top level of this module to learn more about
      // how you can customize GraphiQL by providing different values or
      // additional child elements.
      ReactDOM.render(
        React.createElement(GraphiQL, {
          fetcher: graphQLFetcher,
          query: parameters.query,
          variables: parameters.variables,
          operationName: parameters.operationName,
          onEditQuery: onEditQuery,
          onEditVariables: onEditVariables,
          onEditOperationName: onEditOperationName
        }),
        document.getElementById('graphiql')
      );
    </script>
  </body>
</html>

Naturally when you have made changes to the application, you need to restart your server.

Once you restart server, you can point the URL at http://localhost:9000 in the browser and you will see the following screen on the browser.

So now it’s very easy to execute Query or Mutation on this IDE.

wrap rest api with graphql

Reading Website Information

Now when we execute below Query on left pane then we get nothing on the right pane because there is no available website in the server.

wrap rest api with graphql in spring boot

Creating Website Information

Now execute Mutation to save new website information.

wrap rest api with graphql
wrap rest api with graphql

Reading Website Information

Now execute Query to fetch all available websites.

wrap rest api with graphql

You can also query a particular website as shown below.

wrap rest api with graphql

Updating Website Information

You can update an existing website information.

wrap rest api in graphql

Deleting Website Information

You can delete an existing website information.

rest api with graphql

Reading Website Information

The final Query output will be as shown below:

graphql server

That’s all. Hope you got idea on how to wrap REST API with GraphQL in Spring Boot application.

source code

You may also like to read Wrap REST API with GraphQL in Node.js.

Thanks for reading.

3 thoughts on “Wrap REST API with GraphQL in Spring Boot

  1. spring.graphql.graphiql.enabled=true
    with this line in application properties, you can avoid making index.html

    to improve the tutorial, you could suggest file names next to the java code.

    Thanks

  2. Sorry I have a question that is out of the topic of the course, if the teacher can or know is already a help. I have already configured the zuul server, which is working as the application’s gateway, however I would like to have a new layer with graphQL to serve as my gateway, does the teacher have any experience in this type of configuration?

    Thanks in advance

Leave a Reply

Your email address will not be published. Required fields are marked *