Spring Boot + GraphQL example
Tags: webdev, springboot, graphql, todayilearned
Published: 2023-11-15 03:32:28

GraphQL is a query language that offers an alternative model to developing APIs (REST, SOAP or gRPC) with detailed description.

In this tutorial, we're gonna build a Spring Boot GraphQL example with H2 database that will expose CRUD APIs to create, read, update and delete objects with the help of <code>graphql-spring-boot-starter</code> and <a href="https://docs.spring.io/spring-data/jpa/docs/current/reference/html/">Spring Data JPA</a>.

Related Post:
- <a href="https://www.bezkoder.com/spring-boot-graphql-mysql-jpa/">Spring Boot + GraphQL + MySQL example</a>
- <a href="https://www.bezkoder.com/spring-boot-graphql-postgresql/">Spring Boot + GraphQL + PostgreSQL example</a>
- <a href="https://www.bezkoder.com/spring-boot-graphql-mongodb-example-graphql-java/">Spring Boot + GraphQL + MongoDB example</a>
- <a href="https://www.bezkoder.com/spring-boot-3-rest-api/">Spring Boot 3 Rest API example: CRUD Application</a>
- <a href="https://www.bezkoder.com/spring-boot-security-login-jwt/">Spring Boot Security: Login and Registration example with JWT</a>
- <a href="https://www.bezkoder.com/spring-boot-thymeleaf-example/">Spring Boot Thymeleaf CRUD example</a>
- Documentation: <a href="https://www.bezkoder.com/spring-boot-swagger-3/">Spring Boot Swagger 3 example</a>
- Unit Test: <a href="https://www.bezkoder.com/spring-boot-unit-test-jpa-repo-datajpatest/">@DataJpaTest example for Spring Data Repository</a>
- Caching: <a href="https://www.bezkoder.com/spring-boot-redis-cache-example/">Spring Boot Redis Cache example</a>

<h2>Spring Boot GraphQL example</h2>
<h3>Overview</h3>
We have two data models: Author and Tutorial.
```graphql
Author {
  id: Long
  name: String
  age: Integer
}

Tutorial {
  id: Long
  title: String
  description: String
  author: Author
}
```
One Author will have many Tutorials, so it is One-to-Many relationship.

The goal of this example is to build a GraphQL APIs to do CRUD operations with H2 database using only one endpoint: <code>/apis/graphql</code>.

The Spring Boot GraphQL Starter will make GraphQL server running in a short time. 

<h3>CRUD GraphQL APIs</h3>
- Create an Author:
GraphQL
```json
mutation {
  createAuthor(
    name: "bezkoder",
    age: 27) {
      id name
  }
}
```
Response
```json
{
  "data": {
    "createAuthor": {
      "id": "1",
      "name": "bezkoder"
    }
  }
}
```
</ul>

- Create a Tutorial:
GraphQL: we want response including Tutorial id, title and author (name)
```json
mutation {
  createTutorial (
    title: "Tutorial #1",
    description: "Description for Tut#1"
    author: 1)
    {
      id title author { name }
    }
}
```
Response
```json
{
  "data": {
    "createTutorial": {
      "id": "1",
      "title": "Tutorial #1",
      "author": {
        "name": "bezkoder"
      }
    }
  }
}
```

- Read all Authors:
GraphQL
```json
{
  findAllAuthors{
    id
    name
    age
  }
}
```
Response
```json
{
  "data": {
    "findAllAuthors": [
      {
        "id": "1",
        "name": "bezkoder",
        "age": 27
      },
      {
        "id": "2",
        "name": "zKoder",
        "age": 30
      }
    ]
  }
}
```

- Read all Tutorials:
GraphQL
```json
{
  findAllTutorials{
    id
    title
    description
    author{
      id
      name
    }
  }
}
```
Response
```json
{
  "data": {
    "findAllTutorials": [
      {
        "id": "1",
        "title": "Tutorial #1",
        "description": "Description for Tut#1",
        "author": {
          "id": "1",
          "name": "bezkoder"
        }
      },
      {
        "id": "2",
        "title": "Tutorial #2",
        "description": "Description for Tut#2",
        "author": {
          "id": "1",
          "name": "bezkoder"
        }
      },
      {
        "id": "3",
        "title": "Tutorial #3",
        "description": "Description for Tut#3",
        "author": {
          "id": "2",
          "name": "zKoder"
        }
      }
    ]
  }
}
```
</ul>

- Update a Tutorial:
GraphQL
```json
mutation {
  updateTutorial (
    id: 2
    description: "updated Desc Tut#2")
    {
      id title description author { name }
    }
}
```
Response
```json
{
  "data": {
    "updateTutorial": {
      "id": "2",
      "title": "Tutorial #2",
      "description": "updated Desc Tut#2",
      "author": {
        "name": "bezkoder"
      }
    }
  }
}
```

- Delete a Tutorial:
GraphQL
```json
mutation {
  deleteTutorial(id: 1)
}
```
Response
```json
{
  "data": {
    "deleteTutorial": true
  }
}
```
</ul>

- Count number of Tutorials:
GraphQL
```json
{
  countTutorials
}
```
Response
```json
{
  "data": {
    "countTutorials": 2
  }
}
```

If you check H2 database, it will have 2 tables: <strong>author</strong> and <strong>tutorial</strong>.
The content inside these tables looks like:

![spring-boot-graphql-example-table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01p3oyiruekyrfw4eno8.png)

<h2>Step by step to build Spring Boot GraphQL example</h2>
<h3>Technology</h3>
Our Spring Boot application will use:
<ul>
	<li>Java 17 / 11 / 8</li>
	<li>Spring Boot 3 / 2 (with Spring Web, Spring Data JPA)</li>
	<li>graphql-spring-boot-starter 15.0.0</li>
	<li>graphql-java-tools 5.2.4</li>
	<li>Maven 3.6.1</li>
	<li>H2 database</li>
</ul>
<h3>Project Structure</h3>
This is folders & files structure for our Spring Boot + GraphQL application:

![spring-boot-graphql-example-project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/81l0z2exiouyscs8u9uv.png)

- <strong>resources/graphql</strong> contains <code>.graphqls</code> files that define GraphQL chemas.
- <strong>model</strong> holds two Entities: <code>Author</code> and <code>Tutorial</code>.
- <strong>repository</strong> contains Repository interfaces to interact with H2 database.
- <strong>resolver</strong> resolves values for query & mutation requests by implementing some <code>Resolver</code> interfaces from GraphQL Java Tools.
- <strong>application.properties</strong> configures Spring Datasource, Spring Data JPA and GraphQL base Url.
- <strong>pom.xml</strong> includes dependencies for the whole project.

<h3>Set up the project</h3>
Create new Spring Boot project using <a href="https://spring.io/tools3/sts/">Spring Tool Suite</a> or going to <a href="https://start.spring.io/">https://start.spring.io/</a>.

Then add these dependencies to <strong>pom.xml</strong> file:
```html
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
	<groupId>com.graphql-java-kickstart</groupId>
	<artifactId>graphql-spring-boot-starter</artifactId>
	<version>15.0.0</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>graphql-java-extended-scalars</artifactId>
	<version>21.0</version>
</dependency>

<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<scope>runtime</scope>
</dependency>
```

<h3>Configure Spring Datasource, JPA, GraphQL</h3>
Open <strong>application.properties</strong> and add the following configuration.
```java
spring.h2.console.enabled=true
# default path: h2-console
spring.h2.console.path=/h2-ui
 
spring.datasource.url=jdbc:h2:file:./testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
 
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto= update

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

<ul>
	<li><code>spring.datasource.url</code>: <code>jdbc:h2:mem:[database-name]</code> for In-memory database and <code>jdbc:h2:file:[path/database-name]</code> for disk-based database.</li>
	<li>We configure <code>H2Dialect</code> for H2 Database</li>
	<li><code>spring.h2.console.enabled=true</code> tells the Spring to start H2 Database administration tool and you can access this tool on the browser: <code>http://localhost:8080/h2-console</code>.</li>
	<li><code>spring.h2.console.path=/h2-ui</code> is for H2 console's url, so the default url <code>http://localhost:8080/h2-console</code> will change to <code>http://localhost:8080/h2-ui</code>.</li>
</ul>

By default, Spring Boot GraphQL starter exposes the GraphQL Service on <code>/graphql</code> endpoint for HTTP POST requests containing the GraphQL payload. In the code above, we config the endpoint with new base url: <code>/apis/graphql</code>.

<h3>Extend Scalars for graphql-java Long type</h3>
The implementation of GraphQL Java doesn't provide a Long scalar type by default. So we need to use the <code>graphql-java-extended-scalars</code> package that provides a <code>GraphQLLong</code> scalar type definition which represents <code>java.lang.Long</code>.

Open <em>SpringBootGraphqlApplication.java</em> and add a Bean named <code>extendedScalarLong()</code>:

```java
@SpringBootApplication
public class SpringBootGraphqlApplication {
  // ...

  @Bean
  public graphql.schema.GraphQLScalarType extendedScalarLong() {
    return graphql.scalars.ExtendedScalars.GraphQLLong;
  }
}
```

<h3>Create GraphQL Schema</h3>
We're gonna split up your schema into two <code>.graphqls</code> files. The Spring Boot GraphQL starter will automatically find these schema files.

Under <strong>src/main/resources</strong> folder, create <em>author.graphqls</em> and <em>tutorial.graphqls</em> files.

<em>author.graphqls</em>
```json
scalar Long

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 <code>Query</code> and one root <code>Mutation</code> 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 <code>Query</code> and <code>Mutation</code> types.

<em>tutorial.graphqls</em>
```json
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 <code>null</code> value in the response.

<h3>Define Data Models</h3>
Now we define twom main models with One-to-Many Relationship in <strong>model</strong> package:
<ul>
	<li>Author: id, name, age</li>
	<li>Tutorial: id, title, description, author_id</li>
</ul>
<em>Author.java</em>
```java
package com.bezkoder.spring.graphql.model;

// import javax.persistence.*;
import jakarta.persistence.*; // for Spring Boot 3

@Entity
public class Author {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  @Column(name = "name", nullable = false)
  private String name;

  @Column(name = "age")
  private Integer age;

  public Author() {
  }

  public Author(Long id) {
    this.id = id;
  }

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

  public Long 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 + "]";
  }

}
```

<em>Tutorial.java</em>
```java
package com.bezkoder.spring.graphql.model;

// import javax.persistence.*;
import jakarta.persistence.*; // for Spring Boot 3

@Entity
public class Tutorial {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  @Column(name = "title", nullable = false)
  private String title;

  @Column(name = "description")
  private String description;

  @ManyToOne
  @JoinColumn(name = "author_id", nullable = false, updatable = false)
  private Author author;

  public Tutorial() {
  }

  public Tutorial(String title, String description, Author author) {
    this.title = title;
    this.description = description;
    this.author = author;
  }

  public Long 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 Author getAuthor() {
    return author;
  }

  public void setAuthor(Author author) {
    this.author = author;
  }

  @Override
  public String toString() {
    return "Tutorial [id=" + id + ", title=" + title + ", description=" + description + ", author=" + author + "]";
  }

}
```

<h3>Create Repositories</h3>
In <strong>repository</strong> package, create two interfaces that implement <code>JpaRepository</code>.

<em>AuthorRepository.java</em>
```java
package com.bezkoder.spring.graphql.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.bezkoder.spring.graphql.model.Author;

public interface AuthorRepository extends JpaRepository<Author, Long> {

}
```

<em>TutorialRepository.java</em>
```java
package com.bezkoder.spring.graphql.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.bezkoder.spring.graphql.model.Tutorial;

public interface TutorialRepository extends JpaRepository<Tutorial, Long> {

}
```
Once we extends the <code>JpaRepository</code>, Spring Data JPA will automatically generate implementation with find, save, delete, count methods for the entities.

<h3>Implement GraphQL Root Query Resolver</h3>
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.
```bash
# author.graphqls
type Query {
	findAllAuthors: [Author]!
	countAuthors: Long!
}

# tutorial.graphqls
extend type Query {
	findAllTutorials: [Tutorial]!
	countTutorials: Long!
}
```
This <code>Query</code> class implements <code>GraphQLQueryResolver</code>. We don't want to use <code>GraphQLLong</code> for <code>countAuthors</code> and <code>countTutorials</code> return type, so we alias it by using <code>newAliasedScalar("Long")</code>.

<em>resolver/Query.java</em>
```java
package com.bezkoder.spring.graphql.resolver;

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

import com.bezkoder.spring.graphql.model.Author;
import com.bezkoder.spring.graphql.model.Tutorial;
import com.bezkoder.spring.graphql.repository.AuthorRepository;
import com.bezkoder.spring.graphql.repository.TutorialRepository;
import graphql.kickstart.tools.GraphQLQueryResolver;
import graphql.scalars.ExtendedScalars;
import graphql.schema.GraphQLScalarType;

@Component
public class Query implements GraphQLQueryResolver {
  private AuthorRepository authorRepository;
  private TutorialRepository tutorialRepository;

  GraphQLScalarType longScalar = ExtendedScalars.newAliasedScalar("Long")
          .aliasedScalar(ExtendedScalars.GraphQLLong)
          .build();

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

}
```

<h3>Implement GraphQL Root Mutation Resolver</h3>
Mutation class will implement <code>GraphQLMutationResolver</code>.
Just like Query Resolver, every field in the schema mutation query should have a method in the Mutation Resolver class with the same name.

<em>resolver/Mutation.java</em>
```java
package com.bezkoder.spring.graphql.resolver;

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

import com.bezkoder.spring.graphql.model.Author;
import com.bezkoder.spring.graphql.model.Tutorial;
import com.bezkoder.spring.graphql.repository.AuthorRepository;
import com.bezkoder.spring.graphql.repository.TutorialRepository;
import graphql.kickstart.tools.GraphQLMutationResolver;
import jakarta.persistence.EntityNotFoundException;

@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, Long authorId) {
    Tutorial tutorial = new Tutorial();
    tutorial.setAuthor(new Author(authorId));
    tutorial.setTitle(title);
    tutorial.setDescription(description);

    tutorialRepository.save(tutorial);

    return tutorial;
  }

  public boolean deleteTutorial(Long id) {
    tutorialRepository.deleteById(id);
    return true;
  }

  public Tutorial updateTutorial(Long id, String title, String description) throws EntityNotFoundException {
    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 EntityNotFoundException("Not found Tutorial to update!");
  }

}
```
<h3>Implement GraphQL Field Resolver</h3>
For complex fields like <code>author</code> in <code>Tutorial</code>, we have to resolve the value of those fields.
<code>TutorialResolver</code> implements <code>GraphQLResolver</code> interface and has <code>getAuthor()</code> method.

<em>resolver/TutorialResolver.java</em>
```java
package com.bezkoder.spring.graphql.resolver;

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

import com.bezkoder.spring.graphql.model.Author;
import com.bezkoder.spring.graphql.model.Tutorial;
import com.bezkoder.spring.graphql.repository.AuthorRepository;
import graphql.kickstart.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.getAuthor().getId()).orElseThrow(null);
  }
}
```
If the client want to get a <code>Tutorial</code> without <code>author</code> field, the GraphQL Server will never do the work to retrieve it. So the <code>getAuthor()</code> method above will never be executed.

<h2>Run &#038; Check result</h2>
Run Spring Boot application with command: <code>mvn spring-boot:run</code>

Tables will be automatically generated in Database.
If you check H2 database (by open browser with url: <code>http://localhost:8080/h2-ui/</code>):

![spring-boot-graphql-example-database](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/974u9zcnchjywm821i9z.png)

Click on Connect button, and you can see them.

![spring-boot-graphql-example-h2-database](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wdu1wb5ea3160y3n6gsy.png)

For checking result, you can use <strong>Postman</strong> to make HTTP POST request to <code>http://localhost:8080/apis/graphql</code>.

![spring-boot-graphql-example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/38tpfgg1t7th70d1ifrd.png)

<h2><span id="Conclusion">Conclusion</span></h2>
As an alternative approach to REST APIs, GraphQL minimize complexity between client & server.

Today we've learned how to use Spring Boot GraphQL Starter and the GraphQL Java Tools to build GraphQL Apis. Our Spring Boot project also can communicate with H2 database using Spring Data JPA.

Happy learning! See you again.
<h2><span id="Further_Reading">Further Reading</span></h2>
<ul>
	<li><a href="https://graphql.org/learn/">GraphQL documentation</a></li>
	<li><a href="https://www.graphql-java.com/tutorials/getting-started-with-spring-boot/">https://www.graphql-java.com/tutorials/getting-started-with-spring-boot/</a></li>
	<li><a href="https://github.com/graphql-java/graphql-java">graphql-java Github</a></li>
</ul>

<h2>Source Code</h2>
You can find the complete source code for this tutorial on <a href="https://github.com/bezkoder/spring-boot-graphql-example">Github</a>.

Other databases:
- <a href="https://www.bezkoder.com/spring-boot-graphql-mysql-jpa/">Spring Boot + GraphQL + MySQL example</a>
- <a href="https://www.bezkoder.com/spring-boot-graphql-postgresql/">Spring Boot + GraphQL + PostgreSQL example</a>
- <a href="https://www.bezkoder.com/spring-boot-graphql-mongodb-example-graphql-java/">Spring Boot + GraphQL + MongoDB example</a>

Security: <a href="https://www.bezkoder.com/spring-boot-security-login-jwt/">Spring Boot Security: Login and Registration example with JWT</a>
Unit Test: <a href="https://www.bezkoder.com/spring-boot-unit-test-jpa-repo-datajpatest/">@DataJpaTest example for Spring Data Repository</a>
Documentation: <a href="https://www.bezkoder.com/spring-boot-swagger-3/">Spring Boot + Swagger 3 example (with OpenAPI 3)</a>
Caching: <a href="https://www.bezkoder.com/spring-boot-redis-cache-example/">Spring Boot Redis Cache example</a>