Create a GraphQL query
Learn how to query Composable Commerce with GraphQL.
After completing this page, you should be able to:
- Identify GraphQL queries that successfully query Composable Commerce.
Having seen the advantages of using GraphQL to improve performance, stability, and maintainability of our application, let’s now focus on querying. We will use GraphQL to:
- request only specific fields instead of the entire record when fetching resources.
- create reusable queries using parameters.
- determine the existence of a Composable Commerce resource.
- retrieve data from multiple endpoints in one GraphQL query.
- retrieve large amounts of data using pagination.
- use query fragments to modularize our GraphQL queries.
Requesting specific fields in returned data
As already mentioned, one of the main features of GraphQL is the ability to specify the fields in a resource that you are interested in-you don’t have to fetch the data of the entire record. This approach saves time and improves performance in cases when you're returning multiple records across the network. Less data per record means you can have more records in each response.
Let’s look at another example of overfetching. Say you want to read Customer records, but only need the customer’s first name, last name, and email address. When making this request with the REST API, you will get the full Customer information in your response. This would look like the following:
{"limit": 20,"offset": 0,"count": 6,"total": 6,"results": [{"id": "e5ab352e-7be2-4562-8bc5-4d9ba763489c","version": 1,"createdAt": "2024-01-17T23:51:35.600Z","lastModifiedAt": "2024-01-17T23:51:35.600Z","lastModifiedBy": {"isPlatformClient": true,"user": {"typeId": "user","id": "c950c084-f5d0-4a42-8761-229d5c7a8598"}},"createdBy": {"isPlatformClient": true,"user": {"typeId": "user","id": "c950c084-f5d0-4a42-8761-229d5c7a8598"}},"email": "jen@example.uk","firstName": "Jennifer","lastName": "Jones","password": "****hO4=","addresses": [{"id": "mjNpcegk","firstName": "Jennifer","lastName": "Jones","streetName": "Main Road","streetNumber": "100","postalCode": "SW1A2AA","city": "Westminster","country": "GB"}],"shippingAddressIds": [],"billingAddressIds": [],"isEmailVerified": true,"key": "jennifer-jones","stores": [],"authenticationMode": "Password"}]}}]
In this example, we’re only showing one Customer record of the returned data for brevity. You could imagine how much data would be included if you were requesting multiple records. This is more information than necessary, consuming excessive bandwidth if you are not using all of this data.
With a GraphQL query specifying only the fields you want, you could instead get a response like this:
{"data": {"customers": {"results":[{"firstName": "Jennifer","lastName": "Jones","email": "jen@example.uk"},...]}}}
A much cleaner and more efficient representation of the data.
What does a GraphQL query look like? If we wanted to write a request to generate the response above, it would look like this:
query queryCustomers {customers {results {firstNamelastName}}}
How is this query put together? Let’s take a closer look.
- First, the word
query
indicates that this is a request to retrieve information, as opposed to an update or deletion. - Next is the name of the query. Here we’ve chosen
queryCustomers
because that’s a nice description of what this query does. - Then comes the resource that we want to query. We are using Customers, and this allows one or many customers to be returned. If we only want to return one Customer, then we can use a
where
condition or use the Customer resource and query by a Customer ID. When there are multiple results we use PagedQueryResult to manage the pagination. - The last element is a list of the fields that you want to have returned. Here we see that we list subfields from the results array.
results
here is an array of Customer records, so we call out the individual fields we want from each Customer record that is returned. In this case, the first name, last name, and email.
Building our query or mutation
In addition to querying, we can use predicates to define which records we want to have returned. Predicates are included in parenthesis after the resource name and before the opening bracket for the return values.
Inside the brackets we can pass:
where
: uses standard query predicates in double quotation marks, so if any values therein contain quotation marks, those have to be escaped with a backslash. For example, if you want to match all the Customers where the first name isMartha
, you would need to enter that as follows: (where: "name(en=\"T-Shirt\")"
).sort
: states which field to use to sort the returned records and if they should be in ascending or descending order based on that field. For example, if you want records sorted by the ID field in ascending order, you would enter: (sort: "createdAt asc"
)limit
: states the maximum number of records that should be returned (limit: 30
).offset
: defines where in the logical set of records the data returned should start. For example, if you have 10,000 Products you cannot fetch all of them in one request. If you set the limit to 100, you would need to request data 100 times to get all of that data. For each request, you would need to indicate which chunk of that data you wanted. Offset defines how far into that data you want a request to start. For example, on your first request, you would set offset to 1, then 2, then 3, and so forth. More on this later when we talk about pagination.
Now we need to explicitly define what values will be returned from the GraphQL server.
offset
: the current offset for the result.count
: the number of records that were returned in the response. If it is less than the limit you requested, then you have reached the end of the dataset.total
: the number of records in the matching predicate. If you don’t request this return value, it won’t be calculated, which can make the request more performant. This is the same as setting the REST API query parameterwithTotal
to false.results
: the array of requested records containing only the fields you requested.
Here’s a sample query using the where
and the sort
query parameters. This query returns data for all Customers whose first name is Jennifer
in ascending order by id
.
query queryCustomersWithWhere {customers(where: "firstName=\"Jennifer\"", sort: "id asc") {results {firstNamelastName}}}
Using variables in your query parameters
In addition to sending hardcoded values in query parameters, like the previous example (where: "firstName=\"Jennifer\""
), you can also use variables for those parameters:
query queryCustomersWithWhereVariable($customerName: String!) {customers(where: $customerName, sort: "id asc") {results {firstNamelastName}}}
This gives your GraphQL queries much greater flexibility! As you can see, you define the variable right after the query name using the dollar sign ($) as the first character and then using that same variable in the where clause. In this case, the variable customerName
could contain a string like "firstName=\"Jennifer\""
or "lastName=\"Jones\""
.
You can also use variables in a single response query where, for example, you set the variable custKey
to a string like jennifer-jones
.
query queryCustomerByKeyWithVariable($custKey: String!) {customer(key: $custKey) {firstNamelastName}}
You define the value for the variable in the variables section of the request, or in the QUERY VARIABLES window of GraphiQL.
Check whether resources exist
If you want to determine if there is at least one record matching your query, but don’t need to see any of the data, you can optimize your query with an existence check, like the following:
query queryCustomersWithWhere {customers(where: "firstName=\"John\"") {exists}}
In this case, if there is a Customer record with the firstName
field set to John
, you'll see the following:
{"data": {"customers": {"exists": true}}}
Retrieve data from multiple resource endpoints
GraphQL not only lets you select specific fields within a requested record but also retrieves data from multiple Composable Commerce resources in a single request. This approach reduces the latency associated with multiple network calls. Moreover, when accessing data from various resources, GraphQL can process these requests in parallel, making it more efficient than REST.
Let’s assume you would like to build a customer's profile section on a webshop including their personal record and their last five orders. The following example achieves this in one request. It uses variables in the parameters to simultaneously retrieve details about the Customer and all of their Orders, sorted in descending order by creation time.
query queryCustomerAndOrders($custId: String!, $whereCustId: String!) {customer(id: $custId) {idfirstNamelastNamedefaultShippingAddress {country}}orders(where: $whereCustId, sort: "createdAt desc", limit: 5) {results {customerIdcustomerEmailcreatedAtorderStatetotalPrice {typecurrencyCodecentAmountfractionDigits}}}}
The variables are:
{"custId": "{id}","whereCustId": "customerId=\"{id}\""}
Which, in our case, retrieves the following:
{"data": {"customer": {"id": "e5ab352e-7be2-4562-8bc5-4d9ba763489c","firstName": "Jennifer","lastName": "Jones","email": "jen@example.uk","defaultShippingAddress": null},"orders": {"results": [{"customerId": "e5ab352e-7be2-4562-8bc5-4d9ba763489c","customerEmail": "jen@example.uk","createdAt": "2024-01-17T23:51:48.354Z","orderState": "Confirmed","totalPrice": {"type": "centPrecision","currencyCode": "GBP","centAmount": 461799,"fractionDigits": 2}}]}}}
Retrieve large amounts of data using pagination
If you run a successful webshop with thousands of customers and even more orders, retrieving pages of data in the most efficient way possible is a task that will require a sound strategy.
Generally, you may want to sort records by ID (in Composable Commerce, IDs can be sorted) and then retrieve a set number of them at a time. If Composable Commerce knows what the last ID is at the end of a given page, then we can pass zeros as the initial ID (00000000-0000-0000-0000-000000000000
).
To do that with GraphQL, we could use a query like the following and update the ID value on each subsequent query.
query queryCustomersWithPagination {customers(limit: 20sort: "id asc"where: "id>\"00000000-0000-0000-0000-000000000000\"") {countresults {idfirstNamelastName}}}
This starts off with the lowest ID possible and returns 20 records. You would, in your code, take the value of the ID in the last record returned and use it in the where
clause of the next query. When the count returned is less than the limit you requested, you have reached the end of your dataset.
To do this, it is not necessary to include the total
field; you can exclude this field to decrease the latency. This is because the total
count adds a slight computation overhead to each query.
Optimizing paged requests is an important topic. See our docs for more on the concept of Paging in Composable Commerce.
Use fragments to modularize your queries
Suppose you need to find specific information for all addresses of a customer, including different data for their default billing and shipping addresses. Instead of repeatedly specifying the same information in your GraphQL query, why not simplify the process by dividing the address data into reusable fragments? This approach allows you to efficiently query only the necessary subsets of information for each address.
In this case, you could use one fragment to return only postal codes and country fields, and another fragment for a larger set of address fields.
To define a fragment, you use the keyword fragment
. To include a fragment in your query, you precede the fragment with three periods ( …
).
Here’s an example where we define a fragment to return postal code and country, and a second fragment that includes those fields along with additional ones. We will first show how to write this query without using fragments, and then illustrate how using fragments can simplify the query.
This is what the query would look like without fragments:
query queryCustomerWithAddresses($custId: String!) {customer(id: $custId) {firstNamelastNameidversionaddresses {postalCodecountry}defaultShippingAddress {streetNumberstreetNameapartmentadditionalStreetInfocitystatepostalCodecountryadditionalAddressInfo}defaultBillingAddress {streetNumberstreetNameapartmentadditionalStreetInfocitystatepostalCodecountryadditionalAddressInfo}}}
The result would look like this:
{"data": {"customer": {"firstName": "Jennifer","lastName": "Jones","email": "jen@example.uk","id": "e5ab352e-7be2-4562-8bc5-4d9ba763489c","version": 7,"addresses": [{"postalCode": "SW1A2AA","country": "GB"},{"postalCode": "SWA 2AS","country": "GB"}],"defaultShippingAddress": {"streetNumber": "70","streetName": "Whitehall","apartment": null,"additionalStreetInfo": null,"city": "London","state": null,"postalCode": "SWA 2AS","country": "GB","additionalAddressInfo": null},"defaultBillingAddress": {"streetNumber": "70","streetName": "Whitehall","apartment": null,"additionalStreetInfo": null,"city": "London","state": null,"postalCode": "SWA 2AS","country": "GB","additionalAddressInfo": null}}}}
Now, let’s introduce the first fragment, which will return only the postal code and country:
fragment CountryInfo on Address {postalCodecountry}
Now the second fragment, returning more address info and referencing the first fragment:
fragment AddressInfo on Address {streetNumberstreetNameapartmentadditionalStreetInfocity...CountryInfoadditionalAddressInfo}
Let’s rewrite our original query utilizing these newly defined fragments:
query queryCustomerWithFragments($custId: String!) {customer(id: $custId) {firstNamelastNameidversionaddresses {...CountryInfo}defaultShippingAddress {...AddressInfo}defaultBillingAddress {...AddressInfo}}}
This much shorter query returns exactly the same data as the original one above.
Let’s try another example. This example returns Product information. Let’s look at the fragments first and then build them into a query.
Here’s the first fragment defining the return values in ProductData of SKUs and the English localization for the name field:
fragment Product on ProductData {skusname(locale: "en")}
You can then reference that fragment in a query, or even in another fragment, using the three periods syntax:
fragment StagedProduct on ProductQueryResult {results {idmasterData {staged {...Product}}}}
And now we can build a larger query, like the following:
query queryProduct {products(limit: 1) {...StagedProduct...CurrentProduct}}fragment Product on ProductData {skusname(locale: "en-us")}
Which returns something like this:
{"data": {"products": {"results": [{"id": "6840d46f-bfd8-4bb1-9ae6-4a1a544a9e74","masterData": {"staged": {"skus": ["SQB-034"],"name": "Ecru Double Bed"},"current": {"skus": ["SQB-034"],"name": "Ecru Double Bed"}}}]}}}