Vert.x Web GraphQL

Vert.x Web GraphQL extends Vert.x Web with the GraphQL-Java library so that you can build a GraphQL server.

Tip
This is the reference documentation for Vert.x Web GraphQL. It is highly recommended to get familiar with the GraphQL-Java API first. You may start by reading the GraphQL-Java documentation.

Getting started

To use this module, add the following to the dependencies section of your Maven POM file:

<dependency>
 <groupId>io.vertx</groupId>
 <artifactId>vertx-web-graphql</artifactId>
 <version>4.5.7</version>
</dependency>

Or, if you use Gradle:

compile 'io.vertx:vertx-web-graphql:4.5.7'

Handlers setup

HTTP

Create a Vert.x Web Route and a GraphQLHandler for it:

GraphQL graphQL = setupGraphQLJava();

router.route("/graphql").handler(GraphQLHandler.create(graphQL));
Tip

The GraphQLHandler supports Apollo’s automatic persisted queries, provided GraphQL-Java is configured accordingly:

graphQLBuilder.preparsedDocumentProvider(new ApolloPersistedQuerySupport(queryCache));

The handler serves both GET and POST requests. However, you can restrict the service to one type of HTTP method:

GraphQL graphQL = setupGraphQLJava();

router.post("/graphql").handler(GraphQLHandler.create(graphQL));
Important
The GraphQLHandler requires a BodyHandler to read POST requests content.

Query batching

Query batching consists in posting an array instead of a single object to the GraphQL endpoint.

Vert.x Web GraphQL can handle such requests but by default the feature is disabled. To enable it, create the GraphQLHandler with options:

GraphQLHandlerOptions options = new GraphQLHandlerOptions()
  .setRequestBatchingEnabled(true);

GraphQLHandler handler = GraphQLHandler.create(graphQL, options);

GraphQL over WebSocket

Vert.x Web GraphQL is compatible with the GraphQL over Websocket protocol.

The websocket transport is specially useful when you need to add subscriptions to your GraphQL schema, but you can also use it for queries and mutations.

Caution
By default, the configuration does not include a default Origin property. To prevent Cross-Site WebSocket Hijacking attacks from web browsers, it is recommended to set this property to the internet facing origin of the application. This will enforce a check that web sockets origin is from this application. This check is important because WebSockets are not restrained by the same-origin policy, an attacker can easily initiate a WebSocket request from a malicious webpage targeting the ws:// or wss:// endpoint URL of the GraphQL WS handler.
router.route("/graphql").handler(GraphQLWSHandler.create(graphQL));
Important

The client will ask for the graphql-transport-ws websocket subprotocol. Consequently, it has to be added to the list of supported subprotocols in the server configuration:

HttpServerOptions httpServerOptions = new HttpServerOptions()
  .addWebSocketSubProtocol("graphql-transport-ws");
Tip

The GraphQLWSHandler supports Apollo’s automatic persisted queries, provided GraphQL-Java is configured accordingly:

graphQLBuilder.preparsedDocumentProvider(new ApolloPersistedQuerySupport(queryCache));

To support both HTTP and Websockets on the same URI, the GraphQLWSHandler must be installed to the Router before the GraphQLHandler:

router.route("/graphql")
  .handler(GraphQLWSHandler.create(graphQL))
  .handler(GraphQLHandler.create(graphQL));
Important
A subscription DataFetcher has to return a org.reactivestreams.Publisher instance.

GraphiQL IDE

As you are building your application, testing your GraphQL queries in GraphiQL can be handy.

To do so, create a route for GraphiQL resources and a GraphiQLHandler for them:

GraphiQLHandlerOptions options = new GraphiQLHandlerOptions()
  .setEnabled(true);

GraphiQLHandler handler = GraphiQLHandler.create(vertx, options);

router.route("/graphiql*").subRouter(handler.router());
Note
The GraphiQL user interface is disabled by default for security reasons. This is why you must configure the GraphiQLHandlerOptions to enable it.
Tip

GraphiQL is enabled automatically when Vert.x Web runs in development mode. To switch the development mode on, use the VERTXWEB_ENVIRONMENT environment variable or vertxweb.environment system property and set it to dev. In this case, create the GraphiQLHandler without changing the enabled property.

If your application is protected by authentication, you can customize the headers sent by GraphiQL dynamically:

GraphiQLHandlerOptions options = new GraphiQLHandlerOptions()
  .setEnabled(true);

GraphiQLHandler handler = GraphiQLHandler.builder(vertx)
  .with(options)
  .addingHeaders(rc -> {
    String token = rc.get("token");
    return MultiMap.caseInsensitiveMultiMap().add("Authorization", "Bearer " + token);
  })
  .build();

router.route("/graphiql*").subRouter(handler.router());

Please refer to the GraphiQLHandlerOptions documentation for further details.

Fetching data

The GraphQL-Java API is very well suited for the asynchronous world: the asynchronous execution strategy is the default for queries (serial asynchronous for mutations).

To avoid blocking the event loop, all you have to do is implement data fetchers that return a CompletionStage instead of the result directly.

DataFetcher<CompletionStage<List<Link>>> dataFetcher = environment -> {
  Future<List<Link>> future = retrieveLinksFromBackend(environment);
  return future.toCompletionStage();
};

RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
  .type("Query", builder -> builder.dataFetcher("allLinks", dataFetcher))
  .build();
Tip

Instead of converting Vert.x Future to java.util.concurrent.CompletionStage manually in every data fetcher implementation, configure GraphQL-Java with the VertxFutureAdapter instrumentation.

First, declare the instrumentation while configuring GraphQL-Java.

graphQLBuilder.instrumentation(VertxFutureAdapter.create());

Then you can return Vert.x futures directly.

DataFetcher<Future<List<Link>>> dataFetcher = environment -> {
  Future<List<Link>> future = retrieveLinksFromBackend(environment);
  return future;
};

Providing data fetchers with some context

Very often, the GraphQLHandler, or the GraphQLWSHandler, will be declared after other route handlers. For example, you could protect your application with authentication.

In this case, it is likely that your data fetchers will need to know which user is logged-in, to narrow down the results.

For this, you may retrieve the RoutingContext object by inspecting the DataFetchingEnvironment:

DataFetcher<CompletionStage<List<Link>>> dataFetcher = environment -> {

  RoutingContext routingContext = environment.getGraphQlContext().get(RoutingContext.class);

  User user = routingContext.user();

  Future<List<Link>> future = retrieveLinksPostedBy(user);
  return future.toCompletionStage();

};

JSON data results

The default GraphQL data fetcher is the PropertyDataFetcher. It is able to read the fields of your domain objects without further configuration.

Nevertheless, in Vert.x applications it is common to work with JsonArray and JsonObject. The PropertyDataFetcher can read the items in a JsonArray out of the box, but not the fields of a JsonObject.

The solution to this problem depends on your GraphQL-Java version.

Note
Both solutions let you mix JsonObject, JsonArray and domain objects results.

GraphQL-Java 20 and later

Configure GraphQL-Java with the JsonObjectAdapter instrumentation.

graphQLBuilder.instrumentation(new JsonObjectAdapter());

GraphQL-Java 19 and before

Configure GraphQL-Java to use VertxPropertyDataFetcher instead:

RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();

builder.wiringFactory(new WiringFactory() {

  @Override
  public DataFetcher<Object> getDefaultDataFetcher(FieldWiringEnvironment environment) {

    return VertxPropertyDataFetcher.create(environment.getFieldDefinition().getName());

  }
});

Batch loading

Dataloaders help you to load data efficiently by batching fetch requests and caching results.

First, create a batch loader:

BatchLoaderWithContext<String, Link> linksBatchLoader = (ids, env) -> {
  // retrieveLinksFromBackend takes a list of ids and returns a CompletionStage for a list of links
  return retrieveLinksFromBackend(ids, env);
};

Then, configure the GraphQLHandler to create a DataLoaderRegistry for each request:

GraphQLHandler handler = GraphQLHandler.builder(graphQL).beforeExecute(builderWithContext -> {

  DataLoader<String, Link> linkDataLoader = DataLoaderFactory.newDataLoader(linksBatchLoader);

  DataLoaderRegistry dataLoaderRegistry = new DataLoaderRegistry().register("link", linkDataLoader);

  builderWithContext.builder().dataLoaderRegistry(dataLoaderRegistry);

}).build();

File uploads

GraphQL multipart request is an interoperable multipart form field structure for GraphQL requests. By enabling this functionality, GraphQL clients will be able to upload files using a single mutation call. All the server-side file handling will be abstracted by the GraphQLHandler.

To enable it, create a GraphQLHandler with the requestMultipartEnabled configuration set to true and add the BodyHandler to the router.

GraphQLHandler graphQLHandler = GraphQLHandler.create(
  setupGraphQLJava(),
  new GraphQLHandlerOptions().setRequestMultipartEnabled(true)
);

Router router = Router.router(vertx);

router.route().handler(BodyHandler.create());
router.route("/graphql").handler(graphQLHandler);
Important
If the router does not have a BodyHandler, the multipart request parser will not be able to handle the GraphQL mutation call.

Finally, create the Upload scalar and set it to the RuntimeWiring:

RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring().scalar(UploadScalar.build()).build();

The FileUpload instance can be accessed using the DataFetchingEnvironment::getArgument method.

FileUpload file = environment.getArgument("myFile");

RxJava 3 API

Setting up with an Rxified router

To handle GraphQL requests on a Rxified Route, make sure to import the GraphQLHandler class.

Working with Vert.x Rxified APIs

GraphQL-Java expects CompletionStage for asynchronous results in data fetchers and batch loaders.

Therefore, if you work with the Vert.x Rxified APIs (e.g. the Web Client), you will have to adapt the Single and Maybe objects.

DataFetcher<CompletionStage<String>> fetcher = environment -> {
 Single<String> data = loadDataFromBackend();
 return data.toCompletionStage();
};
Tip

Instead of converting Single or Maybe to CompletionStage manually in every data fetcher implementation, configure GraphQL-Java with the SingleAdapter and MaybeAdapter instrumentations.

First, declare the instrumentations while configuring GraphQL-Java.

graphQLBuilder.instrumentation(new ChainedInstrumentation(SingleAdapter.create(), MaybeAdapter.create()));

Then you can return Single or Maybe directly.

DataFetcher<Single<List<Link>>> dataFetcher = environment -> {
  Single<List<Link>> single = retrieveLinksFromBackend(environment);
  return single;
};