Graphql Mutation and error handling in spring boot
In the previous Create GraphQL using Spring Boot, we have learned how to fetch data from graphQL server using graplQL query but in a real-time mobile app or any project not only need data from server but also need some function to update data.
In today's blog, we are going to learn how to update, create and delete data on the graphql server.
Integrating GraphQL in Spring boot API
GraphQL Mutation
In GraphQL, mutations are used to perform some manipulation operations in server data such as updating, creation, and deleting operations.
*
type Mutation {
addEmployee(employee: EmployeeInput): Employee
updateEmployee(employee: EmployeeInput): Employee
}
In the above, the addEmployee and updateEmployee are mutations defined in the schema.
The addEmployee and updateEmployee take EmployeeInput object as input and respond back Employee object.
The main difference between query and mutation is that query operations are executed in parallel where mutations are executed in series one after the other.
GraphQL input type
input EmployeeInput {
id: Int
name: String
jobTitle: String
mobileNo: String
joinedDate: String
gender: String
salary: Int
department: DepartmentInput
project: ProjectInput
}
input DepartmentInput {
id: ID
name: String
}
input ProjectInput {
id: ID
name: String
}
mutation($employee:EmployeeInput){
updateEmployee(employee:$employee){
id
name
department{
id
name
}
project{
id
name
}
}
}
Create GrpahQl mutation resolver
import java.util.Objects;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.coxautodev.graphql.tools.GraphQLMutationResolver;
import com.example.graphql.exception.InvalidEmployeeException;
import com.example.graphql.model.Employee;
import com.example.graphql.repo.EmployeeRepository;
@Component
public class GraphQLMutationService implements GraphQLMutationResolver {
@Autowired
EmployeeRepository employeeRepository;
public Employee addEmployee(Employee employee) {
System.out.println("Employee input " + employee);
return employeeRepository.save(employee);
}
public Employee updateEmployee(Employee employee) {
System.out.println(employee);
Employee emp = null;
if (Objects.nonNull(employee)) {
boolean isEmployeePresent = employeeRepository.existsById(employee.getId());
System.out.println(isEmployeePresent);
if (!isEmployeePresent) {
throw new InvalidEmployeeException("Employee Details not found in database", employee.getId());
} else {
emp = employeeRepository.save(employee);
}
}
return emp;
}
}
Testing the mutation
{
"employee":{
"name": "Priya",
"jobTitle": "Support",
"joinedDate": "20-05-2020",
"gender": "female",
"mobileNo": "9840353535",
"salary": 20000,
"department": {
"id": "1"
},
"project": {
"id": "2"
}
}
}
Exception Handling in GraphQL
GraphQL will always give a response (200 OK HTTP response) even if any error occurred during the processing of the request in that case graphQL will respond back with error details, the response
contains two parts the data and errors.
-
Data - contains the result of the operation.
-
errors - contain the errors which occurred during the
operation.
The errors part contains below fields
-
message - description about the exception(error message)
-
path - The path where the error has occurred.
-
locations - contains the graph coordinates where an error has
occurred,
-
extensions - contains additional information about errors.
- Data - contains the result of the operation.
- errors - contain the errors which occurred during the operation.
- message - description about the exception(error message)
- path - The path where the error has occurred.
- locations - contains the graph coordinates where an error has occurred,
- extensions - contains additional information about errors.
what will happen if exceptions are not handled in graphQL?
{
"data": {
"updateEmployee": null
},
"errors": [
{
"message": "Internal Server Error(s) while executing query",
}
]
}
Custom Exception in GraphQL
public class InvalidEmployeeException extends RuntimeException implements GraphQLError {
private static final long serialVersionUID = 1 L;
private Map<String, Object> extensions = new HashMap<>();
String message;
public InvalidEmployeeException(String message, int id) {
super(message);
this.message = message;
extensions.put("invalid employeeid", id);
}
@Override
public String getMessage() {
return message;
}
@Override
public List<SourceLocation> getLocations() {
return null;
}
@Override
public List<Object> getPath() {
return null;
}
@Override
public Map<String, Object> getExtensions() {
return extensions;
}
@Override
public ErrorType getErrorType() {
return ErrorType.DataFetchingException;
}
}
Adapter class for Error Handling
public class GraphQLErrorAdapter implements GraphQLError {
private GraphQLError error;
public GraphQLErrorAdapter(GraphQLError error) {
this.error = error;
}
@Override
public Map<String, Object> getExtensions() {
return error.getExtensions();
}
@Override
public List<SourceLocation> getLocations() {
return error.getLocations();
}
@Override
public ErrorType getErrorType() {
return error.getErrorType();
}
@Override
public List<Object> getPath() {
return error.getPath();
}
@Override
public Map<String, Object> toSpecification() {
return error.toSpecification();
}
@Override
public String getMessage() {
return (error instanceof ExceptionWhileDataFetching) ?
((ExceptionWhileDataFetching) error).getException().getMessage() :
error.getMessage();
}
}
Create Error Handler
@Component
public class ErrorHandler implements GraphQLErrorHandler {
@Override
public List<GraphQLError> processErrors(List<GraphQLError> errors) {
List<GraphQLError> clientErrors = errors.stream()
.filter(this::isClientError)
.collect(Collectors.toList());
List<GraphQLError> serverErrors = errors.stream()
.filter(e -> !isClientError(e))
.map(GraphQLErrorAdapter::new)
.collect(Collectors.toList());
System.out.println(clientErrors);
System.out.println(serverErrors);
List<GraphQLError> e = new ArrayList<>();
e.addAll(clientErrors);
e.addAll(serverErrors);
return e;
}
protected boolean isClientError(GraphQLError error) {
return !(error instanceof ExceptionWhileDataFetching || error instanceof Throwable);
}
}