# Nodejs API Client Caching

> Pattern for building Node.js API clients with TTL-based caching and no external dependencies Use when this capability is needed.

- Skill: `tomevault-io/nodejs-api-client-caching` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/nodejs-api-client-caching`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/nodejs-api-client-caching/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/nodejs-api-client-caching

---


## Context
When building API clients in Node.js (especially VS Code extensions or CLI tools that must stay zero/low-dependency), use the built-in `https` module with a simple TTL cache. This avoids polyfill issues with `fetch` in CommonJS environments and keeps the dependency footprint minimal.

## Patterns

### TTL Cache with Forced Refresh
Store fetched data alongside a `fetchedAt` timestamp. Check expiry on access. Expose `forceRefresh` parameter and `invalidateCache()` method for external triggers (e.g., file watchers).

```typescript
interface Cache<T> {
    data: T;
    fetchedAt: number;
}

class ApiService {
    private cache: Cache<MyData[]> | null = null;
    private cacheTtlMs: number;

    private isCacheExpired(): boolean {
        if (!this.cache) return true;
        return Date.now() - this.cache.fetchedAt > this.cacheTtlMs;
    }

    async getData(forceRefresh = false): Promise<MyData[]> {
        if (!forceRefresh && this.cache && !this.isCacheExpired()) {
            return this.cache.data;
        }
        const data = await this.fetchFromApi();
        this.cache = { data, fetchedAt: Date.now() };
        return data;
    }

    invalidateCache(): void {
        this.cache = null;
    }
}
```

### Node.js HTTPS GET with Promise Wrapper
Wrap `https.request` in a typed Promise. Set `User-Agent` (required by GitHub API), handle status codes, and parse JSON response.

```typescript
private apiGet<T>(path: string): Promise<T> {
    return new Promise((resolve, reject) => {
        const url = new URL(path, this.baseUrl);
        const req = https.request({
            hostname: url.hostname,
            path: url.pathname + url.search,
            method: 'GET',
            headers: { 'User-Agent': 'MyApp', 'Accept': 'application/json' },
        }, (res) => {
            const chunks: Buffer[] = [];
            res.on('data', (chunk: Buffer) => chunks.push(chunk));
            res.on('end', () => {
                const body = Buffer.concat(chunks).toString('utf-8');
                if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
                    resolve(JSON.parse(body) as T);
                } else {
                    reject(new Error(`API returned ${res.statusCode}`));
                }
            });
        });
        req.on('error', reject);
        req.end();
    });
}
```

### Graceful Pagination
When paginating API results, catch errors per-page. If a later page fails, return the pages already fetched rather than losing everything.

## Anti-Patterns
- **Using `node-fetch` or `axios` in zero-dep projects** — Use `https` module instead.
- **Global mutable cache** — Keep cache as instance state so tests can create isolated instances.
- **Infinite cache without invalidation** — Always provide `invalidateCache()` and TTL expiry.
- **Tight coupling to auth mechanism** — Accept token via constructor/setter, don't import VS Code APIs directly.

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/csharpfritz) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-11 -->

