GraphQL Support¶
SpringFlow provides automatic GraphQL API generation for your JPA entities. Simply enable the GraphQL feature and SpringFlow will generate a complete GraphQL schema with queries and mutations.
Quick Start¶
1. Add Dependency¶
<dependency>
<groupId>io.github.tky0065</groupId>
<artifactId>springflow-graphql</artifactId>
<version>0.5.1</version>
</dependency>
2. Enable GraphQL¶
In your application.yml:
springflow:
graphql:
enabled: true
schema-location: src/main/resources/graphql
graphiql-enabled: true
introspection-enabled: true
# IMPORTANT: Spring Boot GraphQL auto-configuration must be enabled
spring:
graphql:
graphiql:
enabled: true # Required - activates Spring GraphQL infrastructure
path: /graphiql
path: /graphql
Required Configuration
The spring.graphql.graphiql.enabled=true setting is mandatory when using springflow-graphql.
Without it, Spring Boot's GraphQL auto-configuration will not activate, causing a
NoSuchBeanDefinitionException: No qualifying bean of type 'BatchLoaderRegistry' error at startup.
This configuration activates the Spring GraphQL infrastructure that SpringFlow depends on.
3. That's It!¶
SpringFlow will automatically generate:
- GraphQL types for your entities
- Input types for create/update operations
- Query resolvers (findAll, findById)
- Mutation resolvers (create, update, delete)
- Pagination support
Generated GraphQL Schema¶
For an entity like this:
@Entity
@AutoApi(path = "/products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
private String name;
@NotNull
@Min(0)
private Double price;
@ReadOnly
private LocalDateTime createdAt;
}
SpringFlow generates:
type Product {
id: ID!
name: String!
price: Float!
createdAt: String
}
input ProductInput {
name: String!
price: Float!
}
type ProductPage {
content: [Product!]!
pageInfo: PageInfo!
}
type PageInfo {
pageNumber: Int!
pageSize: Int!
totalElements: Int!
totalPages: Int!
hasNext: Boolean!
hasPrevious: Boolean!
}
type Query {
products(page: Int, size: Int): ProductPage!
product(id: ID!): Product
}
type Mutation {
createProduct(input: ProductInput!): Product!
updateProduct(id: ID!, input: ProductInput!): Product!
deleteProduct(id: ID!): Boolean!
}
Query Examples¶
Find All with Pagination¶
query {
products(page: 0, size: 10) {
content {
id
name
price
createdAt
}
pageInfo {
totalElements
totalPages
hasNext
}
}
}
Find All with Filters¶
SpringFlow GraphQL supports dynamic filtering using the same filter syntax as the REST API:
query {
products(
page: 0,
size: 10,
filters: {
name_like: "Laptop"
price_gte: "500"
price_lte: "2000"
}
) {
content {
id
name
price
}
pageInfo {
totalElements
}
}
}
Available Filter Operations:
- field: Equals (e.g., category: "Electronics")
- field_like: Contains/LIKE (e.g., name_like: "Laptop")
- field_gt: Greater than (e.g., price_gt: "100")
- field_gte: Greater than or equal (e.g., price_gte: "100")
- field_lt: Less than (e.g., price_lt: "1000")
- field_lte: Less than or equal (e.g., price_lte: "1000")
- field_in: In list (e.g., category_in: "Electronics,Computers")
- field_not_in: Not in list (e.g., status_not_in: "DELETED,ARCHIVED")
- field_null: Is null (e.g., deletedAt_null: "true")
- field_between: Between (e.g., price_between: "100,500")
Multiple Filters: All filters are combined with AND logic:
query {
products(
filters: {
name_like: "Laptop"
price_gte: "500"
price_lte: "2000"
category: "Electronics"
}
) {
content { id, name, price }
}
}
Find by ID¶
Mutation Examples¶
Create¶
Update¶
mutation {
updateProduct(id: "1", input: {
name: "Gaming Laptop"
price: 1499.99
}) {
id
name
price
}
}
Delete¶
Field Annotations¶
GraphQL generation respects SpringFlow field annotations:
@Hidden¶
Fields marked as @Hidden are excluded from both types and input types:
@ReadOnly¶
Fields marked as @ReadOnly are included in output types but excluded from input types:
ID Fields¶
ID fields are automatically excluded from input types:
Configuration Options¶
SpringFlow GraphQL Configuration¶
springflow:
graphql:
# Enable/disable GraphQL support
enabled: true
# Where to write the generated schema.graphqls file
schema-location: src/main/resources/graphql
# Enable GraphiQL UI for testing
graphiql-enabled: true
# Enable introspection (disable in production for security)
introspection-enabled: true
Required Spring GraphQL Configuration¶
Mandatory Configuration
These Spring Boot GraphQL settings are required for SpringFlow GraphQL to work:
spring:
graphql:
graphiql:
enabled: true # MANDATORY - without this, application will fail to start
path: /graphiql # Optional - customize GraphiQL path
path: /graphql # Optional - customize GraphQL endpoint path
Why is this required?
SpringFlow GraphQL depends on Spring Boot's GraphQL infrastructure (BatchLoaderRegistry, GraphQlSource, etc.).
These beans are only created when Spring Boot's GraphQL auto-configuration is activated.
Setting spring.graphql.graphiql.enabled=true triggers this auto-configuration.
GraphiQL Interface¶
Access the GraphiQL interface at /graphiql to explore and test your GraphQL API:
GraphiQL provides: - Interactive query editor with syntax highlighting - Auto-completion - Schema documentation - Query history - Variable support
Type Mappings¶
SpringFlow automatically maps Java types to GraphQL types:
| Java Type | GraphQL Type |
|---|---|
String |
String |
Integer, int |
Int |
Long, long |
ID |
Double, double, Float, float |
Float |
Boolean, boolean |
Boolean |
LocalDate, LocalDateTime |
String (ISO-8601) |
Pagination¶
All generated findAll queries support pagination:
query {
products(page: 0, size: 20) {
content {
id
name
}
pageInfo {
pageNumber # Current page number
pageSize # Items per page
totalElements # Total items across all pages
totalPages # Total number of pages
hasNext # Is there a next page?
hasPrevious # Is there a previous page?
}
}
}
Default values:
- page: 0
- size: 20
Validation¶
Input validation is automatically enforced based on JSR-380 annotations:
If validation fails, GraphQL returns an error:
{
"errors": [
{
"message": "Validation failed: name must not be blank",
"path": ["createProduct"]
}
]
}
Security¶
GraphQL respects Spring Security configuration. If Spring Security is enabled and configured via @AutoApi(security = ...), the same authorization rules apply to GraphQL operations.
Production Recommendation:
- Set introspection-enabled: false in production for security
- Configure appropriate authentication/authorization
Testing GraphQL¶
Use GraphQlTester for integration testing:
@SpringBootTest
class GraphQLTest {
@Autowired
GraphQlTester graphQlTester;
@Test
void testQuery() {
String query = """
query {
products(page: 0, size: 5) {
content {
id
name
}
}
}
""";
graphQlTester.document(query)
.execute()
.path("products.content")
.entityList(Object.class)
.satisfies(list -> assertThat(list).isNotNull());
}
}
DataLoader for N+1 Problem¶
SpringFlow GraphQL automatically configures DataLoaders to solve the N+1 query problem when loading related entities.
What is the N+1 Problem?¶
When loading a list of entities with related data, naive implementations can execute N+1 database queries: - 1 query to load the parent entities - N queries to load related entities (one per parent)
Example without DataLoader:
SELECT * FROM Category WHERE ... -- 1 query
SELECT * FROM Product WHERE category_id = 1 -- Query 1
SELECT * FROM Product WHERE category_id = 2 -- Query 2
... (N more queries)
How DataLoader Solves This¶
DataLoader batches multiple fetch requests into a single database query:
Example with DataLoader:
SELECT * FROM Category WHERE ... -- 1 query
SELECT * FROM Product WHERE category_id IN (1, 2, 3, ...) -- 1 batched query
Automatic Configuration¶
SpringFlow automatically registers a DataLoader for each entity annotated with @AutoApi. No additional configuration required!
When GraphQL resolves entity relationships, SpringFlow's DataLoader:
1. Collects all IDs that need to be loaded
2. Batches them into a single findAllById() query
3. Returns entities in the correct order
4. Dramatically improves query performance
Performance Comparison¶
Without DataLoader:
Loading 100 categories with products: 101 queries (1 + 100)
With DataLoader:
Loading 100 categories with products: 2 queries (1 + 1 batched)
Implementation Details¶
DataLoaders are automatically registered at application startup via the DataLoaderRegistrar component:
@Bean
public DataLoaderRegistrar dataLoaderRegistrar(ApplicationContext applicationContext) {
return new DataLoaderRegistrar(applicationContext);
}
Each entity gets its own DataLoader named {entityName}Loader (e.g., productLoader, categoryLoader).
Relation Loading (Future Enhancement)¶
Automatic field resolvers for entity relationships are planned for a future release. This would enable GraphQL queries like:
query {
products(page: 0, size: 10) {
content {
id
name
category { # Related entity
id
name
}
}
}
}
query {
categories {
content {
id
name
products { # One-to-many relation
id
name
price
}
children { # Self-referencing relation
id
name
}
}
}
}
Current Workaround:
For now, you can fetch related entities using separate queries and the DataLoader will batch the requests:
query {
products(page: 0, size: 10) {
content {
id
name
# Fetch category IDs manually
}
}
}
# Then fetch categories by IDs (batched by DataLoader)
query {
category(id: "1") { id, name }
}
Implementation Complexity:
Full relation support requires: - Schema generation for relation fields - Field resolvers (@SchemaMapping) for each relation type - Circular reference handling (e.g., Category ↔ Products) - Nested DataLoader orchestration - Lazy vs eager loading strategies
This is planned for version 0.4.0 or later.
Future Enhancements¶
Planned for future releases: - Relation Field Resolvers: Automatic GraphQL field resolvers for JPA relationships (v0.4.0+) - Subscriptions: Real-time updates via GraphQL subscriptions - Custom DataLoaders: Support for custom batching strategies - Query Complexity Analysis: Prevent overly complex queries - Persisted Queries: Support for persisted/automatic query caching
Best Practices¶
- Enable Only When Needed: GraphQL is opt-in via configuration
- Disable Introspection in Production: Prevents schema discovery
- Use Pagination: Always paginate large result sets
- Validate Input: Rely on JSR-380 annotations for validation
- Test with GraphiQL: Use the interactive UI during development
Troubleshooting¶
Application Fails to Start: NoSuchBeanDefinitionException¶
Error:
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'org.springframework.graphql.execution.BatchLoaderRegistry' available
Cause: Spring Boot GraphQL auto-configuration is not activated.
Solution: Add the required Spring GraphQL configuration to application.yml:
This configuration is mandatory when using springflow-graphql. It activates Spring Boot's GraphQL
infrastructure that SpringFlow depends on.
Technical Details:
SpringFlow GraphQL requires several Spring Boot GraphQL beans (BatchLoaderRegistry, GraphQlSource, etc.)
that are only created when Spring Boot's GraphQL auto-configuration activates. Setting
spring.graphql.graphiql.enabled=true is the simplest way to trigger this auto-configuration.
Starting with SpringFlow v0.5.1, the DataLoaderRegistrar bean is conditional on BatchLoaderRegistry
existence, which prevents this error and allows the REST API to work even if GraphQL configuration is incomplete.
GraphQL Not Available¶
Ensure GraphQL is enabled:
Schema Not Generated¶
Check logs for schema generation errors. The schema file should be created at the configured schema-location.
GraphiQL Not Loading¶
Verify Spring GraphQL configuration:
Access at: http://localhost:8080/graphiql