Visiting Devoxx, a large developers community conference, is always fun. Enjoying interesting Java sessions, learning new things, meeting old friends and getting a newfound appreciation for my restroom at home, because there are no queue times there.
After the conference there are typically several sessions that trigger me. This year, one of those talks that struck me the most was the one about GraphQL, presented by Bojan Tomić. I was surprised by the strengths of this particular framework and I would like to show you why.
If you rather like to (re)watch some of the Devoxx sessions, you can do so at their Youtube channel.
It’s a library that allows you to create a custom query language for your REST API’s. One of its greatest strengths is that it allows the client to define which data he wants from the API. You’ve probably run into the situation where the mobile app needed a custom endpoint or logic because it needed less data than the existing endpoint. Well with GraphQL this is no longer an issue. The client will also be able to fetch related data in one go instead of calling multiple endpoints.
I will show you how to use GraphQL using a simple example. I’ve set up a basic project using Spring Boot with JPA and the H2 database. For the data model: let’s assume it’s software for a company that has a stock of products and needs to be able to order products to refill the stock.
+------------------+ +------------------+
| PRODUCT | | ORDER |
+------------------+ +------------------+
| | | |
| id | | id |
| | | |
| name | 1 +------------------+ 1 | reference |
| +-----+ | ORDER_ITEM | +-----+ |
| stock_quantity | | +------------------+ | | placed_date |
| | | n | | n | | |
| description | ------+ fk_product_id +-----+ | paid_date |
| | | | | |
| image_url | | fk_order_id | | |
| | | | | |
+------------------+ | quantity | +------------------+
| |
| price_per_unit |
| |
| |
| |
+------------------+
Our first step is to translate our data model into the GraphQL scheme. Let’s take a look at it:
schema {
query: Query
}
type Query {
product(id : ID) : [Product]
order(id : ID) : [Order]
}
type Product {
id: ID!
name: String
quantity: Int
description: String
imageUrl: String
}
type Order {
id: ID!
reference: String
placedDate: String
paidDate: String
orderItems: [OrderItem]
}
type OrderItem {
quantity: Int
unitPrice: Float
product: Product
}
We’ll start off by declaring our scheme type, which can consist of 3 root types:
In this demo we’ll only use query, for which we defined the type “Query“. Mutation is used to update our data and subscription is used to create reactive sources.
Type “Query” contains 2 queries, one to get all the products with an optional ID parameter and one to get all the orders, also with an optional ID parameter. These are our 2 queries where the client can fetch data from.
The “product” query can return a list of products and the “order” query can return a list of orders. The “Product” type is a simple mapping of the product object, but the “Order” type is more complex since it contains order fields, but also a list of “OrderItem”, which in turn contain a “Product” type.
Now that we have our scheme, we can start with setting up our GraphQL. This consists of 4 steps:
Let’s start with the datafetchers. In this example we have 2 queries (order and product), so we’ll have to define 2 datafetchers. The datafetcher will process any GraphQL parameters, retrieve the data, do the mapping if needed and then return the data.
It’s pretty straightforward for the order datafetcher as all the fields in the GraphQL scheme and the JPA entity line up nicely. So we just have to process the parameter and retrieve the data. The mapping is done by the framework.
DataFetcher orderDataFetcher = environment -> {
String id = environment.getArgument("id");
return id == null ? orderRepository.findAll() : orderRepository.findAllById(Collections.singletonList(Long.valueOf(id)));
};
For the product datafetcher however, we need to map the fields because the names of the GraphQL and JPA entity fields don’t match.
DataFetcher productDataFetcher = environment -> {
List productMapList = new ArrayList<>();
String id = environment.getArgument("id");
List products;
if ( id == null )
products = productRepository.findAll();
else
products = productRepository.findAllById(Collections.singletonList(Long.valueOf(id)));
for (Product product : products) {
Map objectMap = new HashMap<>();
objectMap.put("id", product.getId());
objectMap.put("name", product.getName());
objectMap.put("quantity", product.getStockQuantity());
objectMap.put("description", product.getDescription());
objectMap.put("imageUrl", product.getImageUrl());
productMapList.add(objectMap);
}
return productMapList;
};
Now we can initialize the GraphQL engine by loading the schema and wiring the datafetchers to each of our queries.
final ClassPathResource classPathResource = new ClassPathResource(schemaName);
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(classPathResource.getFile());
RuntimeWiring runtimeWiring = newRuntimeWiring()
.type(TypeRuntimeWiring.newTypeWiring("Query").dataFetcher("product", graphQLDataFetchers.productDataFetcher))
.type(TypeRuntimeWiring.newTypeWiring("Query").dataFetcher("order", graphQLDataFetchers.orderDataFetcher))
.build();
SchemaGenerator schemaGenerator = new SchemaGenerator();
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
graphQL = GraphQL.newGraphQL(graphQLSchema).build();
All we have left to do is to create a restcontroller that exposes the GraphQL engine to a rest endpoint.
@RequestMapping(value = "/graphql", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Map doQuery(@RequestParam("query") String query) {
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(query)
.build();
return graphQL.execute(executionInput).toSpecification();
}
Now our application is ready to process GraphQL queries through REST. So lets run through some query examples. We’ll start easy and just query all the products.
Note that the GraphQL query is passed as an URL query parameter because we have the endpoint behind a GET method. You can also implement this in a POST method and post the queries instead.
{
product {
id
name
quantity
}
}
{
"data": {
"product": [
{
"id": "1",
"name": "Cool Product X",
"quantity": 10
},
{
"id": "2",
"name": "Awesome Product Y",
"quantity": 20
},
{
"id": "3",
"name": "Designer Product Z",
"quantity": 30
}
]
}
}
Let’s do a query to get the order with id “1” and immediately list all the order items and corresponding products.
{
order(id:1) {
id
reference
placedDate
paidDate
orderItems {
quantity
unitPrice
product {
id
name
}
}
}
}
{
"data": {
"order": [
{
"id": "1",
"reference": "BE12345",
"placedDate": "2019-01-01",
"paidDate": null,
"orderItems": [
{
"quantity": 10,
"unitPrice": 100,
"product": {
"id": "1",
"name": "Cool Product X"
}
},
{
"quantity": 20,
"unitPrice": 200,
"product": {
"id": "2",
"name": "Awesome Product Y"
}
}
]
}
]
}
}
That concludes my introduction to GraphQL. There are a lot of basic things I haven’t covered in this project, for example
So my first (non-professional) contact with GraphQL was a good experience. Although, I had some concerns during this tiny project.
During this project I did have some concerns though:
The community is pretty active though, so I expect that clever solutions or extensions will be created for these issues.
The project is available for download here.
The references I used for the project and article:
Would you like to know more about this topic or start a discussion? Please contact us.
Backend
C4J is uw partner voor al uw backend oplossingen. Reken op onze uitgebreide kennis en ervaring voor al uw applicaties en systemen.
Bezoek c4j.be