For the latest stable version, please use Spring GraphQL 1.3.2! |
For the latest stable version, please use Spring GraphQL 1.3.2! |
Spring for GraphQL supports server handling of GraphQL requests over HTTP, WebSocket, and RSocket.
HTTP
GraphQlHttpHandler
handles GraphQL over HTTP requests and delegates to the
Interception chain for request execution. There are two variants, one for
Spring MVC and one for Spring WebFlux. Both handle requests asynchronously and have
equivalent functionality, but rely on blocking vs non-blocking I/O respectively for
writing the HTTP response.
Requests must use HTTP POST with "application/json"
as content type and GraphQL request details
included as JSON in the request body, as defined in the proposed
GraphQL over HTTP specification.
Once the JSON body has been successfully decoded, the HTTP response status is always 200 (OK),
and any errors from GraphQL request execution appear in the "errors" section of the GraphQL response.
The default and preferred choice of media type is "application/graphql-response+json"
, but "application/json"
is also supported, as described in the specification.
GraphQlHttpHandler
can be exposed as an HTTP endpoint by declaring a RouterFunction
bean and using the RouterFunctions
from Spring MVC or WebFlux to create the route. The
Boot Starter does this, see the
Web Endpoints section for
details, or check GraphQlWebMvcAutoConfiguration
or GraphQlWebFluxAutoConfiguration
it contains, for the actual config.
The 1.0.x branch of this repository contains a Spring MVC HTTP sample application.
File Upload
As a protocol GraphQL focuses on the exchange of textual data. This doesn’t include binary data such as images, but there is a separate, informal graphql-multipart-request-spec that allows file uploads with GraphQL over HTTP.
Spring for GraphQL does not support the graphql-multipart-request-spec
directly.
While the spec does provide the benefit of a unified GraphQL API, the actual experince has
led to a number of issues, and best practice recommendations have evolved, see
Apollo Server File Upload Best Practices
for a more detailed discussion.
If you would like to use graphql-multipart-request-spec
in your application, you can
do so through the library
multipart-spring-graphql.
WebSocket
GraphQlWebSocketHandler
handles GraphQL over WebSocket requests based on the
protocol defined in the
graphql-ws library. The main reason to use
GraphQL over WebSocket is subscriptions which allow sending a stream of GraphQL
responses, but it can also be used for regular queries with a single response.
The handler delegates every request to the Interception chain for further
request execution.
GraphQL Over WebSocket Protocols
There are two such protocols, one in the subscriptions-transport-ws library and another in the graphql-ws library. The former is not active and succeeded by the latter. Read this blog post for the history. |
There are two variants of GraphQlWebSocketHandler
, one for Spring MVC and one for
Spring WebFlux. Both handle requests asynchronously and have equivalent functionality.
The WebFlux handler also uses non-blocking I/O and back pressure to stream messages,
which works well since in GraphQL Java a subscription response is a Reactive Streams
Publisher
.
The graphql-ws
project lists a number of
recipes for client use.
GraphQlWebSocketHandler
can be exposed as a WebSocket endpoint by declaring a
SimpleUrlHandlerMapping
bean and using it to map the handler to a URL path. By default,
the Boot Starter does not expose a GraphQL over WebSocket endpoint, but it’s easy to
enable it by adding a property for the endpoint path. Please, see the
Web Endpoints
section for details, or check the GraphQlWebMvcAutoConfiguration
or the
GraphQlWebFluxAutoConfiguration
for the actual Boot starter config.
The 1.0.x branch of this repository contains a WebFlux WebSocket sample application.
GraphQL Over WebSocket Protocols
There are two such protocols, one in the subscriptions-transport-ws library and another in the graphql-ws library. The former is not active and succeeded by the latter. Read this blog post for the history. |
RSocket
GraphQlRSocketHandler
handles GraphQL over RSocket requests. Queries and mutations are
expected and handled as an RSocket request-response
interaction while subscriptions are
handled as request-stream
.
GraphQlRSocketHandler
can be used a delegate from an @Controller
that is mapped to
the route for GraphQL requests. For example:
import java.util.Map;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.graphql.server.GraphQlRSocketHandler;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;
@Controller
public class GraphQlRSocketController {
private final GraphQlRSocketHandler handler;
GraphQlRSocketController(GraphQlRSocketHandler handler) {
this.handler = handler;
}
@MessageMapping("graphql")
public Mono<Map<String, Object>> handle(Map<String, Object> payload) {
return this.handler.handle(payload);
}
@MessageMapping("graphql")
public Flux<Map<String, Object>> handleSubscription(Map<String, Object> payload) {
return this.handler.handleSubscription(payload);
}
}
Interception
Server transports allow intercepting requests before and after the GraphQL Java engine is called to process a request.
WebGraphQlInterceptor
HTTP and WebSocket transports invoke a chain of
0 or more WebGraphQlInterceptor
, followed by an ExecutionGraphQlService
that calls
the GraphQL Java engine. WebGraphQlInterceptor
allows an application to intercept
incoming requests and do one of the following:
-
Check HTTP request details
-
Customize the
graphql.ExecutionInput
-
Add HTTP response headers
-
Customize the
graphql.ExecutionResult
For example, an interceptor can pass an HTTP request header to a DataFetcher
:
import java.util.Collections;
import reactor.core.publisher.Mono;
import org.springframework.graphql.data.method.annotation.ContextValue;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.server.WebGraphQlInterceptor;
import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.graphql.server.WebGraphQlResponse;
import org.springframework.stereotype.Controller;
class RequestHeaderInterceptor implements WebGraphQlInterceptor { (1)
@Override
public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
String value = request.getHeaders().getFirst("myHeader");
request.configureExecutionInput((executionInput, builder) ->
builder.graphQLContext(Collections.singletonMap("myHeader", value)).build());
return chain.next(request);
}
}
@Controller
class MyContextValueController { (2)
@QueryMapping
Person person(@ContextValue String myHeader) {
...
}
}
1 | Interceptor adds HTTP request header value into GraphQLContext |
2 | Data controller method accesses the value |
Reversely, an interceptor can access values added to the GraphQLContext
by a controller:
import graphql.GraphQLContext;
import reactor.core.publisher.Mono;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.server.WebGraphQlInterceptor;
import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.graphql.server.WebGraphQlResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Controller;
// Subsequent access from a WebGraphQlInterceptor
class ResponseHeaderInterceptor implements WebGraphQlInterceptor {
@Override
public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) { (2)
return chain.next(request).doOnNext((response) -> {
String value = response.getExecutionInput().getGraphQLContext().get("cookieName");
ResponseCookie cookie = ResponseCookie.from("cookieName", value).build();
response.getResponseHeaders().add(HttpHeaders.SET_COOKIE, cookie.toString());
});
}
}
@Controller
class MyCookieController {
@QueryMapping
Person person(GraphQLContext context) { (1)
context.put("cookieName", "123");
...
}
}
1 | Controller adds value to the GraphQLContext |
2 | Interceptor uses the value to add an HTTP response header |
WebGraphQlHandler
can modify the ExecutionResult
, for example, to inspect and modify
request validation errors that are raised before execution begins and which cannot be
handled with a DataFetcherExceptionResolver
:
import java.util.List;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import reactor.core.publisher.Mono;
import org.springframework.graphql.server.WebGraphQlInterceptor;
import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.graphql.server.WebGraphQlResponse;
class RequestErrorInterceptor implements WebGraphQlInterceptor {
@Override
public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
return chain.next(request).map((response) -> {
if (response.isValid()) {
return response; (1)
}
List<GraphQLError> errors = response.getErrors().stream() (2)
.map((error) -> {
GraphqlErrorBuilder<?> builder = GraphqlErrorBuilder.newError();
// ...
return builder.build();
})
.toList();
return response.transform((builder) -> builder.errors(errors).build()); (3)
});
}
}
1 | Return the same if ExecutionResult has a "data" key with non-null value |
2 | Check and transform the GraphQL errors |
3 | Update the ExecutionResult with the modified errors |
Use WebGraphQlHandler
to configure the WebGraphQlInterceptor
chain. This is supported
by the Boot Starter, see
Web Endpoints.
RSocketQlInterceptor
Similar to WebGraphQlInterceptor
, an RSocketQlInterceptor
allows intercepting
GraphQL over RSocket requests before and after GraphQL Java engine execution. You can use
this to customize the graphql.ExecutionInput
and the graphql.ExecutionResult
.
1 | Interceptor adds HTTP request header value into GraphQLContext |
2 | Data controller method accesses the value |
1 | Controller adds value to the GraphQLContext |
2 | Interceptor uses the value to add an HTTP response header |
1 | Return the same if ExecutionResult has a "data" key with non-null value |
2 | Check and transform the GraphQL errors |
3 | Update the ExecutionResult with the modified errors |