MicroProfile GraphQL Support Now Available in Helidon MP

Tim Middleton
Helidon
Published in
7 min readDec 21, 2020

We are pleased to announce that Helidon MP version 2.2.0 now supports the MicroProfile GraphQL specification from the Eclipse Foundation.

From the MicroProfile (MP) GraphQL spec page:

GraphQL is an open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. GraphQL interprets strings from the client, and returns data in an understandable, predictable, pre-defined manner. GraphQL is an alternative, though not necessarily a replacement for REST.

GraphQL provides three types of data operations: query, mutation and subscription. The schema is the core of GraphQL and clearly defines the operations supported by the API, including input arguments and possible responses.

An example of a schema is shown below. It defines a fictitious task API which we’ll go through in detail later in the article.

type Mutation {
"Create a task with the given description"
createTask(description: String): Task
"Remove all completed tasks and return the tasks left"
deleteCompletedTasks: [Task]
"Delete a task and return the deleted task details"
deleteTask(id: String): Task
"Update a task"
updateTask(completed: Boolean,
description: String,
id: String): Task
}

type Query {
"Return a given task"
findTask(id: String): Task
"Query tasks and optionally specified only completed"
tasks(completed: Boolean): [Task]
}

type Task {
completed: Boolean!
createdAt: BigInteger!
description: String!
id: String!
}

"Built-in java.math.BigInteger"
scalar BigInteger

We can see from the schema that there are a number of mutations and queries defined. For example, the findTask query defines an input type of a String for the task id and returns a Task object. The task query defines an optional completed argument and returns an array of Task objects.

We will go through this example API in more detail below.

Note: If you are new to GraphQL, it may help to do some background reading at https://graphql.org/ before you continue.

The MicroProfile GraphQL Specification

Taken from https://github.com/eclipse/microprofile-graphql (1.0.3)

The intent of the MicroProfile GraphQL specification is to provide a “code-first” set of APIs that will enable users to quickly develop portable GraphQL-based applications in Java.

There are 2 main requirements for all implementations of this specification, namely:
1. Generate and make the GraphQL Schema available. This is done by looking at the annotations in the users code and must include all GraphQL Queries and Mutations as well as all entities as defined implicitly via the response type or argument(s) of Queries and Mutations.

2. Execute GraphQL requests. This will be in the form of either a Query or a Mutation. As a minimum the specification must support executing these requests via HTTP.

Note: The spec does not yet contain support for subscriptions.

At a high level the spec defines a number of annotations that can be used to develop this code-first approach:

  • GraphQLApi — marker annotation that identifies a CDI Bean as a GraphQL endpoint
  • Query — applies to a public method which allows a user to ask for all or specific fields on an object or collection of objects. They must be in a class annotated with the GraphQLApi annotation
  • Mutation — applies to a public method which allows a user to create or mutate entries. They must also be in a class annotated with the GraphQLApi annotation

Other related annotations include:

  • Type — defines a complex type (POJO) as an output type
  • Input — defines a complete type as an input type
  • Interface — defines a type as an interface

Helidon Support for the MP GraphQL Spec

Helidon version 2.2.0 supports and passes version 1.0.3 of the MicroProfile GraphQL Specification.

Include Dependencies

Before you begin you must include the Helidon BOM as described in the Managing Dependencies section in the Helidon documentation. Then, to use the implementation, you must also include the following GraphQL dependency in your project:

<dependency>
<groupId>io.helidon.microprofile.graphql</groupId>
<artifactId>helidon-microprofile-graphql-server</artifactId>
</dependency>

Note: There is also support in Helidon SE for GraphQL which requires manual creation of GraphQL schemas and bindings. Refer to the Helidon SE documentation for more information.

Once you have included the above dependency, you can start coding your GraphQL endpoints.

Coding the Example

Note: The example below is taken from the Helidon MP GraphQL example which can be found here.

Create Your API Class
Create a class and annotate with @GraphQLAPI and @ApplicationScoped. For this example we are going to store the Tasks in a ConcurrentHashMap, but look out for an upcoming article showing how to use Oracle Coherence as a backend.

@GraphQLApi
@ApplicationScoped
public class TaskApi {
// store the Tasks in a Map
private Map<String, Task> tasks = new ConcurrentHashMap<>();
...
}

Create an endpoint to create a new Task
The following method defines a mutation that will accept a description, create a new Task, and return the created Task.

@Mutation
@Description("Create a task with the given description")
public Task createTask(@Name("description") String description) {
if (description == null) {
throw new IllegalArgumentException(
"Description must be provided");
}
Task task = new Task(description);
tasks.put(task.getId(), task);
return task;
}

A few things to note about the above:

  1. @Description documents the endpoint and makes this available in the schema.
  2. @Name will ensure the parameter is named.
  3. The spec defines that when a checked exception is thrown, the exception message will be displayed. For unchecked exceptions, such as IllegalArgumentException, the message will be suppressed. This behavior can be changed, which we have done for the unchecked exception. See the META-INF/microprofile-config.propertiesin the example.

The automatically generated GraphQL Schema from this is show below:

type Mutation {
"Create a task with the given description"
createTask(description: String): Task
}

Create an endpoint to display Tasks

The following method defines a query that will display all Tasks with an optional Boolean to determine if we display completed tasks or not.

@Query
@Description("Query tasks and optionally specified only completed")
public Collection<Task> getTasks(@Name("completed")
Boolean completed) {
return tasks.values().stream()
.filter(task -> completed == null
|| task.isCompleted() == completed)
.collect(Collectors.toList());
}

Create an endpoint to return an individual Task

The following method defines a query that will return an individual Task given the task id.

@Query
@Description("Return a given task")
public Task findTask(@Name("id") String id)
throws TaskNotFoundException {
return Optional.ofNullable(tasks.get(id))
.orElseThrow(() ->
new TaskNotFoundException("Task not found " + id));
}

If the above method throws the TaskNotFoundException, the default behavior is to display the exception message.

The automatically generated GraphQL schema from the above two queries is shown below:

type Query {
"Return a given task"
findTask(id: String!): Task
"Query tasks and optionally specify only completed"
tasks(completed: Boolean): [Task]
}

We won’t go through all the methods in the example, but this example should give you a general idea.

Running the Example

To run the Task example, carry out the following:

Note: Ensure you have JDK11 or above.

  1. Clone the Helidon repository and build Helidon
git clone https://github.com/oracle/helidon.git
mvn clean install -DskipTests

2. Build and run the example

cd examples/microprofile/graphql
mvn clean install
java -jar target/helidon-examples-microprofile-graphql.jar

After the startup you should notice a few things in the logs (shortened for convenience)

... Discovered 1 annotated GraphQL API class... Server started on http://localhost:7001... You are using experimental features. These APIs may change, please follow changelog!
... Experimental feature: GraphQL (GraphQL)

The above shows that Helidon MP GraphQL found 1 class annotated with the GraphQLApi annotation, the server is started on port 7001, and that the current version of GraphQL is experimental.

3. Next, we will retrieve the schema. The GraphQL spec says that the schema must be available on http://host:port/graphql/schema.graphql

$ curl http://127.0.0.1:7001/graphql/schema.graphqltype Mutation {
"Create a task with the given description"
createTask(description: String): Task
"Remove all completed tasks and return the tasks left"
deleteCompletedTasks: [Task]
"Delete a task and return the deleted task details"
deleteTask(id: String): Task
"Update a task"
updateTask(completed: Boolean, description:
String, id: String): Task
}
type Query {
"Return a given task"
findTask(id: String): Task
"Query tasks and optionally specify only completed"
tasks(completed: Boolean): [Task]
}
type Task {
completed: Boolean!
createdAt: BigInteger!
description: String!
id: String!
}
"Custom: Built-in java.math.BigInteger"
scalar BigInteger

4. Call the createTask mutation via curl which returns the newly created Task object.

curl -X POST http://127.0.0.1:7001/graphql -d '{"query":"mutation createTask { createTask(description: \"Task Description 1\") { id description createdAt completed }}"}'{ 
"data":{"createTask":
{
"id":"0d4a8d",
"description":"Task Description 1",
"createdAt":1605501774877,
"completed":false
}
}

Using the GraphiQL UI

The GraphiQL UI, which provides a UI to execute GraphQL commands, is not included by default in Helidon’s MicroProfile GraphQL implementation. You can follow the guide below to incorporate the UI into a GraphQL application.

Note: The following is taken from the GraphQL MP example available here.

  1. Copy the contents in the sample index.html file from here into the file at examples/microprofile/graphql/src/main/resources/web/index.html
  2. Change the URL in the line fetch('https://my/graphql', { to http://127.0.0.1:7001/graphql
  3. Re-build Build and run the example using the instructions above.
  4. Access the GraphiQL UI via the following URL: http://127.0.0.1:7001/ui.
GraphiQL UI
GraphiQL UI

Finally, copy the following commands into the editor on the left and then use the Play button to run the individual mutations or queries.

# Fragment to allow shorcut to display all fields for a task
fragment task on Task {
id
description
createdAt
completed
}

# Create a task
mutation createTask {
createTask(description: "Task Description 1") {
...task
}
}

mutation createTaskWithoutDescription {
createTask {
...task
}
}

# Find all the tasks
query findAllTasks {
tasks {
...task
}
}

# Find a task
query findTask {
findTask(id: "251474") {
...task
}
}

# Find completed Tasks
query findCompletedTasks {
tasks(completed: true) {
...task
}
}

# Find outstanding Tasks
query findOutstandingTasks {
tasks(completed: false) {
...task
}
}

mutation updateTask {
updateTask(id: "251474" description:"New Description") {
...task
}
}

mutation completeTask {
updateTask(id: "251474" completed:true) {
...task
}
}

# Delete a task
mutation deleteTask {
deleteTask(id: "1f6ae5") {
...task
}
}

# Delete completed
mutation deleteCompleted {
deleteCompletedTasks {
...task
}
}

The META-INF/microprofile-config.properties contains the following to assist with running GraphiQL.

  1. server.static.classpath.context=/ui
  2. server.static.classpath.location=/web
  3. graphql.cors=Access-Control-Allow-Origin
  4. mp.graphql.exceptionsWhiteList=java.lang.IllegalArgumentException

The first two properties map /ui to resources/web so the index.html is displayed.

The third allows CORS so the GraphiQL interface can run

Normally unchecked exceptions will not be displayed, but the fourth property overrides this.

Where to From Here?

To learn more about GraphQL and Helidon, see the following:

--

--

Tim Middleton
Helidon
Writer for

Software Engineer, Composer and Arranger of Music.