avatarAmila Iroshan

Summary

The provided content outlines a detailed guide on integrating GraphQL with a Spring Boot application, including setup, schema definition, data models, and error handling, to perform CRUD operations on a MySQL database.

Abstract

The article "GraphQL With Spring Boot" offers a comprehensive tutorial on integrating GraphQL into a Spring Boot application. It begins with an introduction to GraphQL, explaining its advantages over traditional REST APIs, such as avoiding over-fetching and under-fetching of data, providing predictable query results, and reducing network and memory usage. The author then guides readers through setting up a Spring Boot project with Gradle, configuring the data source, and creating GraphQL schemas for queries and mutations. The tutorial covers defining data models with JPA, implementing service and resolver layers, and handling errors with custom GraphQL error handling. The article concludes with practical examples of executing GraphQL queries and mutations to perform CRUD operations on a MySQL database, demonstrating the implementation with screenshots and code snippets. The author encourages readers to follow the publication for more content and provides links to related articles for further learning.

Opinions

  • The author emphasizes the efficiency and stability of applications using GraphQL due to its ability to let clients specify exactly what data they need.
  • There is a clear preference for GraphQL over REST APIs, citing benefits such as improved data fetching, real-time data updates through subscriptions, and reduced dependency on backend developers.
  • The article suggests that using GraphQL can simplify frontend development and eliminate the need for versioning of REST endpoints.
  • By providing a step-by-step guide, the author implies that integrating GraphQL with Spring Boot is straightforward and beneficial for building modern web applications.
  • The inclusion of error handling with a custom GraphQL error handler indicates the author's attention to robust application development and the importance of managing exceptions effectively.
  • The encouragement to follow the publication and the inclusion of related articles at the end of the post suggests the author's commitment to community engagement and continuous learning.

GraphQL With Spring Boot

Introduction

In this article I’ll explain the way of integrate GraphQL API with Spring Boot application and perform create, read and delete operations on MySql Database. In here I’m going to implement GraphQL query, mutation and the way of handle GraphQL exceptions.

What is API ?

API is a Application Programming Interface and which is a way to communicate between different software services over internet. Different types of APIs are used in programming hardware and software, including operating systemAPIs, remote APIs and web APIs. A web API or web service API is a set of tools that allow developers to send and receive instructions and data between a web server and a web browser usually in JSON format to build applications.

What is GraphQL ?

Simply it is a open source query language for API and it allowing you to fetch and manipulate your data. Its functionality is most similar to the REST API and behavioural vice and architectural vice it has more improvements than rest.

Why we use GraphQL over traditional REST API?

Over and Under fetching data is avoided. Over-fetching happens when the response fetches more than is required.Response contains unnesasary data. With GraphQL, you define the fields that you want to fetch and it fetches the data of only requested fields. Under-fetching, on the other hand, is not fetching adequate data in a single API request.It lets you fetch all relevant data in a single query.

Queries return predictable results.

Apps are fast and stable because the server isn’t controlling the data.

Network and memory usage decrease with less bandwidth.

Frontend or service consumer developers do not want to depend on backend developer works and no need to handle versioning of rest end points.

What are the GraphQL Operation Types?

Query: Used to fetch/read data Mutation: Used to create, update and delete data/manupilate data Subscription: Like queries, subscriptions enable you to fetch data.Unlike queries,change their result over time. Subscriptions are useful for notifying your client in real time about changes to back-end data, such as the creation of a new object or updates to an important field.

What is schema?

It is entry point for the application and place where we specify our GraphQL data types, query and mutations. It has “.graphqls” file extension.

What is resolver?

In our application you can see queryresolver and mutationresolver.Its responsible for map the schema and data model in our application. A resolver is the key architectural component that connects GraphQL fields, graph edges, queries, mutations, and subscriptions to their respective data sources.

I think now you got some sufficient knowledge related to GraphQL and lets start the implementation.

Objective

The goal of this example is to build a GraphQL APIs to perform CRUD operations with MySQL database. This exercise is around post and comment data api and each post has many comments.I have used One-to-Many relationship in here.

Technology

Java 8 Spring Boot 2.7.8.RELEASE (with Spring Web, Spring Data JPA and mysql-connector) graphql-spring-boot-starter 12.0.0 graphiql-spring-boot-starter:11.1.0 Gradle build tool

Project Structure

This is folders & files structure for our Spring Boot + GraphQL + MySQL application:

Project Structure

Set up the project

Create spring boot application. — Navigate to https://start.spring.io.

— Choose either Gradle or Maven as build tool. In here I’m using Gradle and Java 18.

Project Create via initializer

This is my dependencies on build.gradle

plugins {
 id 'java'
 id 'org.springframework.boot' version '2.7.8'
 id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'com.graphql'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
 mavenCentral()
}
dependencies {
 implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
 implementation 'com.graphql-java-kickstart:graphql-spring-boot-starter:12.0.0'
 implementation 'com.graphql-java-kickstart:graphiql-spring-boot-starter:11.1.0'
 implementation 'org.springframework.boot:spring-boot-starter-web'
 runtimeOnly 'com.mysql:mysql-connector-j'
 developmentOnly 'org.springframework.boot:spring-boot-devtools'
 implementation group: 'org.modelmapper', name: 'modelmapper', version: '2.1.1'
}
tasks.named('test') {
 useJUnitPlatform()
}

To configure Spring Data source, JPA open the application.properties and add the following configuration

spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/test_graphql
spring.datasource.username=root
spring.datasource.password=amila
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

Added sample data in data.sql for testing purposes.

insert into posts (id, content,is_display,name,no_of_likes)
values (1,'Java is a oop language and it is a one of most popular open source language',true,'About Java',6);
 
insert into posts (id, content,is_display,name,no_of_likes)
values (2,'GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.',true,'About GraphQl',10);

insert into posts (id, content,is_display,name,no_of_likes)
values (3,'React is a free and open-source front-end JavaScript library for building user interfaces based on components',true,'About React',8);
 
insert into post_comments (id,coment_content,post_id)
values (1,'Very informative', 1);
 
insert into post_comments (id,coment_content,post_id)
values (2,'Very nice post', 1);

insert into post_comments (id,coment_content,post_id)
values (3,'Please give sample code', 2);

insert into post_comments (id,coment_content,post_id)
values (4,'Very informative', 2);

insert into post_comments (id,coment_content,post_id)
values (5,'You make my day,thanks in advanced', 1);

insert into post_comments (id,coment_content,post_id)
values (6,'Nice article', 3);

Create GraphQL Schema

I have split up schema into two .graphqls files.Those are mutationPosts.graphqls and queryPosts.graphqls respectively. The Spring Boot GraphQL starter will automatically find these schema files.Under src/main/resources/schema.post folder, create queryPosts.graphqls and mutationPosts.graphqls files.

type Query {
    allPosts :PostContentDTO
    getPosts(PostId: Float!) :PostContentDTO    
}
type PostContentDTO {
    statusCode:Int!
    totalRecord:Int!
    postList:[PostDTO]
}
type PostDTO {
    id:Float
    name:String
    content:String
    noOfLikes:Int
    isDisplay:Boolean
    comments:[PostCommentsDTO]
}
type PostCommentsDTO {
    id:Float
    comentContent:String
}
type Mutation{
createPosts(createPostDTO : CreatePostDTO):Posts
deletePosts(postId:Float!):String
}

input CreatePostDTO{
 postName:String
 postContent:String
 noOfLikes:Int
 postIsDisplay:Boolean
}

type Posts{
 id:Float
 name:String
 content:String
 noOfLikes:Int
 isDisplay:Boolean
 comments:[PostComments]
}

type PostComments {
    id:Float
    comentContent:String
}

Define Data Models

Here I have defined two main models with One-to-Many relationship in model package: -Posts.java -PostComments.java

@Entity
public class Posts {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;
 private String name;
 private String content;
 private int noOfLikes;
 private boolean isDisplay;
 
 @OneToMany(cascade = CascadeType.ALL,orphanRemoval = true,fetch = FetchType.EAGER)
 @JoinColumn(name = "post_id")
    private List<PostComments> comments = new ArrayList<>();

//Getters and Setters here
@Entity
public class PostComments { 
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;
 
 private String comentContent;

 public PostComments() {
  super();
  
 }
//Getters and Setters here

Define DTO(Data Transfer Objects) Models

Dtos used for transfer data between data model classes and resolvers.

public class PostContentDTO extends CommonResponse{
 private List<PostDTO> postList;

 public PostContentDTO() {
  super();
 }
 public List<PostDTO> getPostList() {
  return postList;
 }
 public void setPostList(List<PostDTO> postList) {
  this.postList = postList;
 }
 @Override
 public String toString() {
  return "PostContentDTO [postList=" + postList + "]";
 }
}
public class PostDTO implements Serializable {
 
 private Long id;
 
 private String name;
 
 private String content;
 
 private int noOfLikes;
 
 private boolean isDisplay;
 
 private List<PostCommentsDTO> comments;
//Getters and Setters here

Create Repositories

In repository package, create two interfaces that implement JpaRepository. Once we extends the JpaRepository, Spring Data JPA will automatically generate implementation with find, save, delete, count methods for the entities.

@Repository
public interface PostRepository extends JpaRepository<Posts, Long> {

}

Define Service Layer

Here specify the business logics and data modeling.

@Service
public class PostsService {
 
 private PostRepository postRepository;
 private ModelMapper modelMapper;

 @Autowired
 public PostsService(PostRepository postRepository, ModelMapper modelMapper) {
  this.postRepository = postRepository;
  this.modelMapper = modelMapper;
 }

 public PostContentDTO getPosts(Long PostId) {
  PostContentDTO postContentDto=new PostContentDTO();
  Optional<Posts> post=postRepository.findById(PostId);
  if(!post.isPresent()) {
   throw new ResourceNotFoundException("Unable to find post with given post id :"+ PostId);
  }
  PostDTO postDto = this.modelMapper.map(post.get(), PostDTO.class);
  List<PostDTO> objects = Collections.singletonList(postDto);
  postContentDto.setStatusCode(HttpStatus.FOUND.value());
  postContentDto.setTotalRecord(1);
  postContentDto.setPostList(objects);
  return postContentDto;
 }

 public PostContentDTO allPosts() {
  PostContentDTO postContentDto=new PostContentDTO();
  List<Posts> post=postRepository.findAll();
  List<PostDTO> objectList =new  ArrayList<>();
  post.stream().forEach(postObj-> objectList.add(this.modelMapper.map(postObj, PostDTO.class)) );
  postContentDto.setStatusCode(HttpStatus.FOUND.value());
  postContentDto.setTotalRecord(post.size());
  postContentDto.setPostList(objectList);
  return postContentDto;
 }
 
 public Posts createPost(CreatePostDTO createPostDTO) {
  Posts post = this.modelMapper.map(createPostDTO, Posts.class);
  postRepository.save(post);
  return post;
 }
 
 public void deletePost(Long postId) {
  Optional<Posts> post =postRepository.findById(postId);
  if(post.isPresent()) {
   postRepository.deleteById(postId);
  }
  else {
   throw new ResourceNotFoundException("Unable to find post with given post id :"+ postId);
  }
 }

Implement GraphQL Query Resolver

Every field in the schema query should have a method in the Query Resolver class with the same name. This PostQueryResolver class implements GraphQLQueryResolver.

@Component
public class PostsQueryResolver implements GraphQLQueryResolver  {
 private final PostsService postsService;
 
 @Autowired public PostsQueryResolver(PostsService postsService) { super();
 this.postsService = postsService; }

 public PostContentDTO getPosts(Long PostId) { return
 postsService.getPosts(PostId); }
 
 public PostContentDTO allPosts() { return postsService.allPosts(); }

}

Then PostMutationResolver class implements GraphQLMutationResolver. Just like Query Resolver, every field in the schema mutation query should have a method in the Mutation Resolver class with the same name.

@Component
public class PostMutationResolver implements GraphQLMutationResolver {

 @Autowired
 private ModelMapper modelMapper;
 
 @Autowired
 private PostsService postsService;
 
 public Posts createPosts(CreatePostDTO createPostDTO ) {
  return postsService.createPost(createPostDTO);
 }
 public String deletePosts(Long postId) {
  postsService.deletePost(postId);
  return "Deleted post Id :"+postId;
 }
}

Error Handling

All the errors in GraphQl go through the GraphQlErrorHnadler interface.It implements the DefaultGraphQlErrorHnadler class and it has processError method which excecutes when the graphql related exception occurs.Then the GraphQLError interface used to define our custom graphql error. So lets define our own error handler.

First create package called exception and create class called “ResourceNotFoundException” and extends RuntimeException and implements GraphQLError interface to it.Then you have to override the getLocations() and getErrorType() methods which are derived from GraphQLError interface.The getLocations() is place where we can specify the location where error happens. Then the getErrorType() method is place where you can specify type of the exception. Here I’m using ErrorType enum which contains limited types of graphql related exceptions.

public class ResourceNotFoundException extends RuntimeException implements GraphQLError {

 public ResourceNotFoundException(String message) {
  super(message);
 }
 @Override
 public List<SourceLocation> getLocations() {
  //specify the location where error happens
  return null;
 }
 @Override
 public ErrorClassification getErrorType() {
  return ErrorType.DataFetchingException;
 }
}

Run & Check Result

Run Spring Boot application with command: mvn spring-boot:run.

Then acces http://localhost:8080/graphiql. This is graphql play ground and you can querying data from browser.Besides that you can use postman.

Query for fetch post by Id.

Get posts by Id
Get all posts
Save post using mutation
Check mutation via db
Delete post
Delete post with non existing id
Informative exception after error handle

Thank you for read this article and If you like this article, do follow and clap 👏🏻.Happy coding, Cheers !!😊😊

You can find the complete code for this example on GitHub

Do support our publication by following it

GraphQL
Spring Boot
MySQL
Java
Graphiql
Recommended from ReadMedium