Skip to content
intermediate Phase 62 · GraphQL Advanced

GraphQL Caching

Understanding Magento 2 GraphQL caching: response caching, CDN caching, cache tags, and private content

45m
0 problems
Topic Progress 0%

Response Caching

GraphQL Cache Configuration

<!-- etc/di.xml -->
<config>
    <type name="Magento\Framework\GraphQl\Controller\GraphQl">
        <plugin name="graphql_cache" type="Vendor\Module\Plugin\GraphQlCachePlugin"/>
    </type>
</config>

Cache Plugin

<?php
namespace Vendor\Module\Plugin;

class GraphQlCachePlugin
{
    public function __construct(
        private \Magento\Framework\Cache\FrontendInterface $cache,
    ) {
    }
    
    public function beforeExecute(
        \Magento\Framework\GraphQl\Controller\GraphQl $subject
    ): void {
        $cacheKey = $this->getCacheKey($subject->getRequest());
        $cachedResponse = $this->cache->load($cacheKey);
        
        if ($cachedResponse) {
            $subject->getResponse()->setContent($cachedResponse);
            return false; // Skip execution
        }
    }
    
    public function afterExecute(
        \Magento\Framework\GraphQl\Controller\GraphQl $subject,
        $result
    ) {
        $cacheKey = $this->getCacheKey($subject->getRequest());
        $this->cache->save(
            $subject->getResponse()->getContent(),
            $cacheKey,
            ['graphql_cache']
        );
        
        return $result;
    }
}

Cache Key Generation

private function getCacheKey($request): string
{
    $body = $request->getContent();
    $hash = md5($body);
    
    return 'graphql_' . $hash;
}

CDN Caching

CDN Headers for GraphQL

public function execute()
{
    $response = $this->graphQl->execute();
    
    // Add cache headers
    $response->setHeader('Cache-Control', 'public, max-age=3600');
    $response->setHeader('X-Cache-Tag', 'graphql_products');
    
    return $response;
}

Nginx Configuration

# Cache GraphQL queries
location /graphql {
    # Only cache GET requests
    if ($request_method != POST) {
        return 405;
    }
    
    # Cache for 1 hour
    add_header Cache-Control "public, max-age=3600";
    add_header X-Cache-Status $upstream_cache_status;
    
    # Vary by authorization
    add_header Vary "Authorization";
    
    proxy_pass http://backend;
}

Varnish Configuration

# Cache GraphQL responses
sub vcl_backend_response {
    if (req.url ~ "^/graphql") {
        # Cache for 1 hour
        set beresp.ttl = 1h;
        
        # Don't cache private content
        if (beresp.http.Authorization) {
            set beresp.uncacheable = true;
            set beresp.ttl = 0s;
        }
    }
}

CDN Strategies

Strategy Use Case
Public cache Product catalog, categories
Private cache Customer data, cart
No cache Real-time inventory
Short TTL Pricing, promotions

Cache Tags

Cache Tag Implementation

public function resolve(
    Field $field,
    $context,
    ResolveInfo $info,
    array $value = null,
    array $args = null
) {
    $product = $this->productRepository->get($args['sku']);
    
    // Return with cache tags
    return [
        'data' => [
            'id' => $product->getId(),
            'name' => $product->getName()
        ],
        'cache_tags' => [
            'product_' . $product->getId(),
            'category_' . $product->getCategoryId()
        ]
    ];
}

Cache Tag Invalidation

public function saveProduct($product)
{
    $this->productRepository->save($product);
    
    // Invalidate cache tags
    $tags = [
        'product_' . $product->getId(),
        'product_list',
        'category_' . $product->getCategoryId()
    ];
    
    $this->cache->clean($tags);
}

Tag-Based Caching

private function getCacheTags(array $args): array
{
    $tags = ['graphql'];
    
    if (isset($args['filter']['category_id'])) {
        $tags[] = 'category_' . $args['filter']['category_id'];
    }
    
    if (isset($args['filter']['sku'])) {
        $tags[] = 'product_sku_' . $args['filter']['sku'];
    }
    
    return $tags;
}

Private Content

Private Content Handling

public function resolve(
    Field $field,
    $context,
    ResolveInfo $info,
    array $value = null,
    array $args = null
) {
    // Check if request is for private content
    $isPrivate = $this->isPrivateContent($info);
    
    if ($isPrivate) {
        // Don't cache private content
        $context->addCacheFlag('no-cache');
        
        // Or use short TTL
        $context->setCacheTtl(60); // 1 minute
    }
    
    return $this->getData($args);
}

Customer-Specific Caching

private function getCacheKey(array $args, $context): string
{
    $key = 'graphql_' . md5(json_encode($args));
    
    // Add customer ID for private content
    if ($context->getExtensionAttributes()->getCustomerGraphQlContext()) {
        $customerId = $context->getExtensionAttributes()
            ->getCustomerGraphQlContext()
            ->getCustomerId();
        
        $key .= '_customer_' . $customerId;
    }
    
    return $key;
}

Cache Control Headers

public function execute()
{
    $response = $this->graphQl->execute();
    
    // Private content - no cache
    if ($this->isPrivateRequest()) {
        $response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
        $response->setHeader('Pragma', 'no-cache');
    }
    
    // Public content - cache with tags
    else {
        $response->setHeader('Cache-Control', 'public, max-age=3600');
        $response->setHeader('X-Cache-Tags', implode(',', $this->getCacheTags()));
    }
    
    return $response;
}

Quiz

1. How do you cache GraphQL responses?

Question 1 options

2. What are cache tags used for?

Question 2 options

3. How do you handle private content?

Question 3 options

Flashcards

Question

How do you cache GraphQL responses?

Answer

Implement cache plugin with cache key generation

Question

What are cache tags?

Answer

Tags that allow selective cache invalidation

Question

How do you handle private content?

Answer

Don't cache or use short TTL

Question

What CDN header controls caching?

Answer

Cache-Control: public, max-age=3600

Question

How do you invalidate cache?

Answer

Use cache tags and clean specific tags

Revision Notes

Key Takeaways

  • 1. Implement cache plugins for GraphQL responses
  • 2. Use cache tags for selective invalidation
  • 3. Handle private content with no-cache or short TTL
  • 4. Configure CDN headers for caching
  • 5. Vary cache by authorization header

Interview Tips

  • Explain GraphQL caching strategies
  • Know how to implement cache plugins
  • Discuss cache invalidation approaches
  • Be ready to handle private content

Cheat Sheet

Cache:
  Cache-Control: public, max-age=3600
  X-Cache-Tags: product_1,category_5

Private:
  Cache-Control: no-store, no-cache

Invalidation:
  $cache->clean(['product_1', 'product_list'])

CDN:
  Vary: Authorization
  Cache by query hash