opticore-cache
v1.0.2
Published
Highly configurable caching system compatible with OptiCore.js
Readme
opticore-cache
Enterprise‑grade Universal HTTP Cache for OptiCoreJs and Node.js
High‑performance, flexible and production‑ready HTTP caching layer
compatible with Express · Fetch · Axios · Native Node HTTP · cURL
Table of Contents
- Why opticore-cache ?
- Key Features
- Installation
- Quick Start
- HttpCacheFactory Usage
- Configuration Options
- Advanced Usage
- API Reference
- Testing
- Security
- License
- Contributing
Why opticore-cache?
Modern applications require:
- High availability
- Reduced external API load
- Faster response times
- Observability & metrics
- Flexible storage strategies
opticore-cache provides a unified abstraction layer over multiple HTTP
clients with an extensible and configurable caching engine.
Key Features
- Automatic HTTP response caching
- Multi-client support (Fetch, Axios, Node HTTP, cURL)
- Memory, Disk or Hybrid storage
- Per-request TTL override
- Pattern-based invalidation (wildcards supported)
- Built-in statistics & hit-rate tracking
- Namespaces for multi-tenant systems
- TypeScript-first design
- Instance lifecycle management (create / destroy)
Installation
npm install opticore-cacheOptional dependencies:
npm install axios expressQuick Start
import { HttpCacheFactory } from "opticore-cache";
const cache = HttpCacheFactory.create("my-app");
const data = await cache.getWithCache("https://api.example.com/users");
console.log(data);[HttpCacheFactory Usage
The HttpCacheFactory is responsible for:
- Creating configured cache instances
- Managing instance reuse
- Supporting multiple HTTP clients
- Destroying instances when needed
- Adapting existing cache services
Create with Fetch (default)
const cache = HttpCacheFactory.create("app-fetch", {
clientType: "fetch",
storageType: "disk",
diskDir: "./storage/cache",
defaultTTL: 60000
});Create with Axios
const axiosCache = HttpCacheFactory.create("app-axios", {
clientType: "axios",
clientOptions: {
baseURL: "https://jsonplaceholder.typicode.com",
timeout: 5000
}
});Create with Node HTTP
const nodeCache = HttpCacheFactory.create("app-node", {
clientType: "node-http",
clientOptions: {
timeout: 8000
}
});Create with cURL
const curlCache = HttpCacheFactory.create("app-curl", {
clientType: "curl",
clientOptions: {
timeout: 10000
}
});Destroy an Instance
HttpCacheFactory.destroy("app-fetch", "fetch");Adapt Existing Cache
const adapted = HttpCacheFactory.createFromExistingCache(
customCacheService,
"custom-namespace"
);Configuration Options
When creating an instance of the HTTP cache service using HttpCacheFactory.create(), you can pass an options object to customize its behavior. Below are the available options:
| Option | Type | Description | Exemple |
|-------------|--------------------|-------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
| storageType | 'memory' ou 'disk' | Defines the storage type: in-memory (faster, volatile) or on disk (persistent). | 'disk' |
| diskDir | string | Required if storageType: 'disk'. Path to the directory where cache files will be stored. | 'src/core/cache/app' |
| defaultTTL | number | Default time-to-live for cache entries (in milliseconds). Used if no timeToLive is provided in a request. | 60000 (1 minute) |
| maxSize | number | Maximum number of entries in the cache. When exceeded, the oldest entries are removed (LRU). | 100 |
| namespace | string | (Optional) Namespace used to isolate cache keys. Useful when using multiple instances. | 'external-api' |
| serializer | object | (Optional) Overrides the default serialization/deserialization functions (JSON by default). | {{ serialize: JSON.stringify, deserialize: JSON.parse }} |
Example initialization with all options:
const httpCache = HttpCacheFactory.create('external-api', {
storageType: 'disk',
diskDir: 'src/core/cache/demo',
defaultTTL: 10000, // 10 secondes
maxSize: 100,
namespace: 'api-v1'
});Advanced Usage
The IHttpCacheService service offers several advanced features for fine-grained HTTP cache control.
1. Per-Request Cache Bypass
You can bypass the cache for a specific request using the bypassCache option. This forces an API call and updates the cache with the new response.
const cacheOptions = {
bypassCache: req.query.bypass === 'true', // e.g. ?bypass=true
timeToLive: 60000
};
const data = await httpCache.getWithCache(url, {}, cacheOptions);2. Custom Time-To-Live
The timeToLive option lets you define a custom expiration duration for a specific request, overriding the default value.
3. Caching POST Requests
The postWithCache method allows you to cache the response of a POST request. This can be useful when the API always returns the same response for identical input data.
const response = await httpCache.postWithCache(
url,
{ title, body, userId }, // request body
{}, // additional headers
true, // cache the response
{ timeToLive: 30000 } // cache options
);4. Pattern-Based Invalidation
You can invalidate all cache entries whose key matches a given pattern. This is useful for refreshing a set of related resources.
const countExact = await httpCache.invalidateCache('/posts/42'); // Invalidates an exact key
const countPattern = await httpCache.invalidateCache('/posts/*'); // Invalidates all keys starting with /posts/5. Cache Statistics
Retrieve information about the current state of the cache (number of entries, size, etc.).
const stats = await httpCache.getStats();
console.log(stats);
// Example output:
// { size: 45, maxSize: 100, hits: 120, misses: 30, ... }6. Full Cache Flush
To completely clear the cache (all entries), use:
await httpCache.clearHttpCache();7. Response Metadata
Every object returned by getWithCache or postWithCache includes a _metadata property containing information about the origin of the data.
const data = await httpCache.getWithCache(url);
console.log(data._metadata);
// {
// source: 'cache' | 'api',
// cached: boolean,
// createdAt: timestamp,
// ttl: number
// }8. Error Handling
The service encapsulates both network errors and cache errors. You can intercept them as shown in the controller example.
9. Usage with Dynamic Parameters
Build the URL with variable parameters (such as postId) to create unique cache keys per resource.
const url = `https://api.example.com/posts/${postId}`;Complete example
export class UserController {
private httpCache: IHttpCacheService;
constructor() {
this.httpCache = HttpCacheFactory.create('external-api', {
storageType: 'disk',
diskDir: 'src/core/cache/demo',
defaultTTL: 10000,
maxSize: 100
});
}
public async getPostById(req: Request, res: Response) {
const postId = req.params.id;
const bypassCache = req.query.bypass === 'true'; // Allows bypassing the cache with ?bypass=true
try {
const url = `https://jsonplaceholder.typicode.com/posts/${postId}`;
const cacheOptions = {
bypassCache: bypassCache,
timeToLive: 60000 // 1 minute (or use the default value)
};
console.log(`Requête pour le post ${postId}, bypass: ${bypassCache}`);
const post = await this.httpCache.getWithCache(url, {}, cacheOptions);
// Add information about the source (cache or API)
const source = post?._metadata?.source || 'unknown';
const cached = post?._metadata?.cached || false;
res.json({
success: true,
data: post,
source: source,
cached: cached,
timestamp: new Date().toISOString()
});
} catch (error: any) {
console.error(`Erreur lors de la récupération du post ${postId}:`, error);
res.status(500).json({
success: false,
error: error.message
});
}
}
// Method to retrieve comments for a post, using cache
public async getCommentsByPostId(req: Request, res: Response) {
const postId = req.params.id;
const bypassCache = req.query.bypass === 'true';
try {
const url = `https://jsonplaceholder.typicode.com/posts/${postId}/comments`;
const cacheOptions = {
bypassCache: bypassCache,
timeToLive: 300000 // 5 minutes
};
const comments: unknown = await this.httpCache.getWithCache(url, {}, cacheOptions);
const source: any = comments?._metadata ? comments?._metadata.source : 'unknown';
const cached: any = comments?._metadata ? comments?._metadata?.cached : false;
res.json({
success: true,
data: comments,
source: source,
cached: cached,
count: Array.isArray(comments) ? comments.length : 1,
timestamp: new Date().toISOString()
});
} catch (error: any) {
console.error(`Erreur pour les commentaires du post ${postId}:`, error);
res.status(500).json({
success: false,
error: error.message
});
}
}
// Method for making a POST request with optional caching
public async createPost(req: Request, res: Response) {
const { title, body, userId, cacheResponse } = req.body;
try {
const url = 'https://jsonplaceholder.typicode.com/posts';
const response = await this.httpCache.postWithCache(
url,
{ title, body, userId },
{},
cacheResponse || true,
{ timeToLive: 30000 }
);
res.json({
success: true,
data: response,
cached: cacheResponse || false,
timestamp: new Date().toISOString()
});
} catch (error: any) {
console.error('Erreur création de post:', error);
res.status(500).json({
success: false,
error: error.message
});
}
}
// Method for obtaining HTTP cache statistics
public async getCacheStats(req: Request, res: Response) {
try {
const stats = await this.httpCache.getStats();
res.json(stats);
} catch (error: any) {
res.status(500).json({
success: false,
error: error.message
});
}
}
// Method to clear the HTTP cache
public async clearHttpCache(req: Request, res: Response) {
try {
await this.httpCache.clearHttpCache();
res.json({
success: true,
message: 'Cache HTTP vidé avec succès',
timestamp: new Date().toISOString()
});
} catch (error: any) {
res.status(500).json({
success: false,
error: error.message
});
}
}
// Method to invalidate a pattern in the HTTP cache
public async invalidateCachePattern(req: Request, res: Response) {
const pattern = req.query.pattern as string;
if (!pattern) {
return res.status(400).json({
success: false,
error: 'Le paramètre "pattern" est requis'
});
}
try {
const count = await this.httpCache.invalidateCache(pattern);
res.json({
success: true,
message: `Cache invalidé pour le pattern: ${pattern}`,
invalidatedCount: count,
timestamp: new Date().toISOString()
});
} catch (error: any) {
res.status(500).json({
success: false,
error: error.message
});
}
}
} API Reference
Factory
HttpCacheFactory.create(appName?: string, config?)Returns: IHttpCacheService
Core Methods
getWithCache
await cache.getWithCache("https://api.example.com/data");postWithCache
await cache.postWithCache("https://api.example.com/data", payload, {}, true);invalidateCache
await cache.invalidateCache("users:*");clearHttpCache
await cache.clearHttpCache();getStats
const stats = await cache.getStats();
console.log(stats.hitRate);Testing
Run the example project by this command :
tsx example/src/bootstrap.ts
then run :
Method POST :
curl -X POST "http://localhost:4200/api/users/posts" \
-H "Content-Type: application/json" \
-d '{
"url": "https://jsonplaceholder.typicode.com/posts",
"data": {
"title": "test",
"body": "content",
"userId": 2
},
"cacheResponse": true
}'
Response :
{
"success":true,
"data": {
"id": 101,
"_metadata": {
"cached": true,
"timestamp": "2026-02-20T10:56:25.444Z",
"url": "https://jsonplaceholder.typicode.com/posts",
"method": "POST",
"responseTime":433
}
},
"cached": true,
"timestamp": "2026-02-20T10:56:25.444Z"
}
Method GET
curl -X GET "http://localhost:4200/api/users/posts/1"
Response :
{
"success": true,
"data": {
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto",
"_metadata": {
"cached": false,
"timestamp": "2026-02-20T14:20:38.751Z",
"url": "https://jsonplaceholder.typicode.com/posts/1",
"method": "GET",
"responseTime": 103,
"cacheHit": false
}
},
"source": "unknown",
"cached": false,
"timestamp": "2026-02-20T14:20:38.751Z"
}Security
- No automatic caching of sensitive headers
- Namespace isolation supported
- Manual invalidation available
- Safe retry mechanism
License
MIT
Contributing
Contributions are welcome.
- Fork the repository
- Create a feature branch
- Submit a Pull Request
Built for scalable OptiCoreJs and Node.js applications.
