Categories
graphql Java Quarkus

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

  1. Create endpoint to get all teams + create an endpoint to get a single team
  2. Create an endpoint that will give you all users of a team.
  3. Alternatively, you can use ORM to return teams with users in them as a one large result
  4. 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.

If you’re looking for Microprofile GraphQL(the one with annotation support) please follow to official Quarkus guide.

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":{}}'
Here’s how GraphQL query looks in Postman

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":{}}'
Example of Postman GraphQL Query excluding Programmers team and querying only user name

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.

Weekly Quarkus Newsletter

Our newsletter saves your precious time with curated list of top news, guides and articles of the week

Marketing by

Leave a Reply

Connect with




Your email address will not be published. Required fields are marked *