GraphQL is a query language for APIs that allows clients to request exactly the data they need and receive predictable results.
Eang Sopheaktra
June 08 2024 04:58 pm
GraphQL is a query language for APIs that allows clients to request exactly the data they need and receive predictable results. Here are some key points about GraphQL:
GraphQL offers several benefits that make it a compelling choice for modern applications:
Certainly! Spring Boot provides a convenient way to build GraphQL APIs. Here’s how you can set up a GraphQL server using Spring Boot:
Using dependencies such as below:
<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.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
add below config to application.properties for allow graphql
spring.graphql.graphiql.enabled=true
add graphql's schema to package: "/resources/graphql/schema.student" with mutation and query such as below:
type Mutation{
createStudent(createStudentDTO : CreateStudentDTO):StudentDTO
updateStudent(studentId:Float!, updateStudentDTO : UpdateStudentDTO):StudentDTO
deleteStudent(studentId:Float!):String
}
input CreateStudentDTO{
firstName: String
lastName: String
age: Int
isDeleted: Boolean
courses: [Float!]
}
input UpdateStudentDTO{
id: Float
firstName: String
lastName: String
age: Int
isDeleted: Boolean
courses: [Float!]
}
type Query {
allStudents(paginationReq: PaginationReq!) :StudentContent
getStudents(studentId: Float!) :StudentDTO
}
type StudentContent {
pagination: Pagination
content:[StudentDTO]
}
input PaginationReq {
page: Int
size: Int
}
type StudentDTO {
id:Float
firstName:String
lastName:String
age:Int
isDeleted:Boolean
courses:[CourseDTO]
}
type Pagination {
page: Int
size: Int
totalPage: Float
totalElement: Float
}
type CourseDTO {
id:Float
name:String
}
Create controller to handle request from user with graphql such as annotation:
package com.tra21.graphqltesting.controllers;
import com.tra21.graphqltesting.payloads.dtos.students.CreateStudentDTO;
import com.tra21.graphqltesting.payloads.dtos.students.StudentDTO;
import com.tra21.graphqltesting.payloads.dtos.students.UpdateStudentDTO;
import com.tra21.graphqltesting.payloads.req.PaginationReq;
import com.tra21.graphqltesting.payloads.res.students.StudentContents;
import com.tra21.graphqltesting.services.students.IStudentService;
import lombok.RequiredArgsConstructor;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
@Controller
@RequiredArgsConstructor
public class StudentController {
private final IStudentService studentService;
@QueryMapping
public StudentDTO getStudents(@Argument Long studentId ) {
return studentService.getStudents(studentId);
}
@QueryMapping
public StudentContents allStudents(@Argument PaginationReq paginationReq) {
return studentService.allStudents(paginationReq);
}
@MutationMapping
public StudentDTO createStudent(@Argument CreateStudentDTO createStudentDTO){
return studentService.createStudent(createStudentDTO);
}
@MutationMapping
public StudentDTO updateStudent(@Argument Long studentId, @Argument UpdateStudentDTO updateStudentDTO){
return studentService.updateStudent(studentId, updateStudentDTO);
}
@MutationMapping
public String deleteStudent(@Argument Long studentId){
return studentService.deleteStudent(studentId) ? "Success deleted student." : "Failed to delete student.";
}
}
Create CustomErrorResolver for handle error
package com.tra21.graphqltesting.exception;
import graphql.ErrorClassification;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import graphql.schema.DataFetchingEnvironment;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import java.util.stream.Collectors;
@Component
public class CustomErrorResolver extends DataFetcherExceptionResolverAdapter {
@Override
protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
Throwable t = NestedExceptionUtils.getMostSpecificCause(ex);
if(t instanceof ConstraintViolationException constraintViolationException){
return validationError(constraintViolationException, env);
}else if (t instanceof NotFoundException exception) {
return GraphqlErrorBuilder.newError(env)
.errorType(ErrorClassification.errorClassification(HttpStatus.BAD_REQUEST.name()))
.message(exception.getMessage())
.build();
}else if (t instanceof RuntimeException exception) {
return GraphqlErrorBuilder.newError(env)
.errorType(ErrorClassification.errorClassification(HttpStatus.INTERNAL_SERVER_ERROR.name()))
.message("Unexpected Error!")
.build();
}
// other exceptions not yet caught
return GraphqlErrorBuilder.newError(env)
.message("Error occurred: Ensure request is valid ")
.errorType(ErrorClassification.errorClassification(HttpStatus.INTERNAL_SERVER_ERROR.name()))
.build();
}
private GraphQLError validationError(ConstraintViolationException exception, DataFetchingEnvironment env){
String invalidFields = exception.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining("\n"));
return GraphqlErrorBuilder.newError(env)
.errorType(ErrorClassification.errorClassification(HttpStatus.INTERNAL_SERVER_ERROR.name()))
.message(invalidFields)
.build();
}
}
Download the source code for the sample application with graphql. After this tutorial you will learn such as: