Working with GraphQL

1. Adding Spring GraphQL Dependencies (spring-boot-starter-graphql)

Example: GraphQL starter 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>

2. Defining GraphQL Schema (schema.graphqls)

Example: GraphQL schema: types, queries, mutations

# src/main/resources/graphql/schema.graphqls
type Query {
  book(id: ID!): Book
  books(page: Int = 0, size: Int = 20): [Book!]!
}
type Mutation {
  addBook(input: BookInput!): Book!
}
type Book { id: ID!, title: String!, author: Author! }
type Author { id: ID!, name: String! }
input BookInput { title: String!, authorId: ID! }

3. Creating Query Resolvers (@QueryMapping)

Example: Query resolvers for book and books

@Controller
public class BookController {
  private final BookService svc;
  public BookController(BookService s) { this.svc = s; }
  @QueryMapping public Book book(@Argument Long id) { return svc.findById(id); }
  @QueryMapping public List<Book> books(@Argument int page, @Argument int size) {
    return svc.findAll(page, size);
  }
}

4. Creating Mutation Resolvers (@MutationMapping)

Example: Mutation resolver: add book

@MutationMapping
public Book addBook(@Argument BookInput input) {
  return svc.create(input);
}

5. Implementing Data Fetchers (DataFetcher)

Example: Field resolver for book author

@SchemaMapping(typeName = "Book", field = "author")
public Author author(Book book) {
  return authorService.findById(book.getAuthorId()); // per-field fetcher
}

6. Handling Arguments (@Argument)

Annotation Use
@Argument Bind GraphQL arg by name
@Argument(name="x") Custom name
@Arguments Bind whole input object map
@ContextValue Read from GraphQLContext

7. Using GraphQL Context (GraphQLContext)

Example: Inject user into GraphQL context

@Component
public class AuthInterceptor implements WebGraphQlInterceptor {
  @Override public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest req, Chain chain) {
    String user = req.getHeaders().getFirst("X-User");
    req.configureExecutionInput((e, b) -> b.graphQLContext(c -> c.put("user", user)).build());
    return chain.next(req);
  }
}
@QueryMapping public Book book(@Argument Long id, @ContextValue String user) { /* … */ }

8. Implementing Pagination (Connection pattern)

Example: Cursor-based pagination schema

type Query { books(first: Int, after: String): BookConnection! }
type BookConnection { edges: [BookEdge!]!, pageInfo: PageInfo! }
type BookEdge { node: Book!, cursor: String! }
type PageInfo { hasNextPage: Boolean!, endCursor: String }

9. Implementing Error Handling (GraphQLError)

Example: GraphQL exception resolver

@Component
public class GqlExceptionResolver extends DataFetcherExceptionResolverAdapter {
  @Override protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
    if (ex instanceof NotFoundException nfe) {
      return GraphqlErrorBuilder.newError(env)
        .errorType(ErrorType.NOT_FOUND).message(nfe.getMessage()).build();
    }
    return null;
  }
}

10. Testing GraphQL Endpoints (GraphQlTester)

Example: Test GraphQL query with GraphQlTester

@AutoConfigureGraphQlTester
@SpringBootTest
class BookGqlTests {
  @Autowired GraphQlTester tester;
  @Test void getBook() {
    tester.document("{ book(id: 1) { title } }")
      .execute()
      .path("book.title").entity(String.class).isEqualTo("Effective Java");
  }
}

11. Integrating with GraphiQL UI

Example: Enable GraphiQL browser UI

spring:
  graphql:
    graphiql:
      enabled: true
      path: /graphiql
    path: /graphql
    schema.printer.enabled: true

12. Implementing Subscriptions for Real-Time Updates

Example: GraphQL subscription with Flux

@SubscriptionMapping
public Flux<Book> bookAdded() {
  return bookSink.asFlux(); // Sinks.Many<Book>
}
// schema: type Subscription { bookAdded: Book! }
// websocket endpoint: /graphql (set spring.graphql.websocket.path)