Let’s talk about GraphQL and Intershop Commerce Management. Have you heard of GraphQL? What is it all about?
For me it is about the focus on the client as well as the focus on performance and ease of development. Ultimately, it’s all about a better way to communicate with Intershop Commerce Management.
We will first take a look at GraphQL using an example and compare it with the corresponding “classical” REST requests, followed by a closer look at some of the issues related to REST and how GraphQL solves them.
In a follow-up article we will show you how Intershop’s GraphQL interface can be used for complex queries and give you some insights into how it works and how it can be used. Although it works out of the box, it can be easily customized to provide even better support for your business.
A Short Introduction Using an Example
So how does GraphQL work? Let’s take a typical real-life example from our REST-API: We want to retrieve information on a certain product. Typically, we use this kind of OpenAPI schema:
/products/{productKey}:
get:
tags:
- Product
summary: Returns details of a product
description: |
This operation retrieves information of one product, usually in order to render a product details page.
operationId: getProductSingle
parameters:
- name: productKey
in: path
description: The key or UUID to resolve a single item
required: true
schema:
type: string
example: ExampleKey
responses:
200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ProductRO'
text/xml:
schema:
$ref: '#/components/schemas/ProductRO'
404:
description: Not found
This operation returns an object of type ProductRO, which looks similar to this:
ProductRO:
type: object
properties:
sku:
type: string
productName:
type: string
shortDescription:
type: string
availability:
type: boolean
listPrice:
$ref: '#/components/schemas/ProductPriceRO'
This is obviously shortened, but you get the idea. If we want to do the same thing in GraphQL, the first thing to consider is the “schema”. In our case, it looks like this:
type Product {
sku: String!
productName: String!
availability: Boolean!
shortDescription: String!
listPrice: ProductPrice!
}
type ProductPrice {
currency: String!
currencyMnemonic: String!
locale: ProductPrice_locale!
name: String!
priceText: String!
type: String!
value: Float!
}
type Query {
"""
from /products/{productKey} (operation ID getProductSingle)
This operation retrieves information of one product, usually in order to render a product details page.
"""
Product(productKey: String!): Product!
}
Okay, so how do we make requests for this? The requests follow the GraphQL schema very closely:

While the GraphQL response is relatively compact and straightforward, the corresponding REST response is much larger. The GET request https://servername.com/INTERSHOP/rest/WFS/inSPIRED-inTRONICS-Site/-/products/4729529
yields the following:
REST response - CLICK HERE to see the full response
{
"name": "Toshiba Camileo SX500",
"type": "Product",
"attributes": [
{
"name": "Display diagonal",
"type": "String",
"value": "68.6 mm (2.7 \")"
},
{
"name": "Video formats supported",
"type": "String",
"value": "MPEG4 (H.264)"
},
{
"name": "Battery type",
"type": "String",
"value": "PX1686"
},
{
"name": "ISO sensitivity",
"type": "String",
"value": "1600"
},
{
"name": "Camcorder tape type",
"type": "String",
"value": "Flash memory"
},
{
"name": "Compatible operating systems",
"type": "String",
"value": "Windows XP SP3/Vista/Windows 7"
},
{
"name": "Weight",
"type": "ResourceAttribute",
"value": {
"type": "Quantity",
"value": 162,
"unit": "g"
}
},
{
"name": "Normal focusing range",
"type": "String",
"value": "0.1 -?"
},
{
"name": "Manual focus",
"type": "String",
"value": "Y"
},
{
"name": "Maximum video resolution",
"type": "String",
"value": "1920 x 1080 pixels"
},
{
"name": "Maximum memory card size",
"type": "String",
"value": "64 GB"
},
{
"name": "Battery life (max)",
"type": "String",
"value": "1 h"
},
{
"name": "Battery technology",
"type": "String",
"value": "Lithium-Ion"
},
{
"name": "Minimum system requirements",
"type": "String",
"value": "DVD-ROM, 1x USB"
},
{
"name": "Flash",
"type": "String",
"value": "LED"
},
{
"name": "Colour_of_product",
"type": "String",
"value": "Black"
},
{
"name": "Image formats supported",
"type": "String",
"value": "JPG"
},
{
"name": "USB 2.0 ports quantity",
"type": "String",
"value": "1"
},
{
"name": "Display",
"type": "String",
"value": "LCD"
},
{
"name": "Width",
"type": "ResourceAttribute",
"value": {
"type": "Quantity",
"value": 37.5,
"unit": "mm"
}
},
{
"name": "Minimum processor",
"type": "String",
"value": "Intel Core Duo E440 2.0 GHz+"
},
{
"name": "Minimum hard disk space",
"type": "String",
"value": "1024 MB"
},
{
"name": "Minimum RAM",
"type": "String",
"value": "1024 MB"
},
{
"name": "Optical zoom",
"type": "String",
"value": "5 x"
},
{
"name": "Digital zoom",
"type": "String",
"value": "12 x"
},
{
"name": "Built-in flash",
"type": "String",
"value": "Y"
},
{
"name": "PictBridge",
"type": "String",
"value": "Y"
},
{
"name": "Compatible memory cards",
"type": "String",
"value": "SD,SDHC,SDXC"
},
{
"name": "Total megapixels",
"type": "String",
"value": "12 MP"
},
{
"name": "Light exposure control",
"type": "String",
"value": "+- 1.8 EV"
},
{
"name": "Auto focus",
"type": "String",
"value": "Y"
},
{
"name": "Composite video out",
"type": "String",
"value": "Y"
},
{
"name": "Depth",
"type": "ResourceAttribute",
"value": {
"type": "Quantity",
"value": 109.5,
"unit": "mm"
}
},
{
"name": "Self-timer",
"type": "String",
"value": "2/10 s"
},
{
"name": "Image stabilizer",
"type": "String",
"value": "Y"
},
{
"name": "Sensor type",
"type": "String",
"value": "CMOS"
},
{
"name": "Minimum illumination",
"type": "String",
"value": "4 lx"
},
{
"name": "Source data-sheet",
"type": "String",
"value": "Icecat.biz"
},
{
"name": "Still image resolutions",
"type": "String",
"value": "4000 x 3000/3648 x 2736/1600 x 1200/640 x 480/4480 x 2520/3648 x 2056/1920 x 1080/1280 x 720/3648 x 2736"
},
{
"name": "Focal length",
"type": "String",
"value": "6.8 - 34 mm"
},
{
"name": "HDMI ports quantity",
"type": "String",
"value": "1"
},
{
"name": "Analog signal format system",
"type": "String",
"value": "NTSC/PAL"
},
{
"name": "Height",
"type": "ResourceAttribute",
"value": {
"type": "Quantity",
"value": 54.8,
"unit": "mm"
}
}
],
"sku": "4729529",
"productName": "Toshiba Camileo SX500",
"shortDescription": "Camileo SX500 - 10MP CMOS, 6.858 cm (2.7 \") , 16:9, HDMI, black",
"longDescription": "Never mind shady places, low illuminated locations or any other twighlight circumstances with your SX500 camcorder: the low light (4 lux) filming capacity will handle it. Discover the extraordinary, capture it your way!<br/><br/><b>Main Features</b><br/>- High definition movie recording<br/>- High resolution still images<br/>- 5 x optical zoom lens<br/>- Wide LCD screen<br/>- Low light (4 lux) filming capacity<br/>- SD/SDHC/SDXC card slot<br/>- Face Detect",
"productTypes": [
"PRODUCT"
],
"availability": true,
"retailSet": false,
"inStock": true,
"availableStock": 0,
"productMaster": false,
"mastered": false,
"roundedAverageRating": "4.5",
"readyForShipmentMin": 3,
"readyForShipmentMax": 7,
"minOrderQuantity": 1,
"productBundle": false,
"manufacturer": "Toshiba",
"listPrice": {
"type": "ProductPrice",
"value": 178.35,
"currencyMnemonic": "USD",
"currency": "USD"
},
"salePrice": {
"type": "ProductPrice",
"value": 178.35,
"currencyMnemonic": "USD",
"currency": "USD"
},
"maxOrderQuantity": 0,
"stepOrderQuantity": 1,
"packingUnit": "",
"reviews": "inSPIRED-inTRONICS-Site/-/products/4729529/reviews",
"shippingMethods": [
{
"name": "Saturday Delivery",
"type": "ShippingMethod",
"id": "SATURDAY",
"shippingTimeMin": 0,
"shippingTimeMax": 6
},
{
"name": "2-Business Day",
"type": "ShippingMethod",
"id": "STD_2DAY",
"shippingTimeMin": 1,
"shippingTimeMax": 2
},
{
"name": "5-Business Day",
"type": "ShippingMethod",
"id": "STD_5DAY",
"shippingTimeMin": 2,
"shippingTimeMax": 5
},
{
"name": "Standard Ground",
"type": "ShippingMethod",
"id": "STD_GROUND",
"shippingTimeMin": 3,
"shippingTimeMax": 7
}
],
"availableGiftWraps": [
{
"type": "Link",
"uri": "inSPIRED-inTRONICS-Site/-/products/GiftWrapPinkFlowers",
"title": "Gift Wrap Pink Flowers",
"description": "Gift Wrap with a beautiful pink floral pattern"
},
{
"type": "Link",
"uri": "inSPIRED-inTRONICS-Site/-/products/GiftWrapNature",
"title": "Gift Wrap Nature",
"description": "Wrapping paper with ornamental pattern."
},
{
"type": "Link",
"uri": "inSPIRED-inTRONICS-Site/-/products/GiftWrapPhantasy",
"title": "Gift Wrap Phantasy",
"description": "Gift Wrap with phantasy pattern."
}
],
"availableGiftMessages": [
{
"type": "Link",
"uri": "inSPIRED-inTRONICS-Site/-/products/GiftMessagePink",
"title": "Gift Message Pink",
"description": "A gift message card."
},
{
"type": "Link",
"uri": "inSPIRED-inTRONICS-Site/-/products/GiftMessageBlue",
"title": "Gift Message Blue",
"description": "A blue gift card message."
},
{
"type": "Link",
"uri": "inSPIRED-inTRONICS-Site/-/products/GiftMessageGreen",
"title": "Gift Message Green",
"description": "A green message card."
}
],
"images": [
{
"name": "front S",
"type": "Image",
"effectiveUrl": "/INTERSHOP/static/WFS/inSPIRED-inTRONICS-Site/-/inSPIRED/en_US/S/4729529-1157.jpg",
"viewID": "front",
"typeID": "S",
"imageActualHeight": 110,
"imageActualWidth": 110,
"primaryImage": true
},
{
"name": "front L",
"type": "Image",
"effectiveUrl": "/INTERSHOP/static/WFS/inSPIRED-inTRONICS-Site/-/inSPIRED/en_US/L/4729529-1157.jpg",
"viewID": "front",
"typeID": "L",
"imageActualHeight": 500,
"imageActualWidth": 500,
"primaryImage": true
},
{
"name": "front M",
"type": "Image",
"effectiveUrl": "/INTERSHOP/static/WFS/inSPIRED-inTRONICS-Site/-/inSPIRED/en_US/M/4729529-1157.jpg",
"viewID": "front",
"typeID": "M",
"imageActualHeight": 270,
"imageActualWidth": 270,
"primaryImage": true
}
],
"defaultCategory": {
"name": "Camcorders",
"type": "DefaultCategory",
"id": "584",
"categoryPath": [
{
"name": "Cameras",
"type": "CategoryPath",
"id": "Cameras-Camcorders",
"uri": "inSPIRED-inTRONICS-Site/-/categories/Cameras-Camcorders"
},
{
"name": "Camcorders",
"type": "CategoryPath",
"id": "584",
"uri": "inSPIRED-inTRONICS-Site/-/categories/Cameras-Camcorders/584"
}
],
"uri": "inSPIRED-inTRONICS-Site/-/categories/Cameras-Camcorders/584"
},
"attributeGroups": {},
"seoAttributes": {
"metaTitle": "Toshiba Camileo SX500 - Camcorders favorable buying at our shop",
"metaDescription": "Toshiba Camileo SX500 - Camileo SX500 - 10MP CMOS, 6.858 cm (2.7 \") , 16:9, HDMI, black",
"robots": [
"index",
"follow"
]
},
"numberOfReviews": 2,
"supplierSKU": "4729529"
}
You can see one important difference: REST returns everything, even if you don’t want it. With GraphQL you explicitly ask for what you need, and GraphQL adapts to your requests. Keep in mind that this is not some made-up scenario but an actual Intershop REST API request.
Although the example above is simplified, it is quite clear that using REST would provide you with too much information – information that costs resources to look up and bandwidth to transport.
An In-Depth Look: REST vs GraphQL
However, it turned out that REST APIs are often inflexible, that customer requirements change quickly, and that different customers simply have different needs that are difficult to meet.
A major problem with REST is over- and under-fetching. In the previous section you have just seen an example for over-fetching caused by the fixed data structures in REST. The client can only download data from endpoints that provide a fixed set of data. So over-fetching means that a client receives more information than actually required. When a client has to provide the user with a list of products, this list will most probably not show all properties that are part of the products endpoints response.
On the other hand there is under-fetching, meaning that the specific endpoint does not provide all the information the client needs. Therefore, the client has to use several other endpoints to get and download all the required data.
The Intershop REST API tries to solve and overcome some of these problems. To avoid over-fetching, elements within a response are often displayed as links that do not take up too much space, as they only contain a URL and often a name and a short description. You saw this in the example above:
{
// Response for a ProductRO
"reviews": "inSPIRED-inTRONICS-Site/-/products/4729529/reviews",
// (...)
}
In case the client actually needs more data for the element, the link must be resolved. There is another issue with links on the “static” side of things: They erase the type information present in the OpenAPI descriptions that has to be manually included (say, by an extension attribute).
The trick is to avoid overloading in case of large collections of elements, but only if you do not need to expand them. This approach will lead to many client server requests and, ironically, also to overloading.
Another approach is that more recently developed endpoints support requesting “internal” links, meaning that the client gets the chance to explicitly tell the server: “Hey, resolve that link for me before replying, I will need that information anyway”. This clearly avoids some of the over- and under-fetching.
However, for architecture and design reasons, to support smart caching strategies on the server and also for reasons of scalability and extensibility, clients usually still have to connect to several different endpoints to fetch all the data they need, and even then the client will often fetch much more data than actually required. Therefore, more often than not, when connectivity and bandwidth are an issue, REST does not provide a proper answer.
We haven’t even mentioned yet that more data means more effort for parsing, storage etc. GraphQL really solves these problems, so it is often true to say: When performance counts, GraphQL is the answer.
Another problem with REST is the absence of a strong schema. Of course, Intershop’s REST API is fully documented via OpenAPI, but REST itself does not require or specify a schema at all. GraphQL on the other hand uses and requires a strong type system to fully define the capabilities of an API. Time has proven over and over again that type systems are useful and important in programming, they help speed up development and avoid bugs. GraphQL embraces these concepts: All exposed types are part of a standardized schema expressed in GraphQL Schema Definition Language (SDL), as you have seen above.
This has several advantages: Teams working on backend and storefront can be both aware of a definite structure of data – the schema works as a contract. Mocking can easily be used in a safe way for testing or developing. But probably even more important: GraphQL libraries can rely on that. Furthermore, there is a wide range of tools to support developers.
So if we say “the client counts”, we really want to address the issues of the frontend developers, so let’s make their lives easier. Thanks to GraphQL client libraries frontend developers get features like caching, realtime or optimistic UI updates for free. This increases productivity, quality and last but not least ensures a great end-user experience.
GraphQL at Intershop

Intershop’s GraphQL interface now allows you to take advantage of GraphQL in your client application. The interface itself is implemented as its own service and component that can be configured to expose Intershop’s REST APIs as GraphQL. The interface computes the GraphQL schema based on OpenAPI models which describe the REST APIs that are used to fetch the actual data. Thanks to intelligent rules that are used to combine the REST APIs described by OpenAPI into a complete schema, the resulting type system provides a very similar but completely holistic view of the Intershop APIs. Using this approach the GraphQL-interface complements the existing REST APIs in a very client-friendly way.
Let’s discuss some example queries which illustrate the schema and show some of the advantages for clients.
(Note: In the examples we use a free version of Insomnia, a tool that for example takes advantage of GraphQL schema reflection the GraphQL interface offers).
Mutations
Of course, there are not only GET requests but also requests that deal with changing data, e.g. POST, PUT etc. In GraphQL, these are called Mutations. As an example, if you want to create a review, you could use the following mutation:
mutation {
addReview(productKey:"9925078", value: {
name : "some_review"
content: "This product is great!"
})
}
As you can see, mutations are structurally similar to queries, the main differences being perhaps that we use mutation
instead of query
as the root object and that we pass more complex data.
Pagination
There is a dedicated extension specification for GraphQL that deals with pagination. It defines a format on how paging should be handled and is supported by the most common client frameworks. Our gateway translates the Intershop-specific pagination into this format:

This is only one GraphQL query. How would you get this using REST?
You would first make a request to /products
to get a list of links to the products. In order to get further information about each product, you would call the corresponding /products/{productKey}
endpoint. If you want to show 20 products on a single page, it would require 20 requests.
At this point, let us emphasize that a REST-based client can of course fetch exactly the same data. However, several calls would be necessary and the actually required data must be filtered out. In the given example, with 20 retrieved products, this means
- The client calls the gateway and makes a request to retrieve 20 products with some basic data. This request takes 1018 bytes.
- The gateway makes 21 requests (one to get the list of products and one for each product as described above) and gets 21 responses, making up in total for 21407 bytes.
- The gateway sends the data back to the client. The size of the GraphQL response is 1830 bytes.
- Therefore, the client-to-gateway communication is ~2.78KB, as opposed to ~20,91KB gateway-to-backend communication (which is what the client would have to do anyway!). This is a reduction of over 86%!
Now that you have seen how you can query commerce data using powerful GraphQL features, you might ask if you can also add your own services in a transparent way. Indeed, that is possible. The GraphQL interface just uses a set of OpenAPI models (configured by using URLs or file references) to compute the GraphQL schema. To do so, it uses several intrinsic rules to combine and merge the types and queries. Some of the rules for example are able to handle links in REST APIs (end effectively get rid of them in GraphQL), others are able to handle JSON API like REST requests and others handle OpenAPI links.
We think this is worth a blog post on its own, so stay tuned for the next part!