# Cached Query (/sdks/nodejs/how-to/rpc/query-cached)



## Overview [#overview]

**Query response caching** lets the broker answer repeat requests without re-running your handler — useful when a query is expensive to compute (a database lookup, an aggregation, a downstream call) but the same input is asked for repeatedly in a short window. Only the first request pays the processing cost; every other caller gets the same answer straight from the broker.

Set `cacheKey` and `cacheTtlInSeconds` in `createQuery(...)`. The first query with a given key is a miss: it reaches the handler, and the broker stores the response under that key for the TTL. A subsequent query with the same key is a hit — the broker returns the stored response directly without invoking the handler. `cacheHit` on the response tells you which happened.

**Gotchas:** the cache is keyed by the string you choose, not by the query body — if the underlying data changes mid-TTL, callers can get a stale answer until it expires. Keys are scoped per channel, so the same key on another channel is a separate entry. Caching only helps when requests genuinely repeat with the same key.

## Prerequisites [#prerequisites]

* KubeMQ server running on `localhost:50000`
* Node.js SDK installed (`npm install kubemq-js`)

## Code [#code]

```typescript title="cached-query.ts"
/**
 * Example: Query with Cache Key and TTL
 *
 * Demonstrates sending a query with caching enabled. The first query is a
 * cache miss (the handler responds), and the second query is a cache hit
 * (served directly from the KubeMQ server cache without calling the handler).
 *
 * Prerequisites:
 *   - KubeMQ server running on localhost:50000
 *
 * Run: npx tsx examples/rpc/cached-query.ts
 */
import { KubeMQClient, createQuery } from 'kubemq-js';

async function main(): Promise<void> {
  const client = await KubeMQClient.create({
    address: 'localhost:50000',
    clientId: 'js-rpc-cached-query-client',
  });

  try {
    // Set up a query handler so there is a responder for the first (uncached) query.
    const sub = client.subscribeToQueries({
      channel: 'js-rpc.cached-query',
      onQuery: async (q) => {
        console.log('Handler called for query:', q.id);
        await client.sendQueryResponse({
          id: q.id,
          replyChannel: q.replyChannel,
          executed: true,
          body: new TextEncoder().encode(JSON.stringify({ sku: 'WIDGET-42', qty: 128 })),
        });
      },
      onError: (err) => {
        console.error('Subscription error:', err.message);
      },
    });

    // Wait for the subscription to register on the server.
    await new Promise((r) => setTimeout(r, 500));

    const query = createQuery({
      channel: 'js-rpc.cached-query',
      body: JSON.stringify({ sku: 'WIDGET-42', warehouse: 'east' }),
      timeoutInSeconds: 10,
      cacheKey: 'inventory:WIDGET-42:east',
      cacheTtlInSeconds: 60,
    });

    // First query hits the responder (cache miss).
    console.log('Sending first query (expect cache miss)...');
    const response1 = await client.sendQuery(query);
    console.log(
      'Response 1 — executed:',
      response1.executed,
      '| cacheHit:',
      response1.cacheHit ?? false,
    );

    // Second query returns cached response — handler is not called.
    console.log('Sending second query (expect cache hit)...');
    const response2 = await client.sendQuery(query);
    console.log(
      'Response 2 — executed:',
      response2.executed,
      '| cacheHit:',
      response2.cacheHit ?? false,
    );

    sub.cancel();
  } finally {
    await client.close();
  }
}

main().catch(console.error);

```

## How It Works [#how-it-works]

* `cacheKey` and `cacheTtlInSeconds` in `createQuery()` enable server-side response caching: the first query with this key calls the handler; subsequent queries within the TTL are served from the KubeMQ cache.
* `response1.cacheHit` is `false` (or `undefined`) on the first call because the cache is cold; `response2.cacheHit` is `true` because the server returns the stored response without invoking the handler.
* The handler logs `'Handler called for query:'` only once — on the second query the handler is not invoked at all, demonstrating the cache bypass.
* The same `query` object is reused for both calls because `createEventMessage()` / `createQuery()` produce immutable frozen objects; they are safe to send multiple times.

## Related [#related]

* [Pattern overview](/learn/rpc/getting-started)
* [Node.js SDK Reference](/sdks/nodejs/reference)
* [Send Query](/sdks/nodejs/tutorials/query-send)
* [Handle Query](/sdks/nodejs/how-to/rpc/query-handle)
