GraphQL Server with Spring Boot

Introduction

In the earlier article, we talked about GraphQL getting popularity replacing REST and did some comparison between those two. In this article, we will build a sample app for GraphQL and see how to create APIs using GraphQL server and client. GraphQL servers can be built using NodeJS, Spring, and other supported frameworks. In another article, we already explored the GraphQL with Express-NodeJS. In this article, We are going to do that using the Spring Boot with graphql java tools library. 
Like an earlier article, we are going to build an API for Class and Student resources. We will also define some relationships between class and students to see the power of GraphQL. In this example, we will show how a class can have multiple students and can be queried accordingly.

Pre-requisites

  • Familiarity with Spring boot
  • Any IDE
  • Familiarity with Maven build
  • A MongoDB database - You can use MongoDB atlas which gives a free account and sufficient for this article. 
In this article, we will just explore the GraphQL server only. A client can be built using apollo or relay as per your need. You can see the sample implementation in the earlier article.

GraphQL Server

The server is like an API that will implement the functionality for each graphql query by integrating with DB, third party services, etc... It is a bit different from the express-nodejs setup as spring boot has come up with the graphql-java-tools library to eliminate the configuration of a huge boilerplate code.

Step 1 - Set up a Maven Dependency

  1. spring-boot-starter-web - It is a standard library for a spring boot application to bring all the dependencies together required for a web application.

  2. spring-boot-starter-data-mongodb -This jar is required for spring data implementation with mongodb entity classes.

  3. graphql-spring-boot-starter - It is used for enabling GraphQL servlet, and it becomes available at a path /graphql. This path value can be customized by updating the properties in application.properties. It initializes the GraphQLSchema bean. It will add graphql-java and graphql-java-servlet as dependencies of your project. 

  4. graphql-java allows us to write schema with GraphQL schema language, which is simple to understand.

  5. graphiql-spring-boot-starter - It provides a user interface with which GraphQL queries can be tested and can view query definitions. It exposes /graphiql endpoint.

  6. graphql-java-tools - This is the most important library. This library works magically to parse GraphQL Schema and map the GraphQL objects with your own POJOs (Entity classes)
pom
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>    
        <dependency>
            <groupId>com.graphql-java</groupId>
            <artifactId>graphql-spring-boot-starter</artifactId>
            <version>5.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.graphql-java</groupId>
            <artifactId>graphql-java-tools</artifactId>
            <version>5.2.4</version>
        </dependency>
        <dependency>
            <groupId>com.graphql-java</groupId>
            <artifactId>graphiql-spring-boot-starter</artifactId>
            <version>5.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
            <optional>true</optional>
        </dependency>
</dependencies>

Step 2 - Application Properties

Add properties for the MongoDB connection:
spring.data.mongodb.uri=mongodb+srv://graphql:graphql@graphqldb-tochh.mongodb.net/test?retryWrites=true&w=majority
spring.data.mongodb.database=test

Step 3 - Define GraphQL Schema

GraphQL comes with its own language to write GraphQL Schemas called Schema Definition Language (SDL). The schema definition consists of all the API functionalities available at an endpoint.
Add the required Query and Mutation Schema around Classes and Students Collections. This file has to be kept in the resources folder with graphqls extension.
.graphqls
type StudentClass {
    _id: ID!,
    grade: String,
    sections: String
    students: [Student]
}
type Student{
        _id: ID!,
        name: String,
        dresscode: String
        classid: String
}
type Query {
    classes(count: Int):[StudentClass]
    class(_id: ID):StudentClass
    students(count: Int):[Student]
     
}
type Mutation {
    addClass(grade: String!, sections: String!):StudentClass
    addStudent(name: String!, dresscode: String!, classid: String!):Student
}

Step 4 - Define Document Entities for MongoDB

Define both students and classes Document. the id field is auto-generated.
students
@Data
@Document(collection = "students")
public class Student implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private String _id;
    private String name;
    private String dresscode;
    private String classid;
}
classes
@Data
@Document(collection = "classes")
public class StudentClass implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private String _id;
    private String grade;
    private String sections;
    private List<Student> students;
         
  }
}

Step 5 -  Define Repositories

Now, define the repositories for both the documents:
There is one extra method findBy_id has been added to search the StudentClass and Students by _id field. As _id field is ObjectId type in MongoDB, it has to be defined this way to get work.
Another method has been added to Student Repository to get the list of students by searching with a classid.
@Repository
public interface StudentClassRepository extends MongoRepository<StudentClass, String > {
    StudentClass findBy_id(String id);
}
@Repository
public interface StudentRepository extends MongoRepository<Student, String > {
   Student findBy_id(String id);
   List<Student> findStudentsByClassid(String id);
}

Step 6 -  Define Service Implementation

We need to implement the Student and Class service implementation with repository calls. Example of StudentService implementation:

Service Implementation Collapse source
@Service
public class StudentService {
    private final StudentRepository studentRepository ;
    public StudentService(final StudentRepository studentRepository) {
        this.studentRepository = studentRepository ;
    }
    @Transactional
    public Student addStudent(final String name,final String dresscode, final String classid) {
        final Student student = new Student();
        student.setName(name);
        student.setDresscode(dresscode);
        student.setClassid(classid);
        
        return this.studentRepository.save(student);
    }
    @Transactional(readOnly = true)
    public List<Student> getAllStudents(StudentClass classObj) {
        return this.studentRepository.findStudentsByClassid(classObj.get_id()).stream().collect(Collectors.toList());
    }
    @Transactional(readOnly = true)
    public List<Student> getAllStudents(final int count) {
        return this.studentRepository.findAll().stream().limit(count).collect(Collectors.toList());
    }
    @Transactional(readOnly = true)
    public Optional<Student> getStudent(final String id) {
        return this.studentRepository.findById(id);
    }
}

Step 7 -  Define Resolvers

Query or Mutation objects are root GraphQL objects. They need resolvers to map the fields defined in root types.  These resolvers have methods that will be called when those root type fields are requested.
For example, In Query root type, we have a field called students so the resolver class which implements GraphQLQueryResolver needs to have a method with name getStudents() or students() with similar signature defined in the schema. If it doesn't find, it should through a runtime error.
Mutation - Mutation is used to insert and update the records. GraphQL Java has specific resolver GraphQLMutationResolver for Mutation handling.
GraphQLMutationResolver
@Component
public class Mutation implements GraphQLMutationResolver {
   
    @Autowired
    private ClassService classService;
    @Autowired
    private StudentService studentService;
    public Student addStudent(final String name, final String dresscode, final String classid) {
        return this.studentService.addStudent(name, dresscode, classid);
    }
    public StudentClass addClass(final String grade, final String sections) {
        return this.classService.addClass(grade, sections);
    }
  
}
Query - GraphQL Java has specific resolver GraphQLQueryResolver to handle all the query schemas
GraphQLQueryResolver
@Component
public class QueryResolver implements GraphQLQueryResolver {
    @Autowired
    private ClassService classService;
    @Autowired
    private StudentService studentService;
    public List<StudentClass> getClasses(final int count) {
        return this.classService.getAllClasses(count);
    }
    public Optional<StudentClass> getClass(final String id) {
        return this.classService.getClass(id);
    
    public List<Student> getStudents(final int count) {
        return this.studentService.getAllStudents(count);
    }
}
There is no resolver required for graphql scalar types like Int, String, etc.. However, for a complex object, a separate resolver will be required. For example, StudentClass which is referred by classes field in Query type has students field. To resolve the students field, it would need GraphQLResolver. 
GraphQLResolver
@Component
public class StudentClassResolver implements GraphQLResolver<StudentClass> {
    @Autowired
    private StudentService studentService;
    public List<Student> getStudents(StudentClass classObj) {
        return this.studentService.getAllStudents(classObj);
    
}

Test the GraphQL Server

We can launch the graphiql UI using http://localhost:8080/graphiql

We can pass the graphql compatible input queries in that and test all the queries.

Summary 

We have built a simple Spring boot app with GraphQL java library. With the help of graphql-java-tools, we avoided the implementation of DataFetchers, RuntimeWiring, and other classes. The implementation is very straightforward and can work with any Database, Entities, POJOs.
As usual, the code can be found on GitHub: graphql-java

No comments: