<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.vertx.howtos</groupId>
<artifactId>graphql-howto</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<vertx.version>4.4.0</vertx.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-graphql</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.5.0</version>
<configuration>
<mainClass>io.vertx.howtos.graphql.GraphQLVerticle</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
Implementing a GraphQL server
This document will show you how to implement a GraphQL server.
What you will build
You will build a GraphQL server to manage your personal tasks (a.k.a. the todo list).
The application consists in a few files:
-
the
tasks.graphqls
schema file -
the
Task
data class -
the
GraphQLVerticle
class
Create a project
The code of this project contains Maven and Gradle build files that are functionally equivalent.
Using Maven
Here is the content of the pom.xml
file you should be using:
pom.xml
Using Gradle
Assuming you use Gradle with the Kotlin DSL, here is what your build.gradle.kts
file should look like:
build.gradle.kts
plugins {
java
application
}
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
dependencies {
implementation(platform("io.vertx:vertx-stack-depchain:4.4.0"))
implementation("io.vertx:vertx-web-graphql")
}
application {
mainClass.set("io.vertx.howtos.graphql.GraphQLVerticle")
}
tasks.wrapper {
gradleVersion = "7.6"
}
Managing tasks with Vert.x Web and GraphQL
Creating the schema
First things first, let’s create a GraphQL schema for the task management app:
src/main/resources/tasks.graphqls
type Task {
id: String
description: String
completed: Boolean
}
type Query {
allTasks(uncompletedOnly: Boolean = true): [Task]
}
type Mutation {
complete(id: String!): Boolean
}
The schema defines:
-
the
Task
type with 3 fields:id
,description
andcompleted
-
the
allTasks
query that returns an array of tasks and optionally takes a parameteruncompletedOnly
(defaults totrue
) -
the
complete
mutation that takes the taskid
as parameter and returns a boolean indicating whether the operation was successful
Implementing the server
In the application, tasks will be modeled by the Task
class:
src/main/java/io/vertx/howtos/graphql/Task.java
package io.vertx.howtos.graphql;
import java.util.UUID;
public record Task(String id, String description, boolean completed) {
public Task(String description) {
this(UUID.randomUUID().toString(), description, false); (1)
}
}
1 | a random identifier is assigned when a Task instance is created |
The Task class field names must match those of the corresponding GraphQL schema type. |
On startup, we shall create a few items:
src/main/java/io/vertx/howtos/graphql/GraphQLVerticle.java
private Map<String, Task> initData() {
return Stream.of(
new Task("Learn GraphQL"),
new Task("Build awesome GraphQL server"),
new Task("Profit")
).collect(toMap(Task::id, identity()));
}
Then the GraphQL-Java engine must be setup:
src/main/java/io/vertx/howtos/graphql/GraphQLVerticle.java
private GraphQL setupGraphQL() {
var schema = vertx.fileSystem().readFileBlocking("tasks.graphqls").toString(); (1)
var schemaParser = new SchemaParser();
var typeDefinitionRegistry = schemaParser.parse(schema); (2)
var runtimeWiring = newRuntimeWiring() (3)
.type("Query", builder -> builder.dataFetcher("allTasks", this::allTasks))
.type("Mutation", builder -> builder.dataFetcher("complete", this::complete))
.build();
var schemaGenerator = new SchemaGenerator();
var graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring); (4)
return GraphQL.newGraphQL(graphQLSchema).build(); (5)
}
1 | Read the schema file from classpath |
2 | TypeDefinitionRegistry is the GraphQL-Java runtime equivalent to the schema file definitions |
3 | RuntimeWiring tells GraphQL-Java how to resolve types and fetch data |
4 | GraphQLSchema connects the runtime type definitions with the type resolvers and data fetchers |
5 | Create the application’s GraphQL engine |
So far, so good. Now how does one implement a data fetcher?
src/main/java/io/vertx/howtos/graphql/GraphQLVerticle.java
private List<Task> allTasks(DataFetchingEnvironment env) {
boolean uncompletedOnly = env.getArgument("uncompletedOnly");
return tasks.values().stream()
.filter(task -> !uncompletedOnly || !task.completed())
.collect(toList());
}
The allTasks
data fetcher gets the uncompletedOnly
parameter from the DataFetchingEnvironment
. Its value is either provided by the client or, as defined in the schema file, set to true
.
Do not block the Vert.x event loop in your data fetchers. In this how-to, the data set is small and comes from memory, so it’s safe to implement allTasks in a blocking fashion. When working with databases, caches or web services, make sure your data fetcher returns a CompletionStage . For further details, please refer to the The Vert.x Web GraphQL Handler documentation. |
The code for the complete
mutation is not much different:
src/main/java/io/vertx/howtos/graphql/GraphQLVerticle.java
private boolean complete(DataFetchingEnvironment env) {
String id = env.getArgument("id");
var task = tasks.get(id);
if (task == null) {
return false;
}
tasks.put(id, new Task(task.id(), task.description(), true));
return true;
}
It gets the id
parameter provided by the client, then looks up for the corresponding task. If a task is found, it is updated and the mutation returns true
to indicate success.
Almost there! Now let’s put things together in the verticle start
method:
src/main/java/io/vertx/howtos/graphql/GraphQLVerticle.java
private Map<String, Task> tasks;
@Override
public void start() {
tasks = initData();
var graphQL = setupGraphQL();
var graphQLHandler = GraphQLHandler.create(graphQL); (1)
var router = Router.router(vertx);
router.route().handler(BodyHandler.create()); (2)
router.route("/graphql").handler(graphQLHandler); (3)
vertx.createHttpServer()
.requestHandler(router)
.listen(8080);
}
1 | Create Vert.x Web GraphQL handler for the GraphQL-Java object |
2 | Define a catch-all Vert.x Web route and set the BodyHandler (required to handle POST requests bodies) |
3 | Define a Vert.x Web route for GraphQL queries and set the GraphQL handler |
Running the application
The GraphQLVerticle
needs a main
method:
src/main/java/io/vertx/howtos/graphql/GraphQLVerticle.java
public static void main(String[] args) {
var vertx = Vertx.vertx(); (1)
vertx.deployVerticle(new GraphQLVerticle()).onComplete(ar -> { (2)
if (ar.succeeded()) {
System.out.println("Verticle deployed");
} else {
ar.cause().printStackTrace();
}
});
}
1 | Create a Vertx context |
2 | Deploy GraphQLVerticle |
Then you can run the application:
-
straight from your IDE or,
-
with Maven:
mvn compile exec:java
, or -
with Gradle:
./gradlew run
(Linux, macOS) orgradlew run
(Windows).
The following examples use the HTTPie command line HTTP client. Please refer to the installation documentation if you don’t have it installed on your system yet.
Listing uncompleted tasks
To list uncompleted tasks, open your terminal and execute this:
http :8080/graphql query='
query {
allTasks {
id,
description
}
}'
You should see something like:
{
"data": {
"allTasks": [
{
"description": "Learn GraphQL",
"id": "4a9f53fd-584f-4169-b725-6b320043db8b"
},
{
"description": "Profit",
"id": "03770db5-a8ad-44b3-ad6e-6fe979015088"
},
{
"description": "Build awesome GraphQL server",
"id": "6b000f72-8aa9-4f4e-9539-5da2ab11cd94"
}
]
}
}
Completing a task
To complete the Learn GraphQL task:
http :8080/graphql query='
mutation {
complete(id: "4a9f53fd-584f-4169-b725-6b320043db8b")
}'
The id used in the query above must be changed to the value generated by your application. |
After the task is completed, you will see:
{
"data": {
"complete": true
}
}
Retrieving all tasks
If you need to retrieve all tasks, including those already completed, make sure to set the uncompletedOnly
parameter to false
in the allTasks
query:
http :8080/graphql query='
query {
allTasks(uncompletedOnly: false) {
id
description
completed
}
}'
The expected output is:
{
"data": {
"allTasks": [
{
"completed": true,
"description": "Learn GraphQL",
"id": "4a9f53fd-584f-4169-b725-6b320043db8b"
},
{
"completed": false,
"description": "Profit",
"id": "03770db5-a8ad-44b3-ad6e-6fe979015088"
},
{
"completed": false,
"description": "Build awesome GraphQL server",
"id": "6b000f72-8aa9-4f4e-9539-5da2ab11cd94"
}
]
}
}