# How to Implement Caching Strategies for API Responses in LINEJS

> Learn to implement effective caching strategies for API responses in LINEJS. Boost performance by using persistent storage, TTL memoization, and extending the LiffService token cache.

- Repository: [Evex  Developers/linejs](https://github.com/evex-dev/linejs)
- Tags: how-to-guide
- Published: 2026-03-01

---

**Implement caching in LINEJS by implementing the `BaseStorage` interface for persistent storage, wrapping API calls with TTL-based memoization helpers, and extending the built-in `LiffService` token cache to survive page reloads.**

LINEJS provides a modular architecture for interacting with the LINE platform, but frequent API calls can trigger rate limits and degrade performance. By leveraging the library’s pluggable storage system and built-in buffering mechanisms, you can implement robust **caching strategies for API responses in LINEJS** that reduce latency and preserve bandwidth across sessions.

## Understanding the Caching Layers

LINEJS operates through three distinct layers where caching improves efficiency:

- **Push connection layer**: Buffers fragmented TCP packets in `Conn.cacheData` before reassembly
- **Service layer**: Stores LIFF access tokens in `LiffService.liffTokenCache` to avoid repeated authentication
- **Request layer**: Caches HTTP responses via `RequestClient.requestCore` for idempotent operations like profile fetching

Each layer serves a different purpose, from low-level packet integrity to high-level API optimization.

## Choosing a Persistent Storage Backend

By default, LINEJS uses `MemoryStorage`, which loses data on page reload. For cross-session caching, implement the `BaseStorage` interface backed by IndexedDB or another persistent store.

In [`packages/linejs/base/storage/base.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/storage/base.ts), the abstract contract requires four methods: `get`, `set`, `delete`, and `clear`. Here is a complete IndexedDB implementation:

```typescript
// src/custom-storage.ts
import { BaseStorage, type Storage } from "@evex/linejs/storage";

export class IndexedDBStorage extends BaseStorage {
  private db: IDBDatabase | null = null;

  constructor() {
    super();
    const request = indexedDB.open("linejs-cache", 1);
    request.onupgradeneeded = () => {
      request.result.createObjectStore("kv");
    };
    request.onsuccess = () => (this.db = request.result);
  }

  async set(key: Storage["Key"], value: Storage["Value"]): Promise<void> {
    await this.ensureDB();
    const tx = this.db!.transaction("kv", "readwrite");
    tx.objectStore("kv").put(value, key);
    await tx.complete;
  }

  async get(key: Storage["Key"]): Promise<Storage["Value"] | undefined> {
    await this.ensureDB();
    const tx = this.db!.transaction("kv", "readonly");
    const result = await tx.objectStore("kv").get(key);
    await tx.complete;
    return result ?? undefined;
  }

  async delete(key: Storage["Key"]): Promise<void> {
    await this.ensureDB();
    const tx = this.db!.transaction("kv", "readwrite");
    tx.objectStore("kv").delete(key);
    await tx.complete;
  }

  async clear(): Promise<void> {
    await this.ensureDB();
    const tx = this.db!.transaction("kv", "readwrite");
    tx.objectStore("kv").clear();
    await tx.complete;
  }

  private async ensureDB(): Promise<void> {
    if (!this.db) {
      await new Promise((resolve) => setTimeout(resolve, 100));
      return this.ensureDB();
    }
  }
}

```

Inject this storage into `BaseClient` at initialization. In [`packages/linejs/base/core/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/mod.ts), the constructor accepts a `storage` property:

```typescript
import { Client } from "@evex/linejs/client";
import { IndexedDBStorage } from "./custom-storage";

const client = new Client({
  device: "iOS",
  storage: new IndexedDBStorage(), // Persists across reloads
});

```

## Implementing TTL-Based API Response Caching

For general API responses, create a memoization wrapper that stores JSON payloads with expiration timestamps. This strategy targets the `RequestClient.requestCore` method found in [`packages/linejs/base/request/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/request/mod.ts).

The helper checks storage before executing the network request:

```typescript
// src/cache-helper.ts
import type { BaseClient } from "@evex/linejs/client";

const DEFAULT_TTL_MS = 5 * 60 * 1000; // 5 minutes

export async function cachedRequest<T = unknown>(
  client: BaseClient,
  cacheKey: string,
  request: () => Promise<T>,
  ttlMs: number = DEFAULT_TTL_MS,
): Promise<T> {
  const now = Date.now();
  
  // Check for valid cached entry
  const raw = await client.storage.get(cacheKey) as
    | { expires: number; value: T }
    | undefined;

  if (raw && raw.expires > now) {
    client.log("cache-hit", { key: cacheKey });
    return raw.value;
  }

  // Execute request and store with new expiry
  const value = await request();
  await client.storage.set(cacheKey, {
    expires: now + ttlMs,
    value,
  });

  client.log("cache-miss", { key: cacheKey });
  return value;
}

```

Use this helper to cache expensive calls like profile retrieval:

```typescript
import { cachedRequest } from "./cache-helper";

async function getMyProfile(client: BaseClient) {
  return cachedRequest(
    client,
    "profile:self",
    () => client.request.request(
      [[12, 1, []]],      // Empty args for getProfile()
      "getProfile",
      4,                  // ProtocolKey for Profile API
      true,
      "/S3"
    ),
    60000                 // 1 minute TTL for fresh profile data
  );
}

```

## Caching LIFF Access Tokens

The `LiffService` class maintains an in-memory map called `liffTokenCache` defined in [`packages/linejs/base/service/liff/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/service/liff/mod.ts). To make tokens survive page reloads, delegate the cache to your persistent storage implementation.

Create a dedicated cache manager:

```typescript
// src/liff-cache.ts
import type { BaseClient } from "@evex/linejs/client";

export class LiffCache {
  constructor(private client: BaseClient) {}

  async get(to: string, liffId: string): Promise<string | undefined> {
    const key = `liff-token:${to}:${liffId}`;
    return (await this.client.storage.get(key)) as string | undefined;
  }

  async set(to: string, liffId: string, token: string): Promise<void> {
    const key = `liff-token:${to}:${liffId}`;
    await this.client.storage.set(key, token);
  }
}

```

Integrate this into your LIFF workflow to bypass redundant `issueLiffView` calls:

```typescript
// Inside LiffService.sendLiff modification
if (!this.liffTokenCache[to] || forceIssue) {
  token = await this.getLiffToken({ chatMid: to, liffId: this.liffId });
  await this.liffCache.set(to, this.liffId, token); // Persist token
} else {
  token = this.liffTokenCache[to];
}

```

## Handling Push Connection Buffers

At the transport layer, `Conn` in [`packages/linejs/base/push/conn.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/push/conn.ts) uses `cacheData` to reassemble fragmented TCP packets. When partial frames arrive, the buffer concatenates fragments until a complete packet is ready:

```typescript
// Conceptual excerpt from Conn.ts
if (this.isNotFinished) {
  const concat = new Uint8Array(this.cacheData.length + data.length);
  concat.set(this.cacheData);
  concat.set(data, this.cacheData.length);
  data = concat; // Full packet assembled
}

```

While you rarely modify this layer directly, understanding `Conn.cacheData` helps when implementing prefetch logic for media referenced in push payloads.

## Complete Integration Example

Combine these strategies for a production-ready client:

```typescript
import { Client } from "@evex/linejs/client";
import { IndexedDBStorage } from "./custom-storage";
import { cachedRequest } from "./cache-helper";

const client = new Client({
  device: "iOS",
  storage: new IndexedDBStorage(),
});

// Cache friend list for 10 minutes
async function getFriends() {
  return cachedRequest(
    client,
    "friends:list",
    () => client.request.request([], "getFriends", 3, true, "/S4"),
    10 * 60 * 1000
  );
}

// Send LIFF message using persistent token cache
async function notifyChat(chatMid: string, text: string) {
  await client.liff.sendLiff({
    to: chatMid,
    messages: [{ type: "text", text }],
  });
}

// Usage
await getFriends();      // Hits cache on subsequent calls
await notifyChat("u1234...", "Hello!"); // Token cached across reloads

```

## Summary

- **Implement `BaseStorage`** to replace `MemoryStorage` with IndexedDB or other persistent backends for cross-session caching.
- **Wrap API calls** with a TTL-based memoization helper targeting `RequestClient.requestCore` to reduce redundant network traffic.
- **Extend `LiffService`** token caching by delegating `liffTokenCache` to persistent storage, eliminating extra authentication round-trips.
- **Leverage `Conn.cacheData`** for understanding packet-level buffering, though modification is rarely required for API caching.
- **Scope cache keys** per user (e.g., `user:<mid>:profile`) and invalidate entries after mutating operations to prevent stale data.

## Frequently Asked Questions

### How does LINEJS handle storage by default?

LINEJS uses `MemoryStorage` from [`packages/linejs/base/storage/memory.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/storage/memory.ts) by default, which stores all cached data in RAM. This implementation clears automatically when the application closes or the page reloads, making it suitable only for temporary session data.

### Can I use Redis or another external cache with LINEJS?

Yes. Implement the `BaseStorage` interface from [`packages/linejs/base/storage/base.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/storage/base.ts) with async methods that call your Redis client. Since `BaseClient` accepts any object matching the `BaseStorage` contract, you can inject Redis, DynamoDB, or any other external store without modifying the core library.

### What is the difference between `Conn.cacheData` and API response caching?

`Conn.cacheData` in [`packages/linejs/base/push/conn.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/push/conn.ts) is a low-level byte buffer that reassembles fragmented TCP packets for the push connection protocol. It handles transport-layer integrity. API response caching operates at the application layer, storing parsed JSON results from `RequestClient` to avoid redundant HTTP requests.

### How should I invalidate cached API responses after sending messages?

Delete the relevant cache keys immediately after successful mutating operations. For example, after calling `sendMessage`, remove entries matching `chat:<id>:messages` or `chat:<id>:history` from your storage implementation to force fresh data on the next read.