In this article, we will be discussing how we can implement GraphQL in Java and integrate it with a real-time Spring boot project from scratch. The project will be a maven based project having spring data integrated to fetch data from MySQL database.
We will implement multiple GraphQL data fetchers which will inturn utilize spring data JpaRepository to query the DB.
What is GraphQL
GraphQL is a specification to fetch data from the server to the client over an HTTP call. The specification describes how to ask for data and rather than asking the whole bunch of data at once, it specifies to ask only the required data that would be meaningful to a client. It is just an alternative to REST in some way.
For example, we have a GET employee REST API that returns the details of an employee. There could be 50 different fields associated with an employee such as name, DOB, address, level, salary, age, etc but sending all these fields in the response of a REST call does not make sense and it could be difficult to determine what are the fields that should be actually sent in the response and tomorrow the requirement might change and we might require to add some more fields.
In some situation, we start versioning of the API and it becomes difficult to maintain those APIs.
With GraphQL, there is a single entry point that takes care of all kinds of client Query and the client can decide which Query needs to be executed that could be to either fetch employee details or to fetch any department details or it could be any other app info over the same entry point and even the client can decide what are the attributes that he is interested in.
For example the same endpoint http://localhost:8080/api/graphql/dept can be used to fetch all department lists or find a single department based on ID with different queries (payloads).
Fetch all Departments(only name and description){ allDepts{ name description } }Fetch deprtment By ID(name and employee details only)
{ deptById(id: 1) { name emps } }
For both the payloads, the endpoint remains the same whereas the request and response differ.
While fetching department details sometimes it might be useful to fetch the name and description of a department whereas in some cases we might be only interested in knowing the employee's details of a particular department and this could be easily achieved through GraphQL.
Difference Between GraphQL and REST
- There is a single entry point in GraphQL whereas there are multiple entry points in case of REST APIs.
- GraphQL is schema based.
- GraphQL could be faster then REST.
- Exisiting REST APIs can be wrapped with GraphQL.
Spring Boot and GraphQL Project Setup
Let us quickly set up our spring boot and GraphQL project with maven dependencies in place. Head over to https://start.spring.io/ and generate a sample spring boot app with maven dependencies spring-web, spring-data, Lombok and HyperSQL DB. We will first use in-memory DB and then integrate MYSQL with it.
Now, we will manually add GraphQL related maven dependencies in our pom.xml. Below are those dependencies that we have added to our pom file.
<dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-java</artifactId> <version>14.0</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-java-spring-boot-starter-webmvc</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>26.0-jre</version> </dependency>
graphql-java is the Java (server) implementation for GraphQL for executing queries where as graphql-java-spring-boot-starter-webmvc for exposing the API via Spring Boot over HTTP.
Spring Boot Controller and Service Implementation
We have our project setup done and now let us create our basic setup such as model class implementation, spring data repository setup and controller class implementation.
Model Class Implementation
We will have 2 model classes - Department.java and Employee.java. Department and employee will have a one-many relationship. Below are the class implementation.
Department.java@AllArgsConstructor @NoArgsConstructor @Setter @Getter @Entity @Table public class Department { @Id private int id; private String name; private String description; @OneToMany private ListEmployee.javaemps; }
@AllArgsConstructor @NoArgsConstructor @Getter @Setter @Entity @Table public class Employee { @Id private int id; private String name; private int age; private double salary; }
Now let us define our repository class. This extends spring data JpaRepository.
public interface DepartmentRepo extends JpaRepository<Department, Integer> { }EmployeeRepo.java
public interface EmployeeRepo extends JpaRepository<Employee, Integer> { }
Now let us define our controller class. As discussed above, in GraphQL we have a single entry point and hence the controller class contains only one endpoint that takes a string quey as as a body input. We will discuss the GraphQLProvider in the below sections.
@RestController @RequestMapping("/api") public class DepartmentController { @Autowired private GraphQLProvider graphQLProvider; @PostMapping("/graphql/dept") public ResponseEntity<Object> listDepartments(@RequestBody String query){ ExecutionResult execute = graphQLProvider.graphQL().execute(query); return new ResponseEntity<>(execute, HttpStatus.OK); } }
Creating GraphQL Java Server
Creating a GraphQL Java server has 2 steps.
- Defining a GraphQL Schema.
- Data fetchers implementation regarding how the actual data for a query is fetched.
Defining GraphQL Schema
The GraphQL schema file describes the type of query that can be performed by the client and the different fields that can be sent in the response. Below is our schema file created inside src/main/resources
dept.graphqlschema { query: Query } type Query{ allDepts: [Department] deptById(id: Int) : Department } type Department { id: Int name: String description: String emps: [Employee] } type Employee { id: Int name: String age: Int salary: Float }
The client can perform 2 different queries as allDepts
and deptById
and we have defined the different fields that a client can expect in the response. Now, the client has to decide what are the fields that he is interested in.
A sample request from the client is given below where a client is only interested in name of the departments.
{ allDepts{ name } }
GraphQL Data Fetchers Implementation
Data fetchers are the implementation for fetching the actual data that can be either from a DB or any third party API.
Our data fetcher implementation returns a DataFetcher implementation which takes a DataFetcherEnvironment as a parameter and this DataFetcherEnvironment is used to fetch the request parameter.
We also have a simple implementation to load the data into the DB during application startup.
@Component public class GraphQLDataFetchers { @Autowired private DepartmentRepo departmentRepo; @Autowired private EmployeeRepo employeeRepo; public DataFetcher getDeptByIdDataFetcher() { return dataFetchingEnvironment -> { int deptId = dataFetchingEnvironment.getArgument("id"); return departmentRepo.findById(deptId); }; } public DataFetcher getAllDepartmentsDataFetcher() { return dataFetchingEnvironment -> departmentRepo.findAll(); } public DataFetcher findEmployeesByDept() { return dataFetchingEnvironment -> { int deptId = dataFetchingEnvironment.getArgument("id"); Department department = departmentRepo.findById(deptId).get(); return department.getEmps(); }; } @PostConstruct public void loadDb(){ Employee employee = employeeRepo.save(new Employee(1, "Dhiraj", 20, 3456)); Stream.of( new Department(1, "Computer Science", "Computer Science department", Stream.of(employee).collect(Collectors.toList()))) .forEach(department -> departmentRepo.save(department)); } }
Now, we have done our schema defined and our data fetchers are ready and now we have to implement the wiring between the schema file and the data fetchers so that the GraphQL can know which data fetcher to be executed based on the query requested by the client.
GraphQL Providers
GraphQL providers act as a wiring between the schema file and the data fetchers that we created above.
The spring bean GraphQL is required by GraphQL Java Spring adapter will to make our schema available via HTTP on the default url /graphql
.
TypeDefinitionRegistry is the parsed version of our schema file that was loaded from the classpath with the Guava library.
SchemaGenerator combines the TypeDefinitionRegistry with RuntimeWiring to actually make the GraphQLSchema.
The buildWiring()
actually wires the query and the data fetchers.
@Component public class GraphQLProvider { private GraphQL graphQL; @Autowired GraphQLDataFetchers graphQLDataFetchers; @Autowired private AllDeptDataFetchers allDeptDataFetchers; @Bean public GraphQL graphQL() { return graphQL; } @PostConstruct public void init() throws IOException { URL url = Resources.getResource("dept.graphql"); String sdl = Resources.toString(url, Charsets.UTF_8); 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("Query", typeWiring -> typeWiring .dataFetcher("allDepts", graphQLDataFetchers.getAllDepartmentsDataFetcher()) //.dataFetcher("allDepts", allDeptDataFetchers) .dataFetcher("deptById", graphQLDataFetchers.getDeptByIdDataFetcher())) .build(); } }
This is pretty much the immplenetation of GraphQL with Spring Boot. Next, let us test our app.
Spring Boot GraphQL App Testing
To test the app, let us simply run the SpringBootGraphqlApplication.java
as a java application and then we can make some sample requests as below:
Fetch Department List with Employee
{ allDepts{ name description emps{ name } } }
Fetch Department By Id
{ deptById(id: 1) { name description } }
Combined Department and Employee Request
{ allDepts{ name description emps { name } } deptById(id: 1) { name description } }
We can also create our data fetchers by implementing DataFetcher interface as below but make sure you wire the same in our GraphQL provider implementation.
@Component public class AllDeptDataFetchers implements DataFetcher<List<Department>> { @Autowired private DepartmentRepo deptRepo; @Override public Listget(DataFetchingEnvironment dataFetchingEnvironment) throws Exception { return deptRepo.findAll(); } }
Conclusion
In this article, we discussed how we can implement GraphQL in Java and integrate it with a real-time Spring boot project from scratch. Next, we can explore how exception handling can be done in this implementation.