In this tutorial, we’re gonna build a Spring Boot GraphQL example that will expose CRUD APIs to create, read, update and delete objects in MongoDB database with the help of graphql-java and Spring Data.
Related Post:
– Spring Boot with MongoDB CRUD example using Spring Data
More Practice:
– Spring Boot + GraphQL + MySQL example with Spring JPA & graphql-spring-boot-starter
Spring Boot CRUD GraphQL APIs with MongoDB Overview
We have two data models: Author and Tutorial with the fields like this.
Author {
id: String
name: String
age: Integer
}
Tutorial {
id: String
title: String
description: String
author: Author
}
The goal of this example is to build a GraphQL APIs to do CRUD operations with MongoDB database using only one endpoint: /apis/graphql
.
– Create an Author:
- GraphQL
mutation {
createAuthor(
name: "bezkoder",
age: 27) {
id name
}
}
{
"data": {
"createAuthor": {
"id": "5dd7644e572b4b0f3fc558c5",
"name": "bezkoder"
}
}
}
– Create a Tutorial:
- GraphQL: requiring Tutorial id, title and author (name) for response
mutation {
createTutorial (
title: "Tutorial #1",
description: "Tut#1 Description"
author: "5dd7644e572b4b0f3fc558c5")
{
id title author { name }
}
}
{
"data": {
"createTutorial": {
"id": "5dd764a0572b4b0f3fc558c7",
"title": "Tutorial #1",
"author": {
"name": "bezkoder"
}
}
}
}
– Read all Authors:
- GraphQL
{
findAllAuthors{
id
name
age
}
}
{
"data": {
"findAllAuthors": [
{
"id": "5dd7644e572b4b0f3fc558c5",
"name": "bezkoder",
"age": 27
},
{
"id": "5dd7645f572b4b0f3fc558c6",
"name": "zKoder",
"age": 29
}
]
}
}
– Read all Tutorials:
- GraphQL
{
findAllTutorials{
id
title
description
author{
id
name
}
}
}
{
"data": {
"findAllTutorials": [
{
"id": "5dd764a0572b4b0f3fc558c7",
"title": "Tutorial #1",
"description": "Tut#1 Description",
"author": {
"id": "5dd7644e572b4b0f3fc558c5",
"name": "bezkoder"
}
},
{
"id": "5dd764dd572b4b0f3fc558c8",
"title": "Tutorial #2",
"description": "Tut#2 Description",
"author": {
"id": "5dd7644e572b4b0f3fc558c5",
"name": "bezkoder"
}
},
{
"id": "5dd764ed572b4b0f3fc558c9",
"title": "Tutorial #3",
"description": "Tut#3 Description",
"author": {
"id": "5dd7645f572b4b0f3fc558c6",
"name": "zKoder"
}
}
]
}
}
– Update a Tutorial:
- GraphQL
mutation {
updateTutorial (
id: "5dd764dd572b4b0f3fc558c8"
description: "Tut#2 updated Desc")
{
id title description author { name }
}
}
{
"data": {
"updateTutorial": {
"id": "5dd764dd572b4b0f3fc558c8",
"title": "Tutorial #2",
"description": "Tut#2 updated Desc",
"author": {
"name": "bezkoder"
}
}
}
}
– Delete a Tutorial:
- GraphQL
mutation {
deleteTutorial(id: "5dd764a0572b4b0f3fc558c7")
}
{
"data": {
"deleteTutorial": true
}
}
– Count number of Tutorials:
- GraphQL
{
countTutorials
}
{
"data": {
"countTutorials": 2
}
}
Check MongoDB database, we have 2 collections like this:
authors
tutorials
The Spring Boot GraphQL Starter will make GraphQL server running in a short time.
The GraphQL Java Tools provides some Resolver interface that helps us to resolve values for GraphQL query & mutation requests.
Build Spring Boot GraphQL APIs with MongoDB Database
Technology
Our Spring Boot Project will use:
- Java 8
- Spring Boot 2.2.1.RELEASE (with Spring Web, Spring Data MongoDB)
- graphql-spring-boot-starter 5.0.2
- graphql-java-tools 5.2.4
- Maven 3.6.1
- MongoDB 3.4.x
Project Structure
This is folders & files structure for our Spring Boot + GraphQL + MongoDB application:
– resources/graphql contains .graphqls
files that define GraphQL chemas.
– model holds two data models: Author
and Tutorial
.
– repository contains Repository interfaces to interact with MongoDB database.
– resolver resolves values for query & mutation requests by implementing some Resolver
interfaces from GraphQL Java Tools.
– application.properties configures Spring Data MongoDB and GraphQL base Url.
– pom.xml includes dependencies for the whole project.
Set up the project
Create new Spring Boot project using Spring Tool Suite or going to https://start.spring.io/.
Then add these dependencies to pom.xml file:
<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>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>
Configure Spring Data, GraphQL
Open application.properties and add the following configuration.
spring.data.mongodb.database=bezkoder_mongodb
spring.data.mongodb.port=27017
# Graphql
graphql.servlet.mapping: /apis/graphql
By default, Spring Boot GraphQL starter exposes the GraphQL Service on /graphql
endpoint for HTTP POST requests containing the GraphQL payload. In the code above, we config the endpoint with new base url: /apis/graphql
.
Create GraphQL Schema
We’re gonna split up your schema into two .graphqls
files. The Spring Boot GraphQL starter will automatically find these schema files.
Under src/main/resources folder, create author.graphqls and tutorial.graphqls files.
author.graphqls
type Author {
id: ID!
name: String!
age: Int
}
# Root
type Query {
findAllAuthors: [Author]!
countAuthors: Long!
}
# Root
type Mutation {
createAuthor(name: String!, age: Int): Author!
}
GraphQL accepts only one root Query
and one root Mutation
types, so we need to bring all the query and mutation operations into the root Types. But in the schemas above, we want to split the logic for each model. How to do this? We extend the Query
and Mutation
types.
tutorial.graphqls
type Tutorial {
id: ID!
title: String!
description: String
author: Author
}
extend type Query {
findAllTutorials: [Tutorial]!
countTutorials: Long!
}
extend type Mutation {
createTutorial(title: String!, description: String, author: ID!): Tutorial!
updateTutorial(id: ID!, title: String, description: String): Tutorial!
deleteTutorial(id: ID!): Boolean
}
The “!” at the end of some fields indicates non-nullable type. If we don’t use it, GraphQL accepts null
value in the response.
Define Data Models
Now we define twom main models with One-to-Many Relationship in model package:
- Author: id, name, age
- Tutorial: id, title, description, author_id
Author.java
package com.bezkoder.springgraphql.mongodb.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection = "authors")
public class Author {
@Id
private String id;
private String name;
private Integer age;
public Author() {
}
public Author(String id) {
this.id = id;
}
public Author(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 "User [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
Tutorial.java
package com.bezkoder.springgraphql.mongodb.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection = "tutorials")
public class Tutorial {
@Id
private String id;
private String title;
private String description;
private String author_id;
public Tutorial() {
}
public Tutorial(String title, String description, String author_id) {
this.title = title;
this.description = description;
this.author_id = author_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 getAuthorId() {
return author_id;
}
public void setAuthorId(String author_id) {
this.author_id = author_id;
}
@Override
public String toString() {
return "Tutorial [id=" + id + ", title=" + title + ", description=" + description + ", author_id=" + author_id + "]";
}
}
Create Repositories
In repository package, create two interfaces that implement MongoRepository
.
AuthorRepository.java
package com.bezkoder.springgraphql.mongodb.repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.bezkoder.springgraphql.mongodb.model.Author;
public interface AuthorRepository extends MongoRepository<Author, String> {
}
TutorialRepository.java
package com.bezkoder.springgraphql.mongodb.repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.bezkoder.springgraphql.mongodb.model.Tutorial;
public interface TutorialRepository extends MongoRepository<Tutorial, String> {
}
Once we extends the MongoRepository
, Spring Data MongoDB will automatically generate implementation with find, save, delete, count methods for the Documents.
Implement GraphQL Root Query Resolver
Every field in the schema root query should have a method in the Query Resolver class with the same name.
Now look back to the schemas we defined above.
# author.graphqls
type Query {
findAllAuthors: [Author]!
countAuthors: Long!
}
# tutorial.graphqls
extend type Query {
findAllTutorials: [Tutorial]!
countTutorials: Long!
}
This Query
class implements GraphQLQueryResolver
.
resolver/Query.java
package com.bezkoder.springgraphql.mongodb.resolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.bezkoder.springgraphql.mongodb.model.Author;
import com.bezkoder.springgraphql.mongodb.model.Tutorial;
import com.bezkoder.springgraphql.mongodb.repository.AuthorRepository;
import com.bezkoder.springgraphql.mongodb.repository.TutorialRepository;
import com.coxautodev.graphql.tools.GraphQLQueryResolver;
@Component
public class Query implements GraphQLQueryResolver {
private AuthorRepository authorRepository;
private TutorialRepository tutorialRepository;
@Autowired
public Query(AuthorRepository authorRepository, TutorialRepository tutorialRepository) {
this.authorRepository = authorRepository;
this.tutorialRepository = tutorialRepository;
}
public Iterable<Author> findAllAuthors() {
return authorRepository.findAll();
}
public Iterable<Tutorial> findAllTutorials() {
return tutorialRepository.findAll();
}
public long countAuthors() {
return authorRepository.count();
}
public long countTutorials() {
return tutorialRepository.count();
}
}
Implement GraphQL Root Mutation Resolver
Mutation class will implement GraphQLMutationResolver
.
Just like Query Resolver, every field in the schema mutation query should have a method in the Mutation Resolver class with the same name.
resolver/Mutation.java
package com.bezkoder.springgraphql.mongodb.resolver;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.bezkoder.springgraphql.mongodb.model.Author;
import com.bezkoder.springgraphql.mongodb.model.Tutorial;
import com.bezkoder.springgraphql.mongodb.repository.AuthorRepository;
import com.bezkoder.springgraphql.mongodb.repository.TutorialRepository;
import com.coxautodev.graphql.tools.GraphQLMutationResolver;
@Component
public class Mutation implements GraphQLMutationResolver {
private AuthorRepository authorRepository;
private TutorialRepository tutorialRepository;
@Autowired
public Mutation(AuthorRepository authorRepository, TutorialRepository tutorialRepository) {
this.authorRepository = authorRepository;
this.tutorialRepository = tutorialRepository;
}
public Author createAuthor(String name, Integer age) {
Author author = new Author();
author.setName(name);
author.setAge(age);
authorRepository.save(author);
return author;
}
public Tutorial createTutorial(String title, String description, String authorId) {
Tutorial book = new Tutorial();
book.setAuthorId(authorId);
book.setTitle(title);
book.setDescription(description);
tutorialRepository.save(book);
return book;
}
public boolean deleteTutorial(String id) {
tutorialRepository.deleteById(id);
return true;
}
public Tutorial updateTutorial(String id, String title, String description) throws Exception {
Optional<Tutorial> optTutorial = tutorialRepository.findById(id);
if (optTutorial.isPresent()) {
Tutorial tutorial = optTutorial.get();
if (title != null)
tutorial.setTitle(title);
if (description != null)
tutorial.setDescription(description);
tutorialRepository.save(tutorial);
return tutorial;
}
throw new Exception("Not found Tutorial to update!");
}
}
Implement GraphQL Field Resolver
For complex fields like author
in Tutorial
, we have to resolve the value of those fields.
TutorialResolver
implements GraphQLResolver
interface and has getAuthor()
method.
resolver/TutorialResolver.java
package com.bezkoder.springgraphql.mongodb.resolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.bezkoder.springgraphql.mongodb.model.Author;
import com.bezkoder.springgraphql.mongodb.model.Tutorial;
import com.bezkoder.springgraphql.mongodb.repository.AuthorRepository;
import com.coxautodev.graphql.tools.GraphQLResolver;
@Component
public class TutorialResolver implements GraphQLResolver<Tutorial> {
@Autowired
private AuthorRepository authorRepository;
public TutorialResolver(AuthorRepository authorRepository) {
this.authorRepository = authorRepository;
}
public Author getAuthor(Tutorial tutorial) {
return authorRepository.findById(tutorial.getAuthorId()).orElseThrow(null);
}
}
If the client want to get a Tutorial
without author
field, the GraphQL Server will never do the work to retrieve it. So the getAuthor()
method above will never be executed.
Run & Check result
Run Spring Boot application with command: mvn spring-boot:run
.
For checking result, you can use Postman to make HTTP POST request to http://localhost:8080/apis/graphql
.
Conclusion
Today we’ve learned how to use the GraphQL Java Tools to build a Spring Boot CRUD GraphQL Apis that can communicate with MongoDB database using Spring Data MongoDB.
Happy learning! See you again.
Further Reading
- GraphQL documentation
- https://www.graphql-java.com/tutorials/getting-started-with-spring-boot/
- graphql-java Github
- Spring Data MongoDB – Reference Documentation
Great tutorial! Everything worked in the first try.