A caching plugin for the SAP Cloud Application Programming Model (CAP) that improves performance by caching slow remote service calls, complex operations, and queries.
Please also read the introduction blog post: Boosting performance in SAP Cloud Application Programming Model (CAP) applications with cds-caching.
- Read-Through Caching – Transparently cache CQN queries, CAP requests, or function calls
- Pluggable Storage – In-memory, SQLite, Redis, PostgreSQL, SAP HANA, or CDS database
- Multi-Tenancy – Automatic tenant isolation for SAP BTP MTX deployments
- TTL & Tag Support – Time-based expiry and tag-based invalidation
- Compression – LZ4 or GZIP compression for cached data
- Metrics & Monitoring – Hit rates, latencies, key-level tracking, and an OData API
- Annotations – Declarative caching via
@cacheannotations on entities and functions
| Guide | Description |
|---|---|
| Programmatic API | Full API reference for cache operations |
| Key Management | Key templates, context awareness, custom keys |
| Metrics Guide | Statistics, monitoring, and performance tracking |
| OpenTelemetry Integration | Distributed tracing and metrics export |
| OData API | REST endpoints for management and monitoring |
| Dashboard | Setup and usage of the monitoring dashboard |
| Deployment Guide | SAP BTP deployment for Redis, PostgreSQL, HANA, CDS |
| Migration Guide | Upgrading from 0.x to 1.x, 1.1 to 1.2, and 1.2.x to 1.3.0 |
| Example Application | Sample app with caching patterns |
npm install cds-caching{
"cds": {
"requires": {
"caching": {
"impl": "cds-caching"
}
}
}
}This uses the in-memory store — no additional setup needed for development.
The plugin ships CDS entity definitions for database-backed features. These are auto-loaded conditionally based on your configuration — no manual model property or using from needed:
| Condition | Entities loaded | Purpose |
|---|---|---|
statistics block present |
Caches, Metrics, KeyMetrics |
Persist metrics and runtime config to the database |
store: 'cds' |
CacheStore |
Key-value table used by the CDS store adapter |
using from 'cds-caching/index.cds' |
CachingApiService + statistics entities |
OData API for the dashboard |
| None of the above | Nothing | Plugin works with external stores only |
The auto-loading works by injecting the relevant CDS files into cds.env.roots at plugin load time, before CAP compiles the model. This means cds deploy and cds build automatically pick up the required tables.
const cache = await cds.connect.to("caching")
// Key-value operations
await cache.set("bp:1000001", businessPartnerData, { ttl: 60000 })
const data = await cache.get("bp:1000001")
// Read-through caching for CQN queries
const { result } = await cache.rt.run(query, db, { ttl: 30000 })
// Read-through caching for remote services
const { result } = await cache.rt.send(request, remoteService, { ttl: 10000 })
// Function caching
const cachedFn = cache.rt.wrap("expensive-op", expensiveFunction, { ttl: 3600 })
const { result } = await cachedFn("param1")service MyService {
@cache: { ttl: 10000 }
entity Products as projection on db.Products;
@cache: { ttl: 10000, invalidateOnWrite: true }
entity Orders as projection on db.Orders;
@cache: { ttl: 60000 }
function getRecommendations() returns array of Products;
}When invalidateOnWrite is set, the cache for that entity is automatically cleared after any CREATE, UPDATE, or DELETE operation, so subsequent reads always return fresh data.
| Store | Config | Use Case | Adapter Package |
|---|---|---|---|
| In-Memory | "memory" |
Development, small-scale | Built-in |
| SQLite | "sqlite" |
Medium-size, single instance | @resolid/keyv-sqlite or @keyv/sqlite |
| Redis | "redis" |
Production, distributed | @keyv/redis |
| PostgreSQL | "postgres" |
Production, when Redis unavailable | @keyv/postgres |
| CDS Database | "cds" |
Production, HANA, multi-tenant | None (uses app's DB) |
| SAP HANA | "hana" |
Direct HANA connection | keyv-hana |
Recommendation: Use
store: 'cds'for CAP applications on SAP HANA — it reuses your app's DB connection, requires no extra packages, and supports multi-tenancy automatically. Usestore: 'redis'for best performance in distributed setups.
{
"cds": {
"requires": {
"caching": {
"impl": "cds-caching",
"namespace": "caching",
"store": "redis",
"compression": "lz4",
"throwOnErrors": false,
"transactionalOperations": false,
"credentials": { },
"statistics": {
"enabled": true,
"persistenceInterval": 60000
},
"keyManagement": {
"isUserAware": false,
"isTenantAware": false,
"isLocaleAware": false
}
}
}
}
}| Option | Default | Description |
|---|---|---|
store |
"memory" |
Storage backend (memory, sqlite, redis, postgres, hana, cds) |
namespace |
service name | Key prefix for store isolation |
compression |
none | "lz4" or "gzip" |
throwOnErrors |
false |
Whether basic operations throw on cache errors |
transactionalOperations |
false |
Isolate basic ops in dedicated cache transactions |
statistics |
none | When present, auto-loads the statistics data model and enables persistence (see Statistics & Monitoring) |
statistics.enabled |
false |
Enable metrics collection |
statistics.persistenceInterval |
60000 |
Interval (ms) for persisting hourly stats to the database |
keyManagement.isTenantAware |
false (auto true in MTX) |
Include tenant in cache keys |
keyManagement.isUserAware |
false |
Include user in cache keys |
keyManagement.isLocaleAware |
false |
Include locale in cache keys |
{
"cds": {
"requires": {
"caching": {
"impl": "cds-caching",
"store": "redis",
"[development]": {
"credentials": { "host": "localhost", "port": 6379 }
},
"[production]": {
"credentials": { "url": "redis://production-redis:6379" }
}
}
}
}
}For detailed key configuration and deployment instructions, see Key Management and Deployment Guide.
The plugin includes CachingApiService, an OData service for managing caches, browsing entries, and viewing metrics. It powers the dashboard and can be consumed by any OData client.
The easiest way to set this up is cds add caching-dashboard, which creates both the service exposure and the dashboard UI. To expose only the service without the dashboard, reference it in one of your .cds files:
using {plugin.cds_caching.CachingApiService} from 'cds-caching/index.cds';
annotate CachingApiService with @requires: 'authenticated-user';This automatically loads the required database entities (Caches, Metrics, KeyMetrics) via a transitive using from dependency — no additional configuration needed. Without this step, the service won't be served by CAP and the dashboard won't work.
cds-caching supports SAP BTP multi-tenant applications using @sap/cds-mtxs. When multitenancy is detected, the plugin automatically:
- Enables tenant-aware cache keys (
{tenant}:{hash}) - Defers database operations to request-time (avoids startup crashes without tenant context)
- Guards statistics persistence to only run within a tenant request context
{
"cds": {
"requires": {
"multitenancy": true,
"caching": {
"impl": "cds-caching",
"store": "cds"
}
}
}
}With store: 'cds', each tenant's cache data lives in its own HDI container — fully isolated by CAP's Service Manager.
Alternatively, use store: 'redis' for shared Redis with automatic tenant-prefixed keys:
{
"cds": {
"requires": {
"multitenancy": true,
"caching": {
"impl": "cds-caching",
"store": "redis",
"credentials": { "socket": { "host": "localhost", "port": 6379 } }
}
}
}
}
isTenantAwareis automatically set totruein MTX mode. Set"isTenantAware": falseinkeyManagementto explicitly opt out.
Deprecation Notice:
cache.run(),cache.send(),cache.wrap(),cache.exec()are deprecated since v1.0. Usecache.rt.run(),cache.rt.send(),cache.rt.wrap(),cache.rt.exec()instead. See Migration Guide.
const { result } = await cache.rt.run(
SELECT.from(BusinessPartners).where({ type: '2' }),
db,
{ ttl: 30000 }
)this.on('READ', BusinessPartners, async (req, next) => {
const bupa = await cds.connect.to('API_BUSINESS_PARTNER')
const { result } = await cache.rt.run(req, bupa, { ttl: 30000 })
return result
})this.prepend(() => {
this.on('READ', MyEntity, async (req, next) => {
const cache = await cds.connect.to("caching")
const { result } = await cache.rt.run(req, next)
return result
})
})// Wrap: create a cached version of a function
const cachedFn = cache.rt.wrap("bp-data", fetchBPData, { ttl: 3600, tags: ['bp'] })
const { result } = await cachedFn("1000001", true)
// Exec: immediate one-off execution with caching
const { result } = await cache.rt.exec("product", fetchProduct, ["1000001"], { ttl: 3600 })// Time-based (TTL)
await cache.set("key", value, { ttl: 60000 })
// Key-based
await cache.delete("bp:1000001")
// Tag-based
await cache.set("bp:1000001", data, { tags: [{ value: "bp-list" }] })
await cache.set("bp:1000002", data, { tags: [{ value: "bp-list" }] })
await cache.deleteByTag("bp-list")
// Dynamic tags from data
await cache.set("bp-list", bpArray, {
tags: [{ data: "businessPartner", prefix: "bp-" }]
})For annotation-based entity caching, use invalidateOnWrite to automatically clear all cached queries for an entity whenever its data changes:
@cache: { ttl: 10000, invalidateOnWrite: true }
entity CachedProducts as projection on db.Products;This registers after handlers for CREATE, UPDATE, and DELETE that call deleteByTag with an entity-level tag. All cached variants (filtered, sorted, paginated, single-entity) are invalidated at once.
For more usage patterns, error handling details, and TypeScript support, see Programmatic API.
To persist metrics to the database, add a statistics block to your configuration. This automatically loads the required data model (Caches, Metrics, KeyMetrics tables):
{
"cds": {
"requires": {
"caching": {
"impl": "cds-caching",
"store": "redis",
"statistics": {
"enabled": true,
"persistenceInterval": 60000
}
}
}
}
}You can also enable metrics at runtime:
const cache = await cds.connect.to("caching")
await cache.setMetricsEnabled(true)
await cache.setKeyMetricsEnabled(true)
const stats = await cache.getCurrentMetrics()To add the monitoring dashboard to your project, run:
cds add caching-dashboardThis copies a pre-built UI5 dashboard into your app/ folder and exposes the CachingApiService. After running cds watch, the dashboard is available at /caching-dashboard/index.html.
See the Dashboard Guide for details on features, security, and customization.
| API | Description |
|---|---|
| Programmatic API | JavaScript methods for cache operations |
| OData API | REST endpoints for monitoring and management |
Contributions are welcome! Please submit pull requests to the repository.
This project is licensed under the MIT License - see the LICENSE file for details.
