<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-graphql</artifactId>
<version>4.1.8</version>
</dependency>
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.
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:
Or, if you use Gradle:
compile 'io.vertx:vertx-web-graphql:4.1.8'
Handlers setup
HTTP
Create a Vert.x Web Route
and a GraphQLHandler
for it:
GraphQL graphQL = setupGraphQLJava();
router.route("/graphql").handler(GraphQLHandler.create(graphQL));
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));
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);
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);
router.route("/graphiql/*").handler(GraphiQLHandler.create(options));
Then browse to http://localhost:8080/graphiql/.
The GraphiQL user interface is disabled by default for security reasons. This is why you must configure the GraphiQLHandlerOptions to enable it. |
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:
graphiQLHandler.graphiQLRequestHeaders(rc -> {
String token = rc.get("token");
return MultiMap.caseInsensitiveMultiMap().add("Authorization", "Bearer " + token);
});
router.route("/graphiql/*").handler(graphiQLHandler);
Please refer to the GraphiQLHandlerOptions
documentation for further details.
Apollo WebSocketLink
You can use an Apollo WebSocketLink which connects over a websocket. This is specially useful if you want to add subscriptions to your GraphQL schema, but you can also use the websocket for queries and mutations.
GraphQL graphQL = setupGraphQLJava();
router.route("/graphql").handler(ApolloWSHandler.create(graphQL));
To support the graphql-ws websocket subprotocol, it has to be added to the server configuration: |
HttpServerOptions httpServerOptions = new HttpServerOptions()
.addWebSocketSubProtocol("graphql-ws");
vertx.createHttpServer(httpServerOptions)
.requestHandler(router)
.listen(8080);
If you want to support a WebSocketLink and a HttpLink in the same path, you can add the ApolloWSHandler in first place and then the GraphQLHandler. |
GraphQL graphQL = setupGraphQLJava();
router.route("/graphql").handler(ApolloWSHandler.create(graphQL));
router.route("/graphql").handler(GraphQLHandler.create(graphQL));
Here you can find how to configure the Apollo SubscriptionClient: https://github.com/apollographql/subscriptions-transport-ws
A subscription DataFetcher has to return a org.reactivestreams.Publisher instance. |
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 -> {
CompletableFuture<List<Link>> completableFuture = new CompletableFuture<>();
retrieveLinksFromBackend(environment, ar -> {
if (ar.succeeded()) {
completableFuture.complete(ar.result());
} else {
completableFuture.completeExceptionally(ar.cause());
}
});
return completableFuture;
};
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder.dataFetcher("allLinks", dataFetcher))
.build();
Working with callback-based APIs
Implementing a data fetcher that returns a CompletionStage
is not a complex task. But when you work with Vert.x callback-based APIs, it requires a bit of boilerplate.
This is where the VertxDataFetcher
can help:
VertxDataFetcher<List<Link>> dataFetcher = VertxDataFetcher.create((env, promise) -> {
retrieveLinksFromBackend(env, promise);
});
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder.dataFetcher("allLinks", dataFetcher))
.build();
Working with Vert.x Futures
VertxDataFetcher
can also help with futurized APIs:
VertxDataFetcher<List<Link>> dataFetcher = VertxDataFetcher.create(environment -> {
Future<List<Link>> future = retrieveLinksFromBackend(environment);
return future;
});
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder.dataFetcher("allLinks", dataFetcher))
.build();
Providing data fetchers with some context
Very often, the GraphQLHandler
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. Let’s say your authentication layer stores a User
object in the RoutingContext
.
You may retrieve this object by inspecting the DataFetchingEnvironment
:
VertxDataFetcher<List<Link>> dataFetcher = VertxDataFetcher.create((environment, promise) -> {
RoutingContext routingContext = environment.getContext();
User user = routingContext.get("user");
retrieveLinksPostedBy(user, promise);
});
The routing context is available with any kind of data fetchers, not just VertxDataFetcher . |
If you prefer not to expose the routing context to your data fetchers, configure the GraphQL handler to customize the context object:
VertxDataFetcher<List<Link>> dataFetcher = VertxDataFetcher.create((environment, promise) -> {
// User as custom context object
User user = environment.getContext();
retrieveLinksPostedBy(user, promise);
});
GraphQL graphQL = setupGraphQLJava(dataFetcher);
// Customize the query context object when setting up the handler
GraphQLHandler handler = GraphQLHandler.create(graphQL).queryContext(routingContext -> {
return routingContext.get("user");
});
router.route("/graphql").handler(handler);
JSON data results
The default GraphQL data fetcher is PropertyDataFetcher
. As a consequence, it will be able to read the fields of your domain objects without further configuration.
Nevertheless, some Vert.x data clients return JsonArray
and JsonObject
results.
If you don’t need (or don’t wish to) use a domain object layer, you can 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());
}
});
VertxPropertyDataFetcher wraps a PropertyDataFetcher so you can still use it with domain objects. |
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.create(graphQL).dataLoaderRegistry(rc -> {
DataLoader<String, Link> linkDataLoader = DataLoader.newDataLoader(linksBatchLoader);
return new DataLoaderRegistry().register("link", linkDataLoader);
});
If you work with Vert.x APIs, you may use a VertxBatchLoader
or a VertxMappedBatchLoader
to simplify your code:
BatchLoaderWithContext<Long, String> commentsBatchLoader = VertxBatchLoader.create((ids, env) -> {
// findComments takes a list of ids and returns a Future for a list of links
return findComments(ids, env);
});
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);
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 or the Cassandra Client), you will have to adapt the Single
and Maybe
objects.
The RxJavaJdk8Interop
library provides the tooling to do just that. Add the following to the dependencies section of your Maven build file:
<dependency>
<groupId>com.github.akarnokd</groupId>
<artifactId>rxjava3-jdk8-interop</artifactId>
<version>3.0.0-RC6</version>
</dependency>
Or if you use Gradle:
implementation 'com.github.akarnokd:rxjava3-jdk8-interop:3.0.0-RC6'
Then you can create a data fetcher from a Single
result:
Single<String> data = loadDataFromBackend();
DataFetcher<CompletionStage<String>> fetcher = environment -> {
return data.to(SingleInterop.get());
};
For Maybe
results, use MaybeInterop
.