In previous posts, we saw an example of using GraphQL with Spring and Netflix’s DGS. Today, in this Example of GraphQL with Quarkus, we bring the same example, but it’s done entirely with Quarkus.
To accomplish this example, we will use Quarkus’ own libraries for persistence, such as Panache, and the GraphQL library for Quarkus, SmallRye, instead of Netflix’s DGS.
What is GraphQL?
GraphQL is a language created by Facebook that allows us to query and manipulate data for APIs. This method of making queries and obtaining responses enables us to retrieve only the fields we desire. This way, in extensive queries or responses with a lot of data, we can display only the necessary fields.
Hands On
For our example, we will use an in-memory relational database with H2. Our example will consist of three very basic entities: Bank, User, and Bank Account, where a user has one or more bank accounts in a bank, and a bank can have one or more bank accounts.
In our application, we will make use of Lombok to eliminate Java boilerplate.
Similar to how Spring Data provides JPA to simplify database control, in Quarkus, we use Panache. Through Hibernate, it offers the advantages of JPA and makes entity development easier. So, if you are already familiar with Spring Data, Panache shares many similarities, and understanding it should not require much effort.
You can use https://code.quarkus.io/ to generate the structure of your project.
Maven Dependencies for Using GraphQL in Quarkus
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-smallrye-graphql</artifactId> </dependency>
The previous dependency, “smallrye,” provides the necessary logic for integrating GraphQL into our application.
Remember, you can use the following command directly from your terminal if you’ve already created the project:
quarkus:add-extension -Dextensions="graphql"
Maven Dependencies for Using Persistence in Quarkus
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-jdbc-h2</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-hibernate-orm-panache</artifactId> </dependency>
As we mentioned earlier, it’s necessary to add the Panache dependency to provide persistence to our application.
GraphQL Schema and Domain Entities
In our previous example created with Netflix’s DGS and Spring Boot, we had to create and add our GraphQL schemas to the resources folder. But in this case with Quarkus and the SmallRye library, there’s no need to create the schemas, only the model. So, in this case, we will only create the domain objects to demonstrate how it works. Although this is not a good structure, it serves to make the example more understandable. This could be a typical use case for the Hexagonal Architecture since we’ve changed the technology, but the logic remains the same.
Creating the User Entity
For creating the entity, we make use of Lombok and javax annotations.
@Entity @AllArgsConstructor @NoArgsConstructor @Getter @Setter public class User { @Id @GeneratedValue private UUID id; private String firstName; private String lastName; private int age; private String address; private String country; private String city; @OneToMany(mappedBy = "user") private Set<Account> accounts; @ManyToOne() private Bank bank; }
Creation of the Bank Entity
@Entity @Data @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode public class Bank { @Id @GeneratedValue private UUID id; private String name; private String country; @OneToMany(mappedBy = "bank") private Set<User> users; @OneToMany(mappedBy = "bank") private Set<Account> accounts; }
Construction of the Account Entity
@Entity @AllArgsConstructor @NoArgsConstructor @Getter @Setter public class Account { @Id @GeneratedValue private UUID id; private String name; private String alias; private BigDecimal amount; @ManyToOne(fetch = FetchType.LAZY) private User user; @ManyToOne(fetch = FetchType.LAZY) private Bank bank; }
Creation of Mutation or Input Objects
When working with GraphQL, we will define a series of objects as input to our application. In this case, we will define a set of objects that will be stored in the database. In other words, we need input data for our application. The data defined below will be the data that gets stored.
@AllArgsConstructor @NoArgsConstructor @Getter @Setter public class UserInput { private String firstName; private String lastName; private int age; private String address; private String country; private String city; }
@Getter @Setter @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode public class BankInput { private String name; private String country; }
@AllArgsConstructor @NoArgsConstructor @Getter @Setter public class AccountInput { private String name; private String alias; private BigDecimal amount; private UUID userId; private UUID bankId; }
Once we have finished all the domain objects and the input, it’s time to create the repository layer, which will be implemented with PanacheRepository.
Creation of Repositories with PanacheRepository
By using PanacheRepository, we will create the Repository layer. This interface implements query and save methods.
@ApplicationScoped public class UserRepository implements PanacheRepository<User> { }
@ApplicationScoped public class BankRepository implements PanacheRepository<Bank> { }
@ApplicationScoped public class AccountRepository implements PanacheRepository<Organization> { }
Queries and Mutations with GraphQL API in Quarkus
Once we have finished creating the entities and the repository, it’s time to add the necessary logic to perform queries and mutations with GraphQL in Quarkus. To do this, we will make use of the following annotations:
- @GraphQLApi: This annotation at the beginning of the class indicates that it will be a GraphQL endpoint bean.
- @Query: Indicates that this method will be a query.
- @Mutation: Added to a method indicates that it’s a mutation request.
- @Name: This annotation will be used for methods that have input parameters.
GraphQL Query in Quarkus
Next, we will see how to perform queries for the “account” schema.
@GraphQLApi @RequiredArgsConstructor public class AccountQuery { private final AccountRepository repository; @Query("accounts") public List<Account> findAll() { return repository.findAll().stream().collect(Collectors.toList()); } @Query("account") public Account findById(@Name("id") Long id) { return repository.findById(id); } }
This class, in which we will perform two different queries, is marked with @GraphQLApi to indicate that it is a GraphQL Query class. Then, in the methods responsible for making the query, we have added the @Query annotation with the name of the query.
Mutation in GraphQL with Quarkus
The Mutation type in GraphQL is responsible for performing a save or a request that can change the state, similar to what would be a POST, PATCH, or PUT request in REST.
To perform mutations in GraphQL, we will add the @GraphQLApi annotation to the class. In each method responsible for performing a mutation, we will add @Mutation(“name”). Let’s see an example:
@GraphQLApi @RequiredArgsConstructor public class AccountMutation { private final AccountRepository accountRepository; private final BankRepository bankRepository; private final UserRepository userRepository; @Mutation("createAccount") @Transactional public Account createAccount(@Name("account") AccountInput account) { User user = userRepository.findById(account.getUserId()); Bank bank = bankRepository.findById(account.getBankId()); var accountToSave = new Account(null, account.getName(), account.getAlias(), account.getAmount(), user, bank); accountRepository.persistAndFlush(accountToSave); return accountToSave; } }
It’s important to note that in the method, we have added @Name because we are adding an input, meaning we want to save a new account.
Testing GraphQL with Quarkus
Once we have generated our code, the first thing we will do is run our Quarkus application.
mvn compile quarkus:dev
If everything is working correctly, we should see something like this:
__ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2021-05-08 16:42:23,139 INFO [io.quarkus] (Quarkus Main Thread) graphql-quarkus 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.13.3.Final) started in 1.023s. Listening on: http://localhost:8080 2021-05-08 16:42:23,140 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated. 2021-05-08 16:42:23,141 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-h2, mutiny, narayana-jta, smallrye-context-propagation, smallrye-graphql] 2021-05-08 16:42:23,141 INFO [io.qua.dep.dev.RuntimeUpdatesProcessor] (vert.x-worker-thread-7) Hot replace total time: 2.384s
GraphQL with Quarkus starts at the endpoint http://localhost:8080/q/graphql-ui/. If we access that endpoint, we will see the GraphQL interface for making queries.
Let’s run a few queries to see how it works. We’ll start with a mutation because it’s necessary to save a bank first.
mutation CREATE { createBank(bank: {name: "Santander", country:"Spain"}) { id name country } }
Once we’ve saved a bank, we’ll make a request to retrieve all of them:
{ banks{ id name country } }
With the previous query, we will display all the banks in our database, which in our case is just one. The fields that will appear in the search will be those specified in the query, in this case, we will display id, name, and country.
and finally, let’s try to search for a bank by id.
{ bank(id:1){ id name country } }
Now, we’re going to run the same query again, but with fewer fields in the request.
{ bank(id:1){ id } }
In the previous query, only the ID field is included, so the response will contain only that field.
As mentioned near the beginning of the article, when using the smallrye library, GraphQL schemas are automatically generated without the need for manual implementation. If you want to view the generated schemas, you can access the endpoint http://localhost:8080/graphql/schema.graphql for the generated types.
Conclusion of GraphQL Example with Quarkus
Just like the Netflix DGS library, the SmallRye library for Quarkus provides an API for using GraphQL in Quarkus through annotations.
GraphQL has emerged as a good alternative for all applications and architectures that rely on REST services, bringing many improvements and conveniences to any API. We’ve seen some of these enhancements in our example. When combined with a framework like Quarkus, designed for cloud environments, it makes for a perfect combination to create a solid API.
If you want to see the complete example, you can find it on our GitHub.