Working with GraphQL

GraphQL, a wondrous framework you should try at least once.

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.

So what is GraphQL, anyway?

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.

A quick role-play

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

		
	

Step 1

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:

  • query (required)
  • mutation
  • subscription

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.

Step 2

Now that we have our scheme, we can start with setting up our GraphQL. This consists of 4 steps:

  • Loading the schema
  • Configuring the datafetchers
  • Wiring the schema to the datafetchers
  • Exposing the graphql through a rest API

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;
    };
		
	

Step 3

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"
                        }
                    }
                ]
            }
        ]
    }
}
				
			

Awesome framework, right?

That concludes my introduction to GraphQL. There are a lot of basic things I haven’t covered in this project, for example

  • mutations
  • creating custom scalars
  • subscriptions (reactive)

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 pro’s

  • Surprisingly easy to set up.
  • I was able to query through REST in no time.
  • It’s nice that clients can query the REST services bases on their own needs without any custom code at the backend.
  • Getting all the relevant data instead of having to do a lot of N+1 rest calls makes the clients life so much easier.

The con’s

  • Performance : If the client is malicious or too zealous in his querying, he could overload the database and decrease the performance of the DB and/or application. After some searching there are possibilities to reject queries based on complexity or on the depth of the query.
  • Authorization : With REST services you typically secure each endpoint with the necessary roles, but here we only have 1 endpoint. You can pass a context object to the datafetcher in which you could place the user and role information and check the proper permissions there. But I’d rather see a solution where you can define your permissions through other means.

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.

Meet C4J

Backend

C4J is uw partner voor al uw backend oplossingen. Reken op onze uitgebreide kennis en ervaring voor al uw applicaties en systemen.

Visit c4j.be