Introduction

This article will provide details of querying GraphQL API using GraphiQL, Relationship and rich relationship between nodes and exception handling using GraphQLErrorHandler part of GraphQL servlet.

GraphiQL

GraphiQL is a GUI tool for editing and testing GraphQL queries and mutations. It is useful during testing and development but should be disabled in production by default. To enable this in the application we just need to add the following in dependencies.

Once we are done with adding the dependency and our server is up we can access it by default on http://localhost:<PORT>/graphiql. And it looks like the below image.

The leftmost panel contains the query to be executed. In this case, we are executing the “memberByName” query with the argument field “name” and it is followed by the request fields of Member object. The center panel contains the response data of the query. The rightmost panel contains all type nodes, relationships, and queries with their description and fields. We can also search for a specific node, query or relationship in the panel.

Relationships in GraphDB

A relationship is a connection between 2 nodes. It is an edge of a graph that has a start node, end node, and direction. There is no limit to the number and kind of relationships a node can have. In order to establish a relation in GraphQL we need to do it at type level and at the backend where we created the POJO class of the type in java.  In our use case, Member object is related to Skill, Certifications, Reporting Manager(Member), Asset, Contribution, and Project.  Following is a sample of use case followed by type and POJO objects.

Member type object:

POJO in Java sample:

@NodeEntity
public class Member {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue
private Long id;
@Relationship(type = “HAS_CONTRIBUTION”, direction = Relationship.OUTGOING)
private Contributions contribution;

@Relationship(type = “KNOWS_SKILLS”, direction = Relationship.OUTGOING)
private Skill skill;

@Relationship(type = “CREATED_ASSET”, direction = Relationship.OUTGOING)
private Assets asset;

@Relationship(type = “REPORTS_TO” ,direction = Relationship.OUTGOING )
private Member member;

@Relationship(type = “IS_BILLED_FOR”)
private MemberProjectRelation memberProjectRelation;

@Relationship(type = “IS_CERTIFIED_IN”)
private MemberCertificationRelation memberCertificationRelation;

private Long empId;

private String name;

private String location;

private String experience;

private String title;

— Getters & Setters —

}

Rich Relationships

Till now we have been learning about simple relationships. There is always a need for relationships to contain properties for querying purposes. These relationships are known as rich relationships. In the above example, there are 2 relationships which are part of rich category i.e. Project and certification relation. In order to know when was the certification done or when was a Member associated with a project, such answers could only be answered using rich relationships.

We need to create a separate POJO class of the relationship with annotation @RelationshipEntity and add the fields to it with two mandatory annotations specifying starting node and end node. We can have any number of fields depending upon the use case. Following is the code sample:

@RelationshipEntity(type = “PART_OF”)
public class MemberProjectRelation {

public String startDate;

public String endDate;

@StartNode
Member member;

@EndNode
Project project;

— Getters & Setters —

}

Exception Handling

GraphQL returns errors with descriptive messages for client requests having syntax errors or in case of a requested field does not exist without any configuration. It will indicate that an internal error has occurred in the server if there is some exception not handled properly or exception handling is not configured. In order to send meaningful messages or requirements is to throw a custom business exception in response we need to change the behavior of the GraphQLErrorHandler class.

We need to create an adapter class for sending the desired custom error. The adapter must only implement the GraphQLError interface and not inherit Exception class. We need to change the logic of method getMessage() in order to make a response message crisp and clear rather than full error stack.

public class ErrorAdapter implements GraphQLError {

private GraphQLError error;

public ErrorAdapter(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();
}
}

After adapter is created we need an Exception class that extends the Exception class and also implements GraphQLError. We can create different business exception classes or 1 generic class to handle all server exceptions. Or we can have both of them at the same time. The sample of business exception class is attached below.

To adapt to the exception class created above, we need to change the behavior of the GraphQLErrorHandler class so that it transforms the exceptions to the GraphQLError class itself. This class is indicated when building the SimpleGraphQLServlet and ServletRegistrationBean object.

@Bean
public GraphQLErrorHandler graphQLErrorHandler() {
return new DefaultGraphQLErrorHandler() {
@Override
public List<GraphQLError> processErrors(List<GraphQLError> errors) {

List<GraphQLError> clientErrorList = errors.stream()
.filter(e -> isClientError(e))
.map(ErrorAdapter::new)
.collect(Collectors.toList());

           List<GraphQLError>serverErrorList = errors.stream()
.filter(e -> !isClientError(e))
.map(ErrorAdapter::new)
.collect(Collectors.toList());

List<GraphQLError> errorList = new ArrayList<>();
errorList.addAll(clientErrorList);
errorList.addAll(serverErrorList);
return errorList;
}
};
}

To  Be Continued….

In this article, we learned Graphiql and how we can use it for testing and development, Relationships and its properties, and Error handling for sending meaningful messages in response. In the next article, we will know more about querying graph DB using cypher queries and we also touch upon Neo4j OGM library and Spring data neo4j repository.

References

https://github.com/preetpramati/SpringBootNeo4JAPI

https://grandstack.io/docs/graphql-relationship-types.html

https://picodotdev.github.io/blog-bitix/2017/11/devolver-mensajes-de-error-descriptivos-en-graphql/