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.
By default, the GraphQlHttpHandler
will serialize and deserialize JSON payloads using the HttpMessageConverter
(Spring MVC)
and the DecoderHttpMessageReader/EncoderHttpMessageWriter
(WebFlux) configured in the web framework.
In some cases, the application will configure the JSON codec for the HTTP endpoint in a way that is not compatible with the GraphQL payloads.
Applications can instantiate GraphQlHttpHandler
with a custom JSON codec that will be used for GraphQL payloads.
The 1.0.x branch of this repository contains a Spring MVC HTTP sample application.
Server-Sent Events
GraphQlSseHandler
is very similar to the HTTP handler listed above, but this time handling GraphQL requests over HTTP
using the Server-Sent Events protocol. With this transport, clients must send HTTP POST requests to the endpoint with
"application/json"
as content type and GraphQL request details included as JSON in the request body; the only
difference with the vanilla HTTP variant is that the client must send "text/event-stream"
as the "Accept"
request
header. The response will be sent as one or more Server-Sent Event(s).
This is also defined in the proposed GraphQL over HTTP specification. Spring for GraphQL only implements the "Distinct connections mode", so applications must consider scalability concerns and whether adopting HTTP/2 as the underlying transport would help.
The main use case for GraphQlSseHandler
is an alternative to the
WebSocket transport, receiving a stream of items as a response to a
subscription operation. Other types of operations, like queries and mutations, are not supported here and should be
using the plain JSON over HTTP transport variant.
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 you can add a property for the endpoint path to enable it. Please, review
Web Endpoints
in the Boot reference documentation, and the list of supported spring.graphql.websocket
properties.
You can also look at GraphQlWebMvcAutoConfiguration
or GraphQlWebFluxAutoConfiguration
for the actual Boot autoconfig details.
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.
Interceptors allow applications to intercept incoming requests in order to:
-
Check HTTP request details
-
Customize the
graphql.ExecutionInput
-
Add HTTP response headers
-
Customize the
graphql.ExecutionResult
-
and more
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.
WebSocketGraphQlInterceptor
WebSocketGraphQlInterceptor
extends WebGraphQlInterceptor
with additional callbacks
to handle the start and end of a WebSocket connection, in addition to client-side
cancellation of subscriptions. The same also intercepts every GraphQL request on the
WebSocket connection.
Use WebGraphQlHandler
to configure the WebGraphQlInterceptor
chain. This is supported
by the Boot Starter, see
Web Endpoints.
There can be at most one WebSocketGraphQlInterceptor
in a chain of interceptors.
There are two built-in WebSocket interceptors called AuthenticationWebSocketInterceptor
,
one for the WebMVC and one for the WebFlux transports. These help to extract authentication
details from the payload of a "connection_init"
GraphQL over WebSocket message, authenticate,
and then propagate the SecurityContext
to subsequent requests on the WebSocket connection.
There is a websocket-authentication sample in spring-graphql-examples. |
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 |
There is a websocket-authentication sample in spring-graphql-examples. |