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 that will expose CRUD APIs to create, read, update and delete objects in MySQL database with the help of graphql-spring-boot-starter and Spring Data JPA.
Related Post:
– With Embedded Database: Spring Boot + GraphQL example
– Spring Boot, Spring Data JPA – Rest CRUD API example
– Spring Boot Thymeleaf CRUD example
– Documentation: Spring Boot Swagger 3 example
– Unit Test: @DataJpaTest example for Spring Data Repository
– Caching: Spring Boot Redis Cache example
Deployment:
– Deploy Spring Boot App on AWS – Elastic Beanstalk
– Docker Compose: Spring Boot and MySQL example
Contents
Spring Boot CRUD GraphQL APIs with MySQL overview
Goal
We have two data models: Author and Tutorial.
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 MySQL database using only one endpoint: /apis/graphql
.
The Spring Boot GraphQL Starter will make GraphQL server running in a short time.
CRUD GraphQL APIs
– Create an Author:
- GraphQL
mutation {
createAuthor(
name: "bezkoder",
age: 27) {
id name
}
}
{
"data": {
"createAuthor": {
"id": "1",
"name": "bezkoder"
}
}
}
– Create a Tutorial:
- GraphQL: we want response including Tutorial id, title and author (name)
mutation {
createTutorial (
title: "Tutorial #1",
description: "Description for Tut#1"
author: 1)
{
id title author { name }
}
}
{
"data": {
"createTutorial": {
"id": "1",
"title": "Tutorial #1",
"author": {
"name": "bezkoder"
}
}
}
}
– Read all Authors:
- GraphQL
{
findAllAuthors{
id
name
age
}
}
{
"data": {
"findAllAuthors": [
{
"id": "1",
"name": "bezkoder",
"age": 27
},
{
"id": "2",
"name": "zKoder",
"age": 30
}
]
}
}
– Read all Tutorials:
- GraphQL
{
findAllTutorials{
id
title
description
author{
id
name
}
}
}
{
"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"
}
}
]
}
}
– Update a Tutorial:
- GraphQL
mutation {
updateTutorial (
id: 2
description: "updated Desc Tut#2")
{
id title description author { name }
}
}
{
"data": {
"updateTutorial": {
"id": "2",
"title": "Tutorial #2",
"description": "updated Desc Tut#2",
"author": {
"name": "bezkoder"
}
}
}
}
– Delete a Tutorial:
- GraphQL
mutation {
deleteTutorial(id: 1)
}
{
"data": {
"deleteTutorial": true
}
}
– Count number of Tutorials:
- GraphQL
{
countTutorials
}
{
"data": {
"countTutorials": 2
}
}
If you check MySQL database, it will have 2 tables: author & tutorial.
The content inside these tables looks like:
mysql> select * from author;
+----+------+----------+
| id | age | name |
+----+------+----------+
| 1 | 27 | bezkoder |
| 2 | 30 | zKoder |
+----+------+----------+
mysql> select * from tutorial;
+----+-----------------------+-------------+-----------+
| id | description | title | author_id |
+----+-----------------------+-------------+-----------+
| 2 | updated Desc Tut#2 | Tutorial #2 | 1 |
| 3 | Description for Tut#3 | Tutorial #3 | 2 |
+----+-----------------------+-------------+-----------+
Build Spring Boot GraphQL APIs with MySQL Database
Technology
Our Spring Boot application will use:
- Java 17 / 11 / 8
- Spring Boot 3 / 2 (with Spring Web, Spring Data JPA)
- graphql-spring-boot-starter 15.0.0
- graphql-java-tools 5.2.4
- Maven 3.6.1
- MySQL
Project Structure
This is folders & files structure for our Spring Boot + GraphQL + MySQL application:
– resources/graphql contains .graphqls
files that define GraphQL chemas.
– model holds two Entities: Author
and Tutorial
.
– repository contains Repository interfaces to interact with MySQL database.
– resolver resolves values for query & mutation requests by implementing some Resolver
interfaces from GraphQL Java Tools.
– application.properties configures Spring Datasource, Spring Data JPA 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-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.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
Configure Spring Datasource, JPA, GraphQL
Open application.properties and add the following configuration.
spring.datasource.url= jdbc:mysql://localhost:3306/testdb?useSSL=false
spring.datasource.username= root
spring.datasource.password= 123456
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto= update
# 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
.
Extend Scalars for graphql-java Long type
The implementation of GraphQL Java doesn’t provide a Long scalar type by default. So we need to use the graphql-java-extended-scalars
package that provides a GraphQLLong
scalar type definition which represents java.lang.Long
.
Open SpringBootGraphqlMysqlApplication.java and add a Bean named extendedScalarLong()
:
@SpringBootApplication
public class SpringBootGraphqlMysqlApplication {
// ...
@Bean
public graphql.schema.GraphQLScalarType extendedScalarLong() {
return graphql.scalars.ExtendedScalars.GraphQLLong;
}
}
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
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 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.mysql.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 + "]";
}
}
Tutorial.java
package com.bezkoder.springgraphql.mysql.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 + "]";
}
}
Create Repositories
In repository package, create two interfaces that implement JpaRepository
.
AuthorRepository.java
package com.bezkoder.springgraphql.mysql.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.bezkoder.springgraphql.mysql.model.Author;
public interface AuthorRepository extends JpaRepository<Author, Long> {
}
TutorialRepository.java
package com.bezkoder.springgraphql.mysql.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.bezkoder.springgraphql.mysql.model.Tutorial;
public interface TutorialRepository extends JpaRepository<Tutorial, Long> {
}
Once we extends the JpaRepository
, Spring Data JPA will automatically generate implementation with find, save, delete, count methods for the entities.
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
. We don’t want to use GraphQLLong
for countAuthors
and countTutorials
return type, so we alias it by using newAliasedScalar("Long")
.
resolver/Query.java
package com.bezkoder.springgraphql.mysql.resolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.bezkoder.springgraphql.mysql.model.Author;
import com.bezkoder.springgraphql.mysql.model.Tutorial;
import com.bezkoder.springgraphql.mysql.repository.AuthorRepository;
import com.bezkoder.springgraphql.mysql.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();
}
}
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.mysql.resolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.bezkoder.springgraphql.mysql.model.Author;
import com.bezkoder.springgraphql.mysql.model.Tutorial;
import com.bezkoder.springgraphql.mysql.repository.AuthorRepository;
import com.bezkoder.springgraphql.mysql.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!");
}
}
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.mysql.resolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.bezkoder.springgraphql.mysql.model.Author;
import com.bezkoder.springgraphql.mysql.model.Tutorial;
import com.bezkoder.springgraphql.mysql.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 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
Tables will be automatically generated in Database.
If you check MySQL database, you can see them:
mysql> describe author;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id | bigint(20) | NO | PRI | NULL | |
| age | int(11) | YES | | NULL | |
| name | varchar(255) | NO | | NULL | |
+-------+--------------+------+-----+---------+-------+
mysql> describe tutorial;
+-------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+-------+
| id | bigint(20) | NO | PRI | NULL | |
| description | varchar(255) | YES | | NULL | |
| title | varchar(255) | NO | | NULL | |
| author_id | bigint(20) | NO | MUL | NULL | |
+-------------+--------------+------+-----+---------+-------+
For checking result, you can use Postman to make HTTP POST request to http://localhost:8080/apis/graphql
.
Conclusion
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 MySQL database using Spring Data JPA.
Happy learning! See you again.
Further Reading
- GraphQL documentation
- https://www.graphql-java.com/tutorials/getting-started-with-spring-boot/
- graphql-java Github
Deployment:
– Deploy Spring Boot App on AWS – Elastic Beanstalk
– Docker Compose: Spring Boot and MySQL example
Documentation: Spring Boot + Swagger 3 example (with OpenAPI 3)
Source Code
You can find the complete source code for this tutorial on Github.
– With Embedded Database: Spring Boot + GraphQL example
– Unit Test: @DataJpaTest example for Spring Data Repository
– Documentation: Spring Boot + Swagger 3 example (with OpenAPI 3)
– Caching: Spring Boot Redis Cache example
umm good!
Can you add JWT security?
Hello, thank you a lot for this tutorial, I think Im not the only one that appreciate it, I was wondering if maybe you could help me to understand just one part, Im not understanding where or how the /apis/graphql is exposed, hopefully you could answer me 🙂
application.properties file : graphql.servlet.mapping: /apis/graphql
Really good.
My Source code in git here….
https://github.com/Urunov/SpringBoot-GraphQL-FullStack-Projects/tree/master/SpringBoot-GraphQL-FullStack-Projects/Project-2.%20Project%20Backend%20Frontend
Hello,
Thank you for the resource. But I have a question if i want to authenticate that service with http header can i do it or is it possible.
Hi, please visit this post:
Spring Boot Token based Authentication with Spring Security & JWT
I think it can help you 🙂
Hello, good morning.
First of all, thanks for the input.
I just wanted to know if it’s possible for you to share the project with me. This is because I have already done everything step by step and it generates the following error:
Caused by: com.coxautodev.graphql.tools.SchemaClassScannerError: Unable to match type definition (NonNullType{type=ListType{type=TypeName{name=’Author’}}}) with java type (java.lang.Iterable): No TypeDefinition for type name Author
Or in case you could back me up with the mistake.
In case you could share it with me, please send me an email to: [email protected]
Hi, I’ve just share the source code on Github:
https://github.com/bezkoder/spring-boot-graphql-mysql.
You can check it, then if there is anything wrong or I forgot to write in the tutorial, please notice me 🙂
Thank you…
I already checked it with the one I did and it was a typo in the extension of the graphql files here I’m missing the letter “s” at the end.
Thank you very much.
Greetings.
Just want to say thank you for this Spring Boot GraphQL tutorial, it’s easy to understand and implement!