Back to Top

Graphql Caching In Magento 2

Updated 22 May 2023

Here we will get to know about Graphql Caching in Magento 2.

Caching improves response time and also reduces the load on the server. but without caching, each page might need to run blocks of code and retrieve large amounts of information from the database. basically queries submit with an HTTP GET operation can be cache. POST queries cannot be cache.

The definitions for some queries include cache tags because caching uses these tags to keep track of cached content.

Basically graphQL allows you to make multiple queries in a single call. If you specify any uncached query, the system bypasses the cache for all queries in the call.

Lets create a Custom module to perform Graphql Caching.

First we will create a registration.php file inside below path.

Start your headless eCommerce
now.
Find out More

app/code/Webkul/CustomGraphQl

<?php
/**
 * Webkul Software.
 *
 * PHP version 7.0+
 *
 * @category  Webkul
 * @package   Webkul_CustomGraphQl
 * @author    Webkul <[email protected]>
 * @copyright 2010-2019 Webkul Software Private Limited (https://webkul.com)
 * @license   https://store.webkul.com/license.html ASL Licence
 * @link      https://store.webkul.com/license.html
 */

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    "Webkul_CustomGraphQl",
    __DIR__
);

Create module.xml file in below path.

app/code/Webkul/CustomGraphQl/etc/module.xml

<?xml version="1.0"?>
<!--
/**
 * Webkul Software.
 * 
 * PHP version 7.0+
 *
 * @category  Webkul
 * @package   Webkul_CustomGraphQl
 * @author    Webkul <[email protected]>
 * @copyright 2010-2019 Webkul Software Private Limited (https://webkul.com)
 * @license   https://store.webkul.com/license.html ASL Licence
 * @link      https://store.webkul.com/license.html
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Webkul_CustomGraphQl" setup_version="1.0.0">        
        <sequence>
                <module name="Magento_Catalog"/>
                <module name="Magento_GraphQl"/>
        </sequence>
    </module>
</config>

Now we will create a schema.graphqls file in below path.

app/code/Webkul/CustomGraphQl/etc

This file contains schema for your graphql query like resolver class and type.

The @doc directive defines that if we need to add some description to our query.

A resolver performs GraphQL request processing. resolver construct a query, fetching data and performing any calculations.

Resolver transforms the fetched and calculated data into a GraphQL array format and returns the results wrapped by a callable function

@cache Directive

The @cache directive defines whether the results of certain queries can be cached.

The cacheIdentity value points to the class responsible for retrieving cache tags.

Additionally query without a cacheIdentity will not be cached.

To disable caching for queries declared in another module with a cacheIdentity class, the @cache(cacheable: false) directive can be used. This cacheable argument is intended to disable caching for queries that are defined in another module.

Specifying @cache(cacheable: false) or @cache(cacheable: true) on a query without a cacheIdentity class has no effect. The query will not be cache. If a query should not be cached, do not specify the @cache directive.

type Query {
    categoryProductlist (
        categoryId: Int
        pageSize: Int
        currentPage: Int
    ) : CategoryProductList @doc(description: "Get CategoryProductList") @resolver(class: "Webkul\\CustomGraphQl\\Model\\Resolver\\ProductsCollection") @cache(cacheIdentity: "Webkul\\CustomGraphQl\\Model\\Resolver\\Block\\CustomCategoryIdentity")                    
}

type CategoryProductList {
    totalCount: Int
    category_product_list: [CategoryProductListItems]
}

type CategoryProductListItems {
    entity_id: Int
    type_id: String
    sku: String
    name: String
    image: String
    status: String
    visibility: String
    price: String
}

Now we will create resolver ProductsCollection.php in below path because this class contains all the logic.

app/code/Webkul/CustomGraphQl/Model/Resolver

<?php

declare(strict_types=1);

namespace Webkul\CustomGraphQl\Model\Resolver;

use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Query\ResolverInterface;

class ProductsCollection implements ResolverInterface
{
    /**
     * @inheritdoc
     */
   public function __construct(
        \Magento\Catalog\Model\CategoryFactory $categoryFactory,
    ) {
        $this->categoryFactory = $categoryFactory;
    }

      /**
     * @return array
     * @throws GraphQlNoSuchEntityException
     */
   public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
    {
        try {
            $category = $this->categoryFactory->create();
            $category->load($args['categoryId']);
            $products = $category->getProductCollection()->distinct(true);
            $products->addAttributeToSelect('*');
            $productCount = count($products);
            $products->setPageSize($args['pageSize'] ?? 10);
            $products->setCurPage($args['currentPage'] ?? 1);
            $productList = [];
            foreach ($products as $product) {
                $eachProduct = [];
                $eachProduct = $product->toArray();
                $productList[] = $eachProduct;
            }
            
            $productRecord['totalCount'] = $productCount;
            $productRecord['category_product_list'] = $productList;
           
        } catch (NoSuchEntityException $e) {
            throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e);
        }
        return $productRecord;
    }

        
}

Now we will create Identity class for caching our query CustomCategoryIdentity.php in below path.

app/code/Webkul/CustomGraphQl/Model/Resolver/Block

<?php
declare(strict_types=1);

namespace Webkul\CustomGraphQl\Model\Resolver\Block;

use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface;

/**
 * Get identities from resolved data
 */
class CustomCategoryIdentity implements IdentityInterface
{
    private $cacheTag = 'wk_category_products_custom';

    /**
     * Get identity tags from resolved data
     *
     * @param array $resolvedData
     * @return string[]
     */
    public function getIdentities(array $resolvedData): array
    {
        $ids = [];
        $items = $resolvedData['category_product_list'] ?? [];
        foreach ($items as $item) {
            $ids[] = sprintf('%s_%s', $this->cacheTag, $item['entity_id']);
        }
        if (!empty($ids)) {
            $ids[] = $this->cacheTag;
        }
        return $ids;
    }
}

After creation of all files lets register our module di:compile and clean the cache.

We will Now test our graphql query.

Example will be like below.

{
  categoryProductlist(categoryId: 2, pageSize: 20, currentPage: 1) {
    totalCount
    category_product_list {
      entity_id
      type_id
      sku
      name
      image
      status
      visibility
      price
    }
  }
}

We will get response like below.

Selection_004

Check headers for cached params.

Selection_005

In headers X-Magento-Cache-Debug : MISS that means query is not cache.

We can achieve this by creating a GET request and use graphql query in it like below because POST requests cannot be cache.

Request Type =>GET

{{base_url}}/graphql?query={categoryProductlist(categoryId: 2, pageSize: 20, currentPage: 1) {totalCount category_product_list {entity_id type_id sku name image status visibility price } } }

Selection_006

Above headers show X-Magento-Cache-Debug : HIT

finally our query is cached now.

In this way we can achieve GraphQL caching

Basically to invalidate cache tags we need to create an observer while saving product from admin panel.

Also create a file event.xml and also paste it in below path

app/code/Webkul/CustomGraphQl/etc/adminhtml

before saving file add below code in it.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="catalog_product_save_after">
        <observer name="clear_cache_tags_custom" instance="Webkul\CustomGraphQl\Observer\Productsaveafter" />
    </event>
</config>

Now we will create observer file named Productsaveafter.php in below path.

app/code/Webkul/CustomGraphQl/Observer

Also add below code in file which we have just created before.

<?php

namespace Webkul\CustomGraphQl\Observer;

use Magento\Framework\Event\ObserverInterface;
use Zend_Cache;
use Magento\PageCache\Model\Cache\Type;

class Productsaveafter implements ObserverInterface
{ 
    /**
     * @var Type
     */
    private $fullPageCache;

    /**
     * @param Type $fullPageCache
     */
    public function __construct(
        Type $fullPageCache
    ) {
        $this->fullPageCache = $fullPageCache;
    }

    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        $_product = $observer->getProduct();
        $productId = $_product->getId();
        $tags = ['wk_category_products_custom_'.$productId];
        if (!empty($tags)) {
            $this->fullPageCache->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, array_unique($tags));
        }
    }   
}

In this way we can invalidate custom cache tags from graphql query.

Learn more from this link : https://developer.adobe.com/commerce/webapi/graphql/

check for other blogs as well: https://webkul.com/blog/graphql-mutation-2/

. . .

Leave a Comment

Your email address will not be published. Required fields are marked*


2 comments

  • Arun
    • Shubhanshu Sahare (Moderator)
  • Back to Top

    Message Sent!

    If you have more details or questions, you can reply to the received confirmation email.

    Back to Home