Skip to main content

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.
Warning
This module has Tech Preview status, this means the API can change between versions.

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>3.7.1</version>
</dependency>

Or, if you use Gradle:

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

Handler setup

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

def graphQL = this.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:

def graphQL = this.setupGraphQLJava()

router.post("/graphql").handler(GraphQLHandler.create(graphQL))
Tip
The GraphQLHandler does not require a BodyHandler to read POST requests content.

GraphiQL client

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

The GraphiQL user interface is disabled by default. To enable it, configure the GraphiQLOptions:

def options = [
  graphiQLOptions:[
    enabled:true
  ]
]

router.route("/graphql").handler(GraphQLHandler.create(graphQL, options))
Tip
GraphiQL user interface is enabled automatically when Vert.x Web is 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.

Please refer to the GraphiQLOptions documentation for details on customization options.

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

def options = [
  graphiQLOptions:[
    enabled:true
  ]
]

def graphQLHandler = GraphQLHandler.create(graphQL, options).graphiQLRequestHeaders({ rc ->
  def token = rc.get("token")
  return MultiMap.caseInsensitiveMultiMap().add(io.vertx.core.http.HttpHeaders.AUTHORIZATION, "Bearer ${token}")
})

Enable 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:

def options = [
  requestBatchingEnabled:true
]

def handler = GraphQLHandler.create(graphQL, options)

Building a GraphQL server

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.

def dataFetcher = { environment ->

  def completableFuture = new java.util.concurrent.CompletableFuture()

  this.retrieveLinksFromBackend(environment, { ar ->
    if (ar.succeeded()) {
      completableFuture.complete(ar.result())
    } else {
      completableFuture.completeExceptionally(ar.cause())
    }
  })

  return completableFuture
}

def runtimeWiring = graphql.schema.idl.RuntimeWiring.newRuntimeWiring().type("Query", { builder ->
  builder.dataFetcher("allLinks", dataFetcher)
}).build()

Fetching data 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:

def dataFetcher = new io.vertx.ext.web.handler.graphql.VertxDataFetcher({ environment, future ->

  this.retrieveLinksFromBackend(environment, future)

})

def runtimeWiring = graphql.schema.idl.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:

def dataFetcher = new io.vertx.ext.web.handler.graphql.VertxDataFetcher({ environment, future ->

  def routingContext = environment.getContext()

  def user = routingContext.get("user")

  this.retrieveLinksPostedBy(user, future)

})
Note
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:

def dataFetcher = new io.vertx.ext.web.handler.graphql.VertxDataFetcher({ environment, future ->

  // User as custom context object
  def user = environment.getContext()

  this.retrieveLinksPostedBy(user, future)

})

def graphQL = this.setupGraphQLJava(dataFetcher)

// Customize the query context object when setting up the handler
def 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:

def builder = graphql.schema.idl.RuntimeWiring.newRuntimeWiring()

builder.wiringFactory(new graphql.schema.idl.WiringFactory())
Tip
VertxPropertyDataFetcher wraps a PropertyDataFetcher so you can still use it with domain objects.

Using dataloaders

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

First, you must configure the GraphQL object to use the DataLoaderDispatcherInstrumentation:

def dispatcherInstrumentation = new graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation()

def graphQL = graphql.GraphQL.newGraphQL(graphQLSchema).instrumentation(dispatcherInstrumentation).build()

Then create a batch loader:

def linksBatchLoader = { keys, environment ->

  return this.retrieveLinksFromBackend(keys, environment)

}
Tip
If you work with Vert.x callback-based APIs, you may use a VertxBatchLoader to simplify your code.

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

Code not translatable