spring boot

Spring Boot + MongoDB +GraphQL demo with Spring Data & graphql-java

Build a CRUD API with GraphQL, Spring Boot and MongoDB

In this article we are going to discuss about REST API with spring data and graphql with spring data.

GraphQL:-

GraphQL is an open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. GraphQL was developed internally by Facebook in 2012 before being publicly released in 2015.

SpringData:-

Spring Data JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that’s actually needed.

MongoDB:-

MongoDB is a source-available cross-platform document-oriented database program. Classified as a NoSQL database program, MongoDB uses JSON-like documents with optional schemas. 

Tech Stack:-

  1. Java
  2. SpringBoot
  3. Spring Data JPA
  4. MongoDB
  5. Graphql
  6. Maven
  7. Docker (to run mongoDB server on local)
  8. Junit

Let us go ahead and visit http://start.spring.io/ and create project with required dependencies.

Once project downloaded open it into your editor or IDE and check the pom.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.5</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.tcehwasti</groupId>
	<artifactId>sdgpqlmongoex</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>sdgpqlmongoex</name>
	<description>Spring Data mongoDb graphql Demo</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<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-data-mongodb</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-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Add below dependencies into the pom.xml file.

<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>

Now Let us first create the schema for mongoDB. Let us define the data model.

Teacher {
  id: String
  name: String
  age: Integer
}

Subject {
  id: String
  title: String
  description: String
  teacher: Teacher
}

here in the above model, we want to create two data entities teacher and subject. Which subject is associated with which teacher? As the goal of this about graphql and spring data CRUD, so create first mutation define for graphql.

mutation {
  createTeacher(
    name: "RamPrasad",
    age: 32) {
      id name
  }
}

Response we are expecting like below.

{
  "data": {
    "createTeacher": {
      "id": "4bc5644f572b4b0a3ad558c5",
      "name": "RamPrasad"
    }
  }
}

One more mutation to get subject details along with teacher

mutation {
  createSubject (
    title: "Subject #1",
    description: "Sub#1 Description"
    author: "4bc5644f572b4b0a3ad558c5")
    {
      id title teacher { name }
    }
}

Respopnse.

{
  "data": {
    "createSubject": {
      "id": "4bc5644f572b4b0a3ad558c5",
      "title": "Subject #1",
      "author": {
        "name": "RamPrasad"
      }
    }
  }
}

To Read all teachers data.

{
  findAllTeachers{
    id
    name
    age
  }
}

Read all Subjects.

{
  findAllSubjects{
    id
    title
    description
    teacher{
      id
      name
    }
  }
}

Update the subject.

mutation {
  updateSubject (
    id: "4bc5644f572b4b0a3ad558c5"
    description: "Sub#2 updated Desc")
    {
      id title description teacher { name }
    }
}

Delete the subject.

mutation {
  deleteSubject(id: "5dd764a0572b4b0f4bc5644")
}

Count number of subjects we are providing.

{
  countSubjects
}

Create graphQL Schema file inside the src/main/resource and file extention should be “.graphqls” files.

schemadata. graphqls

type Teacher {
	id: ID!
	name: String!
	age: Int
}

# Root
type Query {
	findAllTeachers: [Teacher]!
	countTeachers: Long!
}

# Root
type Mutation {
	createTeacher(name: String!, age: Int): Teacher!
}

=============for subject =========================
type Subject {
	id: ID!
	title: String!
	description: String
	teacher: Teacher
}

extend type Query {
	findAllSubjects: [Subject]!
	countSubjects: Long!
}

extend type Mutation {
	createSubject(title: String!, description: String, teacher: ID!): Subject!
	updateSubject(id: ID!, title: String, description: String): Subject!
	deleteSubject(id: ID!): Boolean
}

The “!” at the end of some fields indicates non-nullable type.

Define the data model for our MongoDB:

To define the data model we have two entities and they have one to many relationship.

  • Teacher: id, name, age
  • Subject: id, title, description, teacher_id

Teacher.java

package com.tcehwasti.sdgpqlmongoex.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "teachers")
public class Teacher {
    @Id
  private String id;
  private String name;
  private Integer age;

  public Teacher() {
  }

  public Teacher(String id) {
    this.id = id;
  }

  public Teacher(String name, Integer age) {
    this.name = name;
    this.age = age;
  }

  public String getId() {
    return id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Integer getAge() {
    return age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }

  @Override
  public String toString() {
    return "Teacher [id=" + id + ", name=" + name + ", age=" + age + "]";
  }
}

Subject.java

package com.tcehwasti.sdgpqlmongoex.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "subjects")
public class Subject {
    
    @Id
    private String id;
    private String title;
    private String description;
    private String teacher_id;
  
    public Subject() {
    }
  
    public Subject(String title, String description, String teacher_id) {
      this.title = title;
      this.description = description;
      this.teacher_id = teacher_id;
    }
  
    public String getId() {
      return id;
    }
  
    public String getTitle() {
      return title;
    }
  
    public void setTitle(String title) {
      this.title = title;
    }
  
    public String getDescription() {
      return description;
    }
  
    public void setDescription(String description) {
      this.description = description;
    }
  
    public String getTeacherId() {
      return teacher_id;
    }
  
    public void setTeacherId(String teacher_id) {
      this.teacher_id = teacher_id;
    }
  
    @Override
    public String toString() {
      return "Subject [id=" + id + ", title=" + title + ", description=" + description + ", teacher_id=" + teacher_id + "]";
    }
}

Data Access layer nothing but repositories:

Repositories help us to do CRUD operation, create repository interfaces and that will be extending from MongoRepository.

TeacherRepository.java

package com.tcehwasti.sdgpqlmongoex.repositories;

import org.springframework.data.mongodb.repository.MongoRepository;
import com.tcehwasti.sdgpqlmongoex.model.Teacher;

public interface TeacherRepository  extends MongoRepository<Teacher, String>{
    
}

SubjectRepository.java

package com.tcehwasti.sdgpqlmongoex.repositories;

import com.tcehwasti.sdgpqlmongoex.model.Subject;

import org.springframework.data.mongodb.repository.MongoRepository;
import com.tcehwasti.sdgpqlmongoex.model.Subject;

public interface SubjectRepository  extends MongoRepository<Subject, Long>{
    
}

When we extends the MongoRepository, Spring Data MongoDB will implicitly generate implementation with find, save, delete, count methods for the mongodb documents.

GraphQL Resolver:

Till now schema defined, data model defined and entity along with repositories are also ready. Now we need root query resolver that will resolve the graphql queries and help us to returning the result.

Let us recheck out schemas defined.


type Query {
	findAllTeachers: [Teacher]!
	countTeachers: Long!
}

extend type Query {
	findAllSubjects: [Subject]!
	countSubjects: Long!
}

To implement the query resolver we have to implement the GraphQLQueryResolver interface.

package com.tcehwasti.sdgpqlmongoex.resolver;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.tcehwasti.sdgpqlmongoex.model.Subject;
import com.tcehwasti.sdgpqlmongoex.model.Teacher;
import com.tcehwasti.sdgpqlmongoex.repositories.SubjectRepository;
import com.tcehwasti.sdgpqlmongoex.repositories.TeacherRepository;
import com.coxautodev.graphql.tools.GraphQLQueryResolver;

@Component
public class QueryResolver implements GraphQLQueryResolver {
    
    private TeacherRepository teacherRepository;
    private SubjectRepository subjectRepository;
  
    @Autowired
    public QueryResolver(TeacherRepository teacherRepository, SubjectRepository subjectRepository) {
      this.teacherRepository = teacherRepository;
      this.subjectRepository = subjectRepository;
    }
  
    public Iterable<Teacher> findAllTeachers() {
      return teacherRepository.findAll();
    }
  
    public Iterable<Subject> findAllSubjects() {
      return subjectRepository.findAll();
    }
  
    public long countTeachers() {
      return teacherRepository.count();
    }
  
    public long countSubjects() {
      return subjectRepository.count();
    }
}

root query should have a method in the Query Resolver class with the same name

Mutation Resolver:

MutationResolver have to implement the GraphQLMutationResolver interface. Same like query resolver, every field in the schema mutation query should have a method in the Mutation Resolver class with the same name.

package com.tcehwasti.sdgpqlmongoex.resolver;


import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.tcehwasti.sdgpqlmongoex.model.Subject;
import com.tcehwasti.sdgpqlmongoex.model.Teacher;
import com.tcehwasti.sdgpqlmongoex.repositories.SubjectRepository;
import com.tcehwasti.sdgpqlmongoex.repositories.TeacherRepository;
import com.coxautodev.graphql.tools.GraphQLMutationResolver;

@Component
public class MutationResolver implements GraphQLMutationResolver {

    private TeacherRepository teacherRepository;
    private SubjectRepository subjectRepository;

  @Autowired
  public MutationResolver(TeacherRepository teacherRepository,SubjectRepository subjectRepository) {
    this.teacherRepository = teacherRepository;
    this.subjectRepository = subjectRepository;
  }

  // first create teacher and then pass teacher id to subject
  public Teacher createTeacher(String name, Integer age) {
    Teacher teacher = new Teacher();
    teacher.setName(name);
    teacher.setAge(age);

    teacherRepository.save(teacher);

    return teacher;
  }

  public Subject createSubject(String title, String description, String teacherId) {
    Subject subject = new Subject();
    subject.setTeacherId(teacherId);
    subject.setTitle(title);
    subject.setDescription(description);

    subjectRepository.save(subject);

    return subject;
  }

  public boolean deleteSubject(String id) {
    subjectRepository.deleteById(id);
    return true;
  }

  public SubjectRepository updateSubject(String id, String title, String description) throws Exception {
    Optional<Subject> optSubject = subjectRepository.findById(id);

    if (optSubject.isPresent()) {
      Subject subject = optSubject.get();

      if (title != null)
        subject.setTitle(title);
      if (description != null)
        subject.setDescription(description);

      subjectRepository.save(subject);
      return subjectRepository;
    }

    throw new Exception("Not found Subject to update!");
  }
    
}

Field Resolver:

To resolve the field we have to implement GraphQLResolver. This help us to resolve the complex fields such as subject which holds the teacher internally.

package com.tcehwasti.sdgpqlmongoex.resolver;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.coxautodev.graphql.tools.GraphQLResolver;
import com.tcehwasti.sdgpqlmongoex.model.Subject;
import com.tcehwasti.sdgpqlmongoex.model.Teacher;
import com.tcehwasti.sdgpqlmongoex.repositories.TeacherRepository;


@Component
public class SubjectResolver implements GraphQLResolver<Subject>{
    
    @Autowired
  private TeacherRepository teacherRepository;

  public SubjectResolver(TeacherRepository teacherRepository) {
    this.teacherRepository = teacherRepository;
  }

  public Teacher getAuthor(Subject subject) {
    return teacherRepository.findById(subject.getTeacherId()).orElseThrow(null);
  }
}

Now 90% work is done, your application class should be look like below.

SdgpqlmongoexApplication.java

package com.tcehwasti.sdgpqlmongoex;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SdgpqlmongoexApplication {

	public static void main(String[] args) {
		SpringApplication.run(SdgpqlmongoexApplication.class, args);
	}

}

Update application.properties with below URLs.

application.properties

spring.data.mongodb.database=school_db
#default port you can change it as per your settings
spring.data.mongodb.port=27017  

# Graphql
graphql.servlet.mapping: /apis/graphql

Run the application:

Now run the application by using below command check your coding.

./mvnw spring-boot:run

Reference Link:-

Conclusion:-

In this article we discussed about the graphql and mongodb along spring data integration and how efficiently we can use graphql along with nosql like mongodb and provide the simple interface to API consumers.

Source Code:

$ git clone https://github.com/maheshwarLigade/spring-boot-examples.git
$ cd sdgpqlmongoex
$./mvnw spring-boot:run