GraphQl with Spring Boot 3

GraphQL is a query language for APIs that allows clients to request exactly the data they need and receive predictable results.

Sopheaktra

Eang Sopheaktra

June 08 2024 04:58 pm

0 209

What's GraphQl and some key points of it?

GraphQL is a query language for APIs that allows clients to request exactly the data they need and receive predictable results. Here are some key points about GraphQL:

  • Data Description: GraphQL provides a complete and understandable description of the data in your API. It defines types, fields, and relationships between them.
  • Precise Queries: Clients can ask for specific data by sending GraphQL queries. Unlike traditional REST APIs, where endpoints return fixed data structures, GraphQL queries return only the requested fields.
  • Single Request: GraphQL queries can access multiple resources in a single request. This efficiency is especially useful for mobile apps on slow networks.
  • Type System: GraphQL APIs are organized around types and fields. Types ensure that clients ask for valid data and receive clear error messages.
  • Developer Tools: GraphQL makes it easy to build tools like GraphiQL, which helps developers explore and test APIs.

Advantages and Disadvantages

  • Advantages:
    • Efficient Data Fetching: Clients can specify exactly the data they need, avoiding over-fetching and under-fetching common in RESTful APIs.
    • Declarative Data Queries: GraphQL uses a clear, declarative query language, making it easy to express complex data requirements.
    • Strongly Typed Schema: The schema acts as a blueprint, ensuring smooth communication between different parts of the application1.
  • Disadvantages:
    • Complex Syntax: GraphQL can be challenging due to its complex syntax.
    • Potential Crashes: A small mistake in the schema can lead to excessive database queries and server crashes.
    • Security Concerns: Hackers can exploit queries to obtain sensitive data from the server2.

Why we need use graphql?

GraphQL offers several benefits that make it a compelling choice for modern applications:

  • Efficient Data Fetching:
    • GraphQL allows clients to request exactly the data they need, reducing over-fetching and under-fetching compared to REST APIs.
    • With a single query, you can retrieve nested data from multiple sources, improving performance and reducing network requests1
  • Declarative Model:
    • GraphQL’s declarative approach simplifies API development.
    • You describe the shape of the data you want, and the server handles combining and filtering backend data to return precisely what you requested1.
  • Strong Typing:
    • GraphQL enforces a strongly typed schema, ensuring type safety and consistency during development.
    • This helps prevent runtime errors and makes your API more robust2.
  • Avoids Versioning:
    • Unlike REST APIs, where versioning can lead to complex endpoints, GraphQL evolves without breaking existing clients.
    • You can add new types and fields without affecting existing queries1.
  • Developer Experience:
    • GraphQL simplifies adding new features. You can design, develop, and deploy quickly.
    • Libraries like Apollo Client handle caching, data normalization, and optimistic UI rendering1.

How it works on Spring boot?

Certainly! Spring Boot provides a convenient way to build GraphQL APIs. Here’s how you can set up a GraphQL server using Spring Boot:

  • Dependencies:
    • Add the graphql-spring-boot-starter dependency to your Spring Boot project. This starter includes the necessary components for GraphQL.
    • You can also use libraries like SPQR or graphql-java-kickstart for additional features12.
  • Schema Definition:
    • Define your GraphQL schema. This schema describes the types, queries, and mutations available in your API.
    • Specify the types (e.g., Post, Author) and their fields (e.g., id, title) in your schema1.
  • Controller Mapping:
    • Create a controller class with @Controller annotation.
    • Use @QueryMapping and @MutationMapping to map GraphQL queries and mutations to methods that interact with your data repositories (e.g.,
    • JPA repositories for database access)3.
  • GraphiQL Console:
    • Enable the GraphiQL console in your application properties. GraphiQL is a web-based tool for testing GraphQL queries and exploring your API.
    • Use it to test your GraphQL API with different queries and mutations3.
  • Testing and Optimization:
    • Write unit tests for your GraphQL queries and mutations.
    • Be aware of potential performance issues, such as the N+1 problem when fetching lazy collections of related data (e.g., authors and their posts).
    • Consider using Query DSL to optimize data retrieval34.

Code

Using dependencies such as below:

<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-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-graphql</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>

		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.mapstruct</groupId>
			<artifactId>mapstruct</artifactId>
			<version>${org.mapstruct.version}</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>${org.projectlombok.version}</version>
			<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>

add below config to application.properties for allow graphql

spring.graphql.graphiql.enabled=true

add graphql's schema to package: "/resources/graphql/schema.student" with mutation and query such as below:

  • Mutation for student with name: mutation.students.graphqls
type Mutation{
    createStudent(createStudentDTO : CreateStudentDTO):StudentDTO
    updateStudent(studentId:Float!, updateStudentDTO : UpdateStudentDTO):StudentDTO
    deleteStudent(studentId:Float!):String
}

input CreateStudentDTO{
    firstName: String
    lastName: String
    age: Int
    isDeleted: Boolean
    courses: [Float!]
}

input UpdateStudentDTO{
    id: Float
    firstName: String
    lastName: String
    age: Int
    isDeleted: Boolean
    courses: [Float!]
}
  • Query schema with name: query.students.graphqls
type Query {
    allStudents(paginationReq: PaginationReq!) :StudentContent
    getStudents(studentId: Float!) :StudentDTO
}

type StudentContent {
    pagination: Pagination
    content:[StudentDTO]
}
input PaginationReq {
    page: Int
    size: Int
}
type StudentDTO {
    id:Float
    firstName:String
    lastName:String
    age:Int
    isDeleted:Boolean
    courses:[CourseDTO]
}
type Pagination {
    page: Int
    size: Int
    totalPage: Float
    totalElement: Float
}

type CourseDTO {
    id:Float
    name:String
}

Create controller to handle request from user with graphql such as annotation: 

  • @QueryMapping: using for bind this method to a query, i.e. a field under the Query type.
  • @MutationMapping: to associate the method with a mutation field in your GraphQL schema. 
  • @Argument: for access to a named field argument bound to a higher-level, typed Object.
package com.tra21.graphqltesting.controllers;

import com.tra21.graphqltesting.payloads.dtos.students.CreateStudentDTO;
import com.tra21.graphqltesting.payloads.dtos.students.StudentDTO;
import com.tra21.graphqltesting.payloads.dtos.students.UpdateStudentDTO;
import com.tra21.graphqltesting.payloads.req.PaginationReq;
import com.tra21.graphqltesting.payloads.res.students.StudentContents;
import com.tra21.graphqltesting.services.students.IStudentService;
import lombok.RequiredArgsConstructor;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;

@Controller
@RequiredArgsConstructor
public class StudentController {
    private final IStudentService studentService;
    @QueryMapping
    public StudentDTO getStudents(@Argument Long studentId ) {
        return studentService.getStudents(studentId);
    }
    @QueryMapping
    public StudentContents allStudents(@Argument PaginationReq paginationReq) {
        return studentService.allStudents(paginationReq);
    }
    @MutationMapping
    public StudentDTO createStudent(@Argument CreateStudentDTO createStudentDTO){
        return studentService.createStudent(createStudentDTO);
    }
    @MutationMapping
    public StudentDTO updateStudent(@Argument Long studentId, @Argument UpdateStudentDTO updateStudentDTO){
        return studentService.updateStudent(studentId, updateStudentDTO);
    }
    @MutationMapping
    public String deleteStudent(@Argument Long studentId){
        return studentService.deleteStudent(studentId) ? "Success deleted student." : "Failed to delete student.";
    }
}

Create CustomErrorResolver for handle error

package com.tra21.graphqltesting.exception;

import graphql.ErrorClassification;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import graphql.schema.DataFetchingEnvironment;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import java.util.stream.Collectors;
@Component
public class CustomErrorResolver extends DataFetcherExceptionResolverAdapter {
    @Override
    protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
        Throwable t = NestedExceptionUtils.getMostSpecificCause(ex);

        if(t instanceof ConstraintViolationException constraintViolationException){
            return validationError(constraintViolationException, env);
        }else if (t instanceof NotFoundException exception) {
            return GraphqlErrorBuilder.newError(env)
                    .errorType(ErrorClassification.errorClassification(HttpStatus.BAD_REQUEST.name()))
                    .message(exception.getMessage())
                    .build();
        }else if (t instanceof RuntimeException exception) {
            return GraphqlErrorBuilder.newError(env)
                    .errorType(ErrorClassification.errorClassification(HttpStatus.INTERNAL_SERVER_ERROR.name()))
                    .message("Unexpected Error!")
                    .build();
        }

        // other exceptions not yet caught
        return GraphqlErrorBuilder.newError(env)
                .message("Error occurred: Ensure request is valid ")
                .errorType(ErrorClassification.errorClassification(HttpStatus.INTERNAL_SERVER_ERROR.name()))
                .build();
    }

    private GraphQLError validationError(ConstraintViolationException exception, DataFetchingEnvironment env){
        String invalidFields = exception.getConstraintViolations().stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining("\n"));

        return GraphqlErrorBuilder.newError(env)
                .errorType(ErrorClassification.errorClassification(HttpStatus.INTERNAL_SERVER_ERROR.name()))
                .message(invalidFields)
                .build();
    }
}

Summary

Download the source code for the sample application with graphql. After this tutorial you will learn such as:

  • Spring Framework
  • SpringBoot
  • Spring Data Jpa
  • H2 database
  • Graphql
  • Lombok
  • Mapstruct

Comments

Subscribe to our newsletter.

Get updates about new articles, contents, coding tips and tricks.

Weekly articles
Always new release articles every week or weekly released.
No spam
No marketing, share and spam you different notification.
© 2023-2025 Tra21, Inc. All rights reserved.