type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: String!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
createdAt: String!
}
type Comment {
id: ID!
content: String!
author: User!
post: Post!
createdAt: String!
}
type Query {
user(id: ID!): User
users(page: Int = 0, size: Int = 10): [User!]!
post(id: ID!): Post
posts(userId: ID): [Post!]!
}
type Mutation {
createUser(name: String!, email: String!, password: String!): User!
updateUser(id: ID!, name: String, email: String): User!
deleteUser(id: ID!): Boolean!
createPost(userId: ID!, title: String!, content: String!): Post!
}
type Subscription {
postCreated: Post!
}
package com.example.demo.controller;
import com.example.demo.model.User;
import com.example.demo.model.Post;
import com.example.demo.service.UserService;
import com.example.demo.service.PostService;
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.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
import java.util.List;
@Controller
public class UserGraphQLController {
private final UserService userService;
private final PostService postService;
public UserGraphQLController(UserService userService, PostService postService) {
this.userService = userService;
this.postService = postService;
}
@QueryMapping
public User user(@Argument Long id) {
return userService.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
@QueryMapping
public List<User> users(@Argument int page, @Argument int size) {
return userService.findAll(page, size);
}
@MutationMapping
public User createUser(@Argument String name,
@Argument String email,
@Argument String password) {
User user = new User();
user.setName(name);
user.setEmail(email);
user.setPassword(password);
return userService.save(user);
}
@MutationMapping
public User updateUser(@Argument Long id,
@Argument String name,
@Argument String email) {
return userService.update(id, name, email);
}
@MutationMapping
public Boolean deleteUser(@Argument Long id) {
userService.deleteById(id);
return true;
}
// Field resolver - fetches posts for a user
@SchemaMapping(typeName = "User", field = "posts")
public List<Post> posts(User user) {
return postService.findByUserId(user.getId());
}
}
package com.example.demo.config;
import graphql.scalars.ExtendedScalars;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
@Configuration
public class GraphQLConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
return wiringBuilder -> wiringBuilder
.scalar(ExtendedScalars.DateTime)
.scalar(ExtendedScalars.Json);
}
}
GraphQL provides flexible APIs where clients specify exact data requirements. Spring for GraphQL integrates GraphQL Java with Spring Boot. Schema-first approach defines types in .graphqls files. Resolvers map schema fields to Java methods using @QueryMapping, @MutationMapping. DataLoader prevents N+1 queries through batching. GraphQL enables fetching related data in single request, reducing over-fetching and under-fetching. Subscriptions support real-time updates via WebSocket. Error handling uses DataFetcherExceptionHandler. Pagination uses cursor-based approach. GraphQL excels for complex data requirements, mobile apps, and evolving APIs. The self-documenting schema simplifies frontend development. Spring's annotation-based approach makes GraphQL accessible to Java developers familiar with REST.