At its core, GraphQL enables clients to request specific data from the server using a single API endpoint. Unlike traditional REST APIs that expose fixed endpoints with pre-defined data structures, GraphQL allows clients to specify exactly what data they need and receive it in a hierarchical structure. This reduces over-fetching and under-fetching of data, optimizing network usage and improving performance.
Key features of GraphQL include:
Strong typing: GraphQL uses a type system to define the shape of the data available in the API. This provides a clear contract between the server and clients, enabling better tooling, documentation, and validation.
Hierarchical queries: Clients can request data in a hierarchical structure, specifying nested fields and relationships. This allows clients to fetch related data in a single query, reducing the number of round trips to the server.
Efficient data fetching: GraphQL retrieves data from multiple resources in a single request, avoiding the need for multiple API calls. Clients have fine-grained control over the shape and size of the data they receive.
Mutations: GraphQL supports mutations for modifying data on the server. Clients can send requests to create, update, or delete data, using a similar syntax to queries.
Real-time updates: GraphQL supports subscriptions, allowing clients to receive real-time updates when data changes on the server. This is particularly useful for applications that require real-time features like chat or notifications.
GraphQL is language-agnostic, meaning it can be used with any programming language or framework. There are libraries and tools available for various programming languages to implement GraphQL servers and clients.
Overall, GraphQL provides a flexible and efficient approach to building APIs, empowering clients to request and receive precisely the data they need while reducing network overhead.
HSQLDB (HyperSQL Database) is an open-source, in-memory relational database management system (RDBMS). It is written in Java and provides a lightweight and fast database solution for applications. HSQLDB is often used in Java-based projects, including web applications and testing environments.
Technology
- Spring Boot 3.0.6
- HyperSQL
- Maven
- IntelliJ IDEA
Configuration Spring Boot project :
<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>3.1.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.henry</groupId>
<artifactId>spring-for-graphql</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-for-graphql</name>
<description>Demo Spring for GraphQL</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</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-data-jpa</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.graphql-java/graphql-java-tools -->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>5.2.4</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-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>
application.yml:
spring:
datasource:
url: jdbc:hsqldb:mem:testdb
username: sa
password:
driver-class-name: org.hsqldb.jdbc.JDBCDriver
# Hibernate Configuration
jpa:
database-platform: org.hibernate.dialect.HSQLDialect
hibernate.ddl-auto: create-drop
# graphql Configuration
graphql:
graphiql:
enabled: true
logging:
level:
org:
hibernate:
SQL: DEBUG
Schema
getAllBooks: [Book]
getAllAuthors: [Author]
getBookById(id: ID!): Book
getAuthorById(id: ID!): Author
}
type Mutation {
createAuthor(firstName: String!, lastName: String! ): Author
updateAuthor(id: ID!, firstName: String!, lastName: String!): Author
deleteAuthor(id: ID!): Boolean
createBook(title: String!, authorId: ID!): Book
updateBook(id: ID!, title: String!): Book
deleteBook(id: ID!): Boolean
}
type Book {
id: ID
title: String!
author: Author!
}
type Author {
id: ID
firstName: String!
lastName: String!
bookRecords: [Book]
}
Spring Data GraphQL Application
Model
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Set;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table(name = "author")
public class Author implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
private Set<Book> bookRecords;
}
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table(name = "book")
public class Book implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id", nullable = false)
private Author author;
}
Repository
import com.henry.model.Author;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AuthorRepository extends JpaRepository<Author, Long> {
}
import com.henry.model.Book;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book,Long> {
}
Resolver
import com.coxautodev.graphql.tools.GraphQLMutationResolver;
import com.coxautodev.graphql.tools.GraphQLQueryResolver;
import com.henry.model.Author;
import com.henry.model.Book;
import com.henry.repository.AuthorRepository;
import com.henry.repository.BookRepository;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class QueryAndMutationResolver implements GraphQLQueryResolver, GraphQLMutationResolver {
private final BookRepository bookRepository;
private final AuthorRepository authorRepository;
public QueryAndMutationResolver(BookRepository bookRepository, AuthorRepository authorRepository) {
this.bookRepository = bookRepository;
this.authorRepository = authorRepository;
}
public List<Book> getAllBooks() {
return bookRepository.findAll();
}
public Book getBookById(Long id) {
return bookRepository.findById(id).orElse(null);
}
public Iterable<Author> getAllAuthors() {
return authorRepository.findAll();
}
public Author getAuthorById(Long id) {
return authorRepository.findById(id).orElseGet(null);
}
public Author createAuthor(String firstName, String lastName ) {
Author author = new Author();
author.setFirstName(firstName);
author.setLastName(lastName);
return authorRepository.saveAndFlush(author);
}
public Author updateAuthor(Long id, String firstName, String lastName) {
Author author = authorRepository.findById(id).orElse(null);
if (author != null) {
author.setFirstName(firstName);
author.setLastName(lastName);
return authorRepository.saveAndFlush(author);
}
return null;
}
public Boolean deleteAuthor(Long id) {
authorRepository.deleteById(id);
return true;
}
public Book createBook(String title, Long author_id) {
Author author = authorRepository.findById(author_id).
orElseGet(null);
Book book = new Book();
book.setTitle(title);
book.setAuthor(author);
return bookRepository.saveAndFlush(book);
}
public Book updateBook(Long id, String title) {
Book book = bookRepository.findById(id).orElse(null);
if (book != null) {
book.setTitle(title);
return bookRepository.saveAndFlush(book);
}
return null;
}
public Boolean deleteBook(Long id) {
bookRepository.deleteById(id);
return true;
}
}
Controller
import com.henry.model.Author;
import com.henry.resolver.QueryAndMutationResolver;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
@Controller
public class AuthorController {
private final QueryAndMutationResolver queryAndMutationResolver;
public AuthorController(QueryAndMutationResolver queryAndMutationResolver) {
this.queryAndMutationResolver = queryAndMutationResolver;
}
@QueryMapping
public Author getAuthorById(@Argument Long id) {
return queryAndMutationResolver.getAuthorById(id);
}
@QueryMapping
public Iterable<Author> getAllAuthors(){
return queryAndMutationResolver.getAllAuthors();
}
@SchemaMapping(typeName = "Mutation", field = "createAuthor")
public Author createAuthor(@Argument String firstName, @Argument String lastName){
return queryAndMutationResolver.createAuthor(firstName, lastName);
}
@SchemaMapping(typeName = "Mutation", field = "updateAuthor")
public Author updateAuthor(@Argument Long id, @Argument String firstName, @Argument String lastName) {
return queryAndMutationResolver.updateAuthor(id,firstName, lastName);
}
@SchemaMapping(typeName = "Mutation", field = "deleteAuthor")
public Boolean deleteAuthor(@Argument Long id) {
return queryAndMutationResolver.deleteAuthor(id);
}
}
import com.henry.model.Book;
import com.henry.resolver.QueryAndMutationResolver;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
import java.util.List;
@Controller
public class BookController {
private final QueryAndMutationResolver queryAndMutationResolver;
public BookController(QueryAndMutationResolver queryAndMutationResolver) {
this.queryAndMutationResolver = queryAndMutationResolver;
}
@QueryMapping
public Book getBookById(@Argument Long id) {
return queryAndMutationResolver.getBookById(id);
}
@QueryMapping
public List<Book> getAllBooks(){
return queryAndMutationResolver.getAllBooks();
}
@SchemaMapping(typeName = "Mutation", field = "createBook")
public Book createBook(@Argument String title, @Argument Long authorId){
return queryAndMutationResolver.createBook(title, authorId);
}
@SchemaMapping(typeName = "Mutation", field = "updateBook")
public Book updateBook(@Argument Long id, @Argument String title) {
return queryAndMutationResolver.updateBook(id,title);
}
@SchemaMapping(typeName = "Mutation", field = "deleteBook")
public Boolean deleteBook(@Argument Long id) {
return queryAndMutationResolver.deleteBook(id);
}
}