NewJoin 2 of Our Co-Founders Talk About Performance
Save Your Spot Nowarrow-right
vsf_logo.svg
github-star-nav-top.svg
How to Create and Use Custom GraphQL Query with Vue Storefront and Commercetools Integration

Explore by Category:

Product

How to Create and Use Custom GraphQL Query with Vue Storefront and Commercetools Integration

Untitled_design_(2).png

Sergii Kirianov

April 11, 2023

Vue Storefront comes with a built-in method of communicating with the commercetools platform - composables. However, we understand that users may have specific needs for their projects. That's why we've designed our composables to allow custom queries that cover all our users' needs. If you've ever wondered how to create and use a custom query with Vue Storefront and the commercetools integration, this article is for you! So, let's get started and create something cool together using your favorite IDE.

Prerequisites

For this article, we assume that you already have your Vue Storefront and commercetools projects up and running.

If that's not the case, you can always contact us on Discord for help, or contact the Vue Storefront Sales Team to help you get started.

The Default Query

Before we start writing the custom query, let’s first prepare our example page. For this article, we will use the Product.vue page that comes out of the box.

example-page-product-vue.png

If you look at the source code for this page, you can see that we are using useProduct composable to fetch the data for the product. We retrieve the information about our product from the URL:

setup() { //... const route = useRoute(); const { products, search, error: productError, loading } = useProduct('products'); const fetchProducts = async () => { try { //... await search({ skus: [route.value.params.sku] }); // useProduct composable search method //... };

When the search method call is done, it populates products with the fetched data. Let’s investigate the products state and see what is being stored there.

(17) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, __ob__: Observer] 0: attributesRaw: Array(16) availability: Object id: 1 images: Array(2) price: Object sku: "M0E20000000E59L" __typename: "ProductVariant" _categories: Array(2) _categoriesRef: Array(2) _description: "Blue jeans that are high-quality, durable, and stylish." _id: "891c95f8-7bf4-4945-9ab5-00906a5f76ba" _key: undefined _master: true _name: "Jeans Closed blue" _original: Object _rating: Object _slug: "closed-jeans-c3202059-blue"

The products state is populated with an array of objects for products that match the sku number.

However, the product object does not contain all the necessary information. All products have a taxCategory information that the default query does not fetch. What if we really need that information? The answer is a custom query.

Writing a Custom Query

We are mostly satisfied with all the information provided by the default query. For this article, we will extend the existing default query and add the taxCategory field to it, but in reality, you may need to write the query from scratch.

Create a folder called customQueries in the root of your project. If you already have one, you can simply open it. Inside this folder, create a new file named productTaxCategory.js. This is where we will write our new custom query.

// ./customQueries/productTaxCategory.js // add taxCategory field on products query module.exports = ` query products( // query args ) { products( where: $where sort: $sort limit: $limit offset: $offset skus: $skus ) { offset count total results { id taxCategory { name rates { amount country includedInPrice subRates { name amount } } } reviewRatingStatistics { averageRating, ratingsDistribution, count } masterData { // rest of the query } } } `;

We have omitted the rest of the query and other details to make it shorter, otherwise, it would be 150 lines long. But, you can find this exact query here - GitHub Gist .

Okay, great, the new query is ready, but how are we supposed to use it? 🤔

First, let's create a function that will call our query. This is a simple function, but it's important to follow the pattern.

If there is no index.js file in the same folder, create one. Then, import the custom query that you have just created and write a function following this pattern:

const productTaxCategory = require('./productTaxCategory'); module.exports = { productTaxCategory: ({ query, variables, metadata }) => { console.log('productTaxCategory variables: ', variables); console.log('productTaxCategory metadata: ', metadata); return { query: productTaxCategory, variables }; } };

So, what are we doing here? We are exporting a function called productTaxCategory that accepts an argument object. For the sake of this article, we added two console.log statements to show that the query is actually being sent. This function then needs to be provided to the middleware. To do that, open middleware.config.js in the root of the project.

const { ctCustomQueries } = require('./extensions/ct'); require('dotenv').config(); const stringToArrayValue = (val, separator = ',') => { return typeof val === 'string' ? val.split(separator) : []; } module.exports = { integrations: { ct: { location: '@vsf-enterprise/commercetools-api/server', configuration: { api: { uri: process.env.VSF_API_URI, apiHost: process.env.VSF_API_HOST, authHost: process.env.VSF_API_AUTH_HOST, projectKey: process.env.VSF_PROJECT_KEY, clientId: process.env.VSF_API_CLIENT_ID, clientSecret: process.env.VSF_API_CLIENT_SECRET, scopes: stringToArrayValue(process.env.VSF_API_SCOPES) }, serverApi: { clientId: process.env.VSF_SERVER_API_CLIENT_ID, clientSecret: process.env.VSF_SERVER_API_CLIENT_SECRET, scopes: stringToArrayValue(process.env.VSF_SERVER_API_SCOPES), operations: stringToArrayValue(process.env.VSF_SERVER_API_OPERATIONS), }, currency: 'USD', country: 'US', languageMap: { en: ['en', 'de'], de: ['de', 'en'] } }, customQueries: ctCustomQueries } };

As you can see, we already have a ctCusomQueries provided. We need to import new customQueries we have created and add them here:

const customQueries = require('./customQueries');customQueries: { ...ctCustomQueries, ...customQueries }

Since we have made changes to the middleware.config.js file, please restart your application.

Great! We have registered a new custom query in our middleware, now, we need to use it in the application!

Using Custom Queries

Let's go back to the Product.vue file and update the search method to use a new custom query. Since the query is already registered in the middleware, we don't need to import anything. All we have to do is provide the query name as a parameter for the search method:

await search({ skus: [route.value.params.sku], customQuery: { products: 'productTaxCategory' } });

Note that in order for the custom query to work, we must provide a proper key name for the customQuery object. This name should be the same as what we used in the GraphQL query itself.

//...rest of GraphQL query products( where: $where sort: $sort limit: $limit offset: $offset skus: $skus ) //...rest of GraphQL query // products as a key for the customQuery object customQuery: { products: 'productTaxCategory' }

When you save and refresh the product page, you will receive an error. However, don't worry, this is expected behavior. The issue could be with the custom query.

If you look at the terminal, you will notice that the console.log printed the result.

productTaxCategory variables: { where: null, skus: [ 'M0E20000000E59L' ], limit: undefined, offset: undefined, locale: 'en', acceptLanguage: [ 'en', 'de' ], currency: 'USD', country: 'US' } productTaxCategory metadata: {}

So, this is not the issue! What is the issue then?

To better understand what is wrong, let's dive deeper into the internals of how the useProduct composable works. First, let's start with the search method itself.

async function search(searchParams) { Logger.debug(`useProduct/${id}/search`, searchParams); try { loading.value = true; products.value = await useProductMethods.productsSearch(context, searchParams); error.value.search = null; } catch (err) { error.value.search = err; Logger.error(`useProduct/${id}/search`, err); } finally { loading.value = false; } }

The search method accepts a searchParams object as an argument and calls useProductMethods.productsSearch internally with both the context and searchParams objects. The productsSearch method performs the following actions:

productsSearch: async (context: Context, { customQuery, enhanceProduct, ...searchParams }): Promise => { const apiSearchParams = { ...searchParams, ...mapPaginationParams(searchParams) }; // @ts-ignore const response = await context.$ct.api.getProduct(apiSearchParams, customQuery); catchApiErrors(response as unknown as ErrorResponse); if (customQuery) return response as any; // @ts-ignore const enhancedProductResponse = (enhanceProduct || originalEnhanceProduct)(response, context); return (enhancedProductResponse.data as any)._variants; }
  • The code performs the following actions:
  • Creates an apiSearchParams object based on the searchParams argument.
  • Calls a getProduct API endpoint.
  • Catches errors.
  • If customQuery exists, returns the response as is without any data transformation.
  • Otherwise, enhances and maps the data to return an array of products.

If the customQuery parameter is passed, the data does not go through the enhanceProduct process, and the raw response data from the commercetools GraphQL API is returned. This is likely the reason for any errors encountered. To diagnose, inspect the products state to see what data is received.

// console.log(products.value) - populated after customQuery usage data: Object products: Object count: 1 offset: 0 results: Array(1) 0: id: "891c95f8-7bf4-4945-9ab5-00906a5f76ba" masterData: Object reviewRatingStatistics: Object taxCategory: Object __typename: "Product" total: 1 __typename: "ProductQueryResult" loading: false networkStatus: 7

As described above, our products state is populated with the raw response from the commercetools GraphQL API. However, the response contains what we initially wanted: a taxCategory object for the product!

To fix the error on a Product.vue page, you should manually map the data in the way you will consume it later in your application. This approach is relatively new and was implemented by Vue Storefront a while ago. Our goal was to give you complete freedom to get the data and use it as you wish!

With great power comes great responsibility.

- Uncle Ben

Conclusions

Creating custom queries is a relatively simple process that doesn't take much time. It can be a little confusing when you do it for the first time, but once you understand how it works, all following queries will be a piece of cake!

Vue Storefront covers a majority of use cases, but also allows users to customize almost everything and tailor the application to their needs.

If something wasn't clear, or you need help with custom queries in your application, feel free to join us on Discord and register for upcoming Workshops where we will cover even more advanced concepts for the commercetools integration with Vue Storefront application!

If you are interested in learning more, please check our commercetools documentation and sign up for the Academy . When you are confident in your knowledge, you can take a commercetools integration quiz to obtain a VSF certificate.

Share:

Share:

Ready to dive in? Schedule a demo

Get a live, personalised demo with one of our awesome product specialists.