Server Transports

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.

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.