How to use GraphQL with Quarkus
GraphQL is both a language and a tool that can simplify your API and save some development hours to your project because you don’t need to create different endpoints for a single data structure. With Quarkus, it can be done really easily with vertx extension.
Let’s start with a simple example. You have a simple service that returns project teams and users for them. You’ll need:
- Create endpoint to get all teams + create an endpoint to get a single team
- Create an endpoint that will give you all users of a team.
- Alternatively, you can use ORM to return teams with users in them as a one large result
- An endpoint that will only return teams with users, but users only have a name.
I included #4 because you might have some really large object(imagine that user has 200 fields, and you return thousands of them. And your clients only need their names to display in a search result.
For better copy-paste experience go to Quarkify website
Easy with GraphQL
This all can be done fairly easily with GraphQL. You define the schema, share it with clients, and they already decide how to deal with it and which fields they want. Let’s start with simple schema:
type User {
id: Long
name: String
}
type Team {
id: Long
name: String
users: [User]
}
type Query {
allTeams(excluding: String = ""): [Team]
}
Briefly, we have User
type with id
and name
, we also have Team
the type that has the same id, name, and additionally users, which is an array of User
. We also have Query
type, where we have methods that clients can execute, specifically allTeams(excluding)
which will return an array of Team
.
Quarkus implementation
A working github repo example can be found here
Firstly, let’s add single dependency that will make all this work.
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-graphql</artifactId>
</dependency>
This will be more than enough to start working with GraphQL. Now we also need super simple Team and User data classes.
Let’s also put our GraphQL file as teams.graphql
into src/main/resources
folder.
Now that it’s there, let’s create GraphQlConfig
class.
@ApplicationScoped
public class GraphQlConfig {
public static final List TEAMS = new ArrayList<>() {{
add(new Team(1L, "Programmers",
new User(1L, "Dmytro"),
new User(2L, "Alex")
));
add(new Team(2L, "Machine Learning",
new User(3L, "Andrew NG")
));
}};
//TODO
}
Nice, we now have our fake data, two teams and each team has it’s own users.
Now let’s register our future GraphQL implementation from in Vertx Router
.
public void init(@Observes Router router) throws Exception {
router.route("/graphql").blockingHandler(GraphQLHandler.create(createGraphQL()));
}
This function will register our GraphQL
instance and bind it to /graphql
route. We're missing createGraphQL()
method, let's write it.
What’s going on? Keep calm, let’s look at each line
TypeDefinitionRegistry teamsSchema = getTeamSchema();
This method will just read teams.graphql
file and parse with with SchemaParser
, here's how it's implemented:
private TypeDefinitionRegistry getTeamSchema() throws Exception {
final URL resource = this.getClass().getClassLoader().getResource("teams.graphql");
String schema = String.join("\n", Files.readAllLines(Paths.get(resource.toURI())));
return new SchemaParser().parse(schema);
}
We use ClassLoader
to get URL from resources and then read it into String
. Okay, let's go back to our createGraphQL()
method.
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query",
builder -> builder.dataFetcher("allTeams", new VertxDataFetcher<>(this::getAllTeams))
).build();
This is our cutting point where we bind java methods with our schema, so that Vertx knows what and where to return. Specifically, we bind allTeams
with list of Team
objects. And as input, we use VertxDataFetch
with the getAllTeams
method reference(which we will write in a second)
SchemaGenerator schemaGenerator = new SchemaGenerator();
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(teamsSchema, runtimeWiring);
return GraphQL.newGraphQL(graphQLSchema).build();
This is our final step, we bind teamsSchema
with our runtimeWiring
, and build final GraphQL
object that we pass to our Router
in a previous init()
method.
That’s it, we read schema, map schema queries or methods to real java methods, and then build GraphQL
object that we later pass to Router
. Let's see final method that we passed to VertxDataFetcher
.
private void getAllTeams(DataFetchingEnvironment env, Promise<List<Team>> future) {
final String excluding = env.getArgument("excluding");
future.complete(
TEAMS.stream()
.filter(it -> !it.name.equals(excluding))
.collect(Collectors.toList())
);
}
This method has two parameters, DataFetchingEnvironment
and Promise
with output. From the environment, you can take any info from the request, in our case we have env.getArgument("excluding")
, because we specified that query can have this argument. We then filter our TEAMS
object with this argument, and pass it into promse.
Here’s full code of GraphQlConfig
:
That’s all that we need to have. Let’s start our dev server with ./mvnw quarkus:dev
and query some of the data. Firstly, let's get all the available data that we have
curl --location --request POST 'http://localhost:8080/graphql' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"query {\n allTeams{\n id\n name\n users {\n id\n name\n }\n }\n} ","variables":{}}'
As you might expect, you’ll get all the teams that we have, as well as all the users that they have.
{
"data": {
"allTeams": [
{
"id": 1,
"name": "Programmers",
"users": [
{
"id": 1,
"name": "Dmytro"
},
...
]
},
{
"id": 2,
"name": "Machine Learning",
...
}
Let’s filter out our Test team, for this you need to specify our excluding
attribute. Additionally, let's query only names of a users. Here's how you can accomplish it:
curl --location --request POST 'http://localhost:8080/graphql' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"query {\n allTeams(excluding: \"Programmers\"){\n id\n name\n users {\n name\n }\n }\n} ","variables":{}}'
This time we’ll get only one group called Machine Learning
, and you won't see id of a user.
{
"data": {
"allTeams": [
{
"id": 2,
"name": "Machine Learning",
"users": [
{
"name": "Andrew NG"
}
]
}
]
}
}
In conclusion
There’s plenty of room for improvement, but you can see how GraphQL can simplify the development of both Backend and Frontend developers. Backend developers don’t need to think about what data user needs to get(of course excluding security use cases, in this case, you can get plenty of info from DataFetchingEnvironment
).
GraphQL can help you not only in terms of saved development hours, but also in terms of optimizing performance, since you can strip out unnecessary fields before you start to transfer them via the network.
What do you think can be improved in example above? I’m writing next part of this article about communication of GraphQL with reactive PostgreSQL, and I would love to hear what topic might interest you.
Originally published at https://quarkify.net on April 29, 2020.