Query Controller
Query controller is a convenient way to manage GraphQL. This architectural solution helps to easily create, update, and extend GraphQL queries with the Extension Mechanism.
Motivation
We wanted an easy way to define fields for GraphQL queries that won't require writing query string, but will rather compose that string automatically, given the supplied data. This would allow for better readability, reusability, and most importantly - extensibility. We wanted the Query Controller to:
Make the process of query declarations more intuitive and simple,
Be modular to allow for extensibility,
Give the option to define query fields conditionally,
Support main GraphQL features, such as aliases and arguments
Usage
Query controllers look and behave just like a regular Javascript classes:
You can immediately spot three things:
Public methods to get fields for specfic query:
getCollectionByHandleField
andgetCollectionsField
. We call them Query getters."Private" methods prefixed with underscore to define the fields themselves:
_getCollectionFields
,_getCollectionField
,_getEdgesField
, and_getCollectionsFields
. We call them Field getters.Field
that is used in every single one of these methods.
Field
Field
is our way of storing GraphQL field data. It's a regular Javascript class and you can read more on Field
as well as other GraphQL features supported by @scandipwa/graphql
in the documentation.
Query getters
Query getters are used to compose a GraphQL query (or mutation) from given arguments, if any. Query getters are only responsible for:
Returning a field that exists in the Query/Mutation root of the target GraphQL schema
Applying and/or passing down arguments for the query/mutation
Referencing the fields declared by Field getters
Query getters are not responsible for any field definitions! You shouldn't list the fields directly in Query getters and instead use Field getters to retrieve the desired fields.
✅ How does a good query getter look like:
❌ How does a bad query getter look like:
Field getters
Field getters are used to define the fields that will be used by controller's queries, they are never called directly in the app. Field getters should be:
Modular to allow for extensibility. Meaning that if there are multiple GraphQL types involved in the queries, each of them should be described in a separate private method
Returning a field or an array of fields. No fields from the Query/Mutation root are permitted.
✅ How does a good field getter look like:
❌ How does a bad field getter look like:
TypedQuery
TypedQuery
allows to map Query getters to a function and offers more deep extension possiblities. This how a Query controller with TypedQuery
looks like:
There are several differences from a regular Query controller definition:
The Query controller now extends the
TypedQuery
classNew member property
typeMap
is presentThe default export is no longer a class instance, but the
mapQueryToType
function.
Query types
The usage of TypedQuery
involves the assignment of type to each Query getter method. In the example above, we map getCollectionsField
to the PAGINATED_COLLECTIONS
type and getCollectionByHandleField
to the SINGLE_COLLECTION
type:
In addition to typeMap
, TypedQuery
class defines this.currentType
property, which will contain the type of the Query getter. If the query of SINGLE_COLLECTION
type was called, this.currentType
will be 'single'
.
The knowledge of query type allows to conditionally add or remove fields from Field getters depending on the query, let's take product images as an example. We would want product page to contain 15 product images, but we would also want product listing page to only show one image. Following all the rules for creating Query controllers we would get:
This leads to an obvious problem, as we would fetch 15 images for each product on the product listing page. We would be fetching unnecessary information and making our requests slower. This is a great place to implement TypedQuery
:
Now that we have access to this.currentType
, it is possible to conditionally change the number of images that we want to fetch based on the Query getter. In this example, we would fetch 15 images for single product query and only 1 for paginated product query.
Getting query by type
As mentioned before, Query controllers that extend TypedQuery
must mapQueryToType(MyQuery)
as a default export. In order to get a query by type, you will to call call the exported function as pass the query type as an argument:
Extension
Why would you extend?
You would want to extend Query controllers for various reasons:
Adding new fields to GraphQL queries made by the Query controller
Defining brand new GraphQL query type
Examples
Learn how to extend Query controllers with these examples taken directly from the code.
Add new fields to a Field getter
Add a new query type to typeMap
Last updated