spring-graphql

SKILL.md

Spring for GraphQL - Quick Reference

Full Reference: See advanced.md for DataLoader configuration, custom scalars, pagination implementation, GraphQL testing patterns, and subscription controllers.

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: spring-graphql for comprehensive documentation.

Dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

Configuration

spring:
  graphql:
    graphiql:
      enabled: true
      path: /graphiql
    schema:
      locations: classpath:graphql/**/
    path: /graphql
    websocket:
      path: /graphql

Schema Definition

type Query {
    bookById(id: ID!): Book
    allBooks: [Book!]!
}

type Mutation {
    createBook(input: CreateBookInput!): Book!
}

type Book {
    id: ID!
    title: String!
    author: Author!
}

input CreateBookInput {
    title: String!
    authorId: ID!
}

Query Controller

@Controller
public class BookController {

    @QueryMapping
    public Book bookById(@Argument String id) {
        return bookRepository.findById(id).orElse(null);
    }

    @QueryMapping
    public List<Book> allBooks() {
        return bookRepository.findAll();
    }

    @SchemaMapping(typeName = "Book", field = "author")
    public Author author(Book book) {
        return authorRepository.findById(book.getAuthorId()).orElse(null);
    }
}

Mutation Controller

@Controller
public class BookMutationController {

    @MutationMapping
    public Book createBook(@Argument CreateBookInput input) {
        return bookService.create(input);
    }
}

BatchMapping (Solve N+1)

@Controller
public class OptimizedBookController {

    @BatchMapping
    public Map<Book, Author> author(List<Book> books) {
        List<String> authorIds = books.stream()
            .map(Book::getAuthorId)
            .distinct()
            .toList();

        Map<String, Author> authorsById = authorRepository.findAllById(authorIds)
            .stream()
            .collect(Collectors.toMap(Author::getId, a -> a));

        return books.stream()
            .collect(Collectors.toMap(
                book -> book,
                book -> authorsById.get(book.getAuthorId())
            ));
    }
}

Input Validation

@MutationMapping
public Book createBook(@Argument @Valid CreateBookInput input) {
    return bookService.create(input);
}

public record CreateBookInput(
    @NotBlank @Size(min = 1, max = 200) String title,
    @NotNull String authorId
) {}

Error Handling

@Component
public class CustomExceptionResolver extends DataFetcherExceptionResolverAdapter {

    @Override
    protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
        if (ex instanceof BookNotFoundException) {
            return GraphqlErrorBuilder.newError(env)
                .errorType(ErrorType.NOT_FOUND)
                .message(ex.getMessage())
                .build();
        }
        return null;
    }
}

Security

@Controller
public class SecuredBookController {

    @QueryMapping
    @PreAuthorize("hasRole('USER')")
    public List<Book> allBooks() {
        return bookRepository.findAll();
    }

    @MutationMapping
    @PreAuthorize("hasRole('ADMIN')")
    public Book createBook(@Argument CreateBookInput input) {
        return bookService.create(input);
    }
}

When NOT to Use This Skill

  • REST APIs - Use standard Spring MVC controllers
  • Standalone GraphQL - Use graphql-java directly
  • Simple CRUD - May be overkill, consider REST
  • File uploads - GraphQL isn't optimized for large binary data

Anti-Patterns

Anti-Pattern Problem Solution
No @BatchMapping N+1 queries on nested fields Use @BatchMapping or DataLoader
Unbounded lists Memory exhaustion Implement pagination
Exposing entities Schema tightly coupled to DB Use DTOs/projections
No error handling Stack traces exposed Custom ExceptionResolver
GraphiQL in prod Security risk Disable in production

Quick Troubleshooting

Problem Diagnostic Fix
N+1 queries Check SQL logs Add @BatchMapping
Field not resolved Check method name Verify @SchemaMapping matches schema
Subscription not working Check WebSocket config Enable WebSocket support
Validation not applied Check @Valid Add @Validated to controller
Auth not working Check security config Add @PreAuthorize annotations

Best Practices

Do Don't
Use @BatchMapping for N+1 Fetch nested data individually
Define clear schema contracts Over-expose internal models
Implement pagination Return unbounded lists
Use input types for mutations Use many scalar arguments
Add proper error handling Expose stack traces

Production Checklist

  • Schema well defined
  • N+1 solved with BatchMapping
  • Input validation enabled
  • Error handling configured
  • Security annotations applied
  • Pagination implemented
  • GraphiQL disabled in prod
  • Query complexity limits
  • Introspection controlled

Reference Documentation

Weekly Installs
12
GitHub Stars
2
First Seen
12 days ago
Installed on
cursor11
gemini-cli11
amp11
cline11
github-copilot11
codex11