Building APIs with Spring Boot and GraphQL


A Step-by-Step Guide with Practical Implementation
Introduction
GraphQL has transformed API design and consumption by offering a flexible, efficient, and strongly-typed alternative to REST. In this article, I will walk you through creating a Workforce Management System using Spring Boot and GraphQL, covering:
✅ GraphQL Schema Design
✅ Spring Boot Integration
✅ CRUD Operations with Pagination, Sorting, & Filtering
✅ Input Validation & Error Handling
✅ Database Setup with H2
You can explore the complete project on GitHub.
Why GraphQL over REST?
Unlike REST, GraphQL allows clients to request exactly the data they need, reducing over-fetching and under-fetching. Key benefits:
Single Endpoint (
/graphql
) for all queries and mutationsStrong Typing with Schema Definition Language (SDL), making it easy to document and evolve
Real-time Data via Subscriptions (WebSocket)
Project Overview
The Workforce Management System is a simple backend that allows you to:
Manage
Employee
andDepartment
entitiesPerform basic operations like create, read, update, and delete (CRUD)
Query data using sorting, filtering, and pagination.
Use GraphQL queries and mutations for interaction
Tech Stack:
Java-21
Spring Boot 3.4.4
GraphQL Java
H2 (in-memory) database
Maven
Project Setup
1. Dependencies (pom.xml
)
<dependencies>
<!-- Spring Boot Starter GraphQL -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<!-- Spring Data JPA + H2 Database -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Input Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
2. GraphQL Schema (schema.graphqls
)
type Department {
id: ID!
name: String!
createdYear: Int!
employees: [Employee]!
}
type Employee {
id: ID!
name: String!
email: String!
department: Department!
city: String!
}
type Query {
departments(
page: Int = 0,
size: Int = 10,
filter: DepartmentFilter
): DepartmentPage!
}
input DepartmentFilter {
name: String
nameContains: String
createdYear: Int
createdYearGt: Int
createdYearLt: Int
}
type Mutation {
createDepartment(input: DepartmentInput!): Department!
deleteDepartment(id: ID!): Boolean!
}
input DepartmentInput {
name: String!
createdYear: Int!
}
Key Implementations
1. Entity Mapping (JPA)
@Entity
public class Department {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
private String name;
@Min(1900) @Max(2025)
private int createdYear;
@OneToMany(mappedBy = "department")
private List<Employee> employees;
}
2. GraphQL Controller
@Controller
public class DepartmentController {
@Autowired
private DepartmentRepository repo;
@QueryMapping
public Page<Department> departments(
@Argument Integer page,
@Argument Integer size,
@Argument DepartmentFilter filter
) {
Pageable pageable = PageRequest.of(page, size);
Specification<Department> spec = buildSpec(filter);
return repo.findAll(spec, pageable);
}
@MutationMapping
public Department createDepartment(@Valid @Argument DepartmentInput input) {
return repo.save(convertToEntity(input));
}
}
3. Dynamic Filtering with JPA Specifications
public class DepartmentSpecification {
public static Specification<Department> withFilter(DepartmentFilter filter) {
return (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (filter.getName() != null) {
predicates.add(cb.equal(root.get("name"), filter.getName()));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
}
}
Testing the API
Sample Queries
1. Fetch Departments (Paginated)
query {
departments(page: 0, size: 5) {
content {
id
name
employees { name }
}
}
}
2. Create a Department
mutation {
createDepartment(input: {
name: "Engineering",
createdYear: 2023
}) {
id
name
}
}
3. Update Department
mutation {
updateDepartment(
id: "1",
input: {
name: "Marketting",
createdYear: 2024
}
) {
id
name
createdYear
}
}
4. Delete a Department
mutation {
deleteDepartment(id: "2")
}
Why This Architecture?
🔹 Efficiency: Clients fetch only required fields.
🔹 Type Safety: Compile-time schema validation.
🔹 Scalability: Easy to extend with new types.
Conclusion
This project demonstrates how Spring Boot + GraphQL can simplify API development while offering flexibility. Key takeaways:
GraphQL reduces over-fetching compared to REST.
Spring Data JPA integrates seamlessly with GraphQL resolvers.
Input validation ensures data consistency.
Explore the full code on GitHub and try it yourself! If you have any questions or feedback, feel free to share them in the comments below.
Subscribe to my newsletter
Read articles from Deepak Kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
