Helpers & Cache
Context Reference - Helpers & Cache Utility functions for common tasks and distributed caching operations. Helpers Utility functions for common tasks. All helper functions require await . JWT Token Generation const token = await $ctx.$helpers.$jwt(payload, expiration); // Example
Context Reference - Helpers & Cache
Utility functions for common tasks and distributed caching operations.
Helpers
Utility functions for common tasks. All helper functions require await.
JWT Token Generation
const token = await $ctx.$helpers.$jwt(payload, expiration);
// Example
const token = await $ctx.$helpers.$jwt(
{ userId: 123, email: '[email protected]' },
'7d' // Expires in 7 days
);
Expiration formats:
- '15m' - 15 minutes
- '1h' - 1 hour
- '1d' - 1 day
- '7d' - 7 days
- '30d' - 30 days
Password Hashing
// Hash password
const hash = await $ctx.$helpers.$bcrypt.hash(plainPassword);
// Verify password
const isValid = await $ctx.$helpers.$bcrypt.compare(plainPassword, hashedPassword);
// Example
const hashedPassword = await $ctx.$helpers.$bcrypt.hash('myPassword123');
const isValid = await $ctx.$helpers.$bcrypt.compare('myPassword123', hashedPassword);
Auto Slug Generation
Generate URL-friendly slugs from text.
const slug = $ctx.$helpers.autoSlug(text);
// Example
const slug = $ctx.$helpers.autoSlug('My Product Name');
// Result: 'my-product-name'
Crypto Helpers
Use $ctx.$helpers.$crypto for bounded cryptographic helpers inside hooks, handlers, flows, and websocket scripts.
const id = $ctx.$helpers.$crypto.randomUUID();
const token = $ctx.$helpers.$crypto.randomBytes(32, 'base64url');
const digest = $ctx.$helpers.$crypto.sha256('payload');
const signature = $ctx.$helpers.$crypto.hmacSha256('payload', 'shared-secret');
Supported encodings for randomBytes, sha256, and hmacSha256 are hex, base64, and base64url. randomBytes is capped to 4096 bytes.
Generate SSH keys with the same crypto helper:
const keyPair = await $ctx.$helpers.$crypto.generateSshKeyPair('[email protected]');
// keyPair.publicKey is OpenSSH format.
// keyPair.privateKey is RSA PKCS#1 PEM.
Do not use legacy $ctx.$helpers.$ssh or manual encryption helpers in new scripts. Database values that need encryption at rest should be stored in columns marked isEncrypted=true; scripts read and write plaintext values.
Sleep Helper
Pause a dynamic script for a bounded duration. The runtime clamps the delay between 0 and 30000 milliseconds.
await $ctx.$helpers.$sleep(1000);
Storage Helpers
Upload files to storage.
const fileResult = await $ctx.$storage.$upload({
originalname: 'image.jpg',
filename: 'custom-filename.jpg',
mimetype: 'image/jpeg',
buffer: fileBuffer,
size: 1024000,
folder: 123, // Optional: folder ID
storageConfig: 1, // Optional: storage config ID
title: 'My Image', // Optional
description: 'Image description' // Optional
});
File Update Helper
Update existing files.
await $ctx.$storage.$update(fileId, {
buffer: newFileBuffer,
originalname: 'new-name.jpg',
mimetype: 'image/jpeg',
folder: 456,
title: 'Updated Title'
});
File Delete Helper
Delete files.
await $ctx.$storage.$delete(fileId);
Register an object that already exists in the configured storage backend without uploading bytes:
await $ctx.$storage.$registerFile({
filename: 'backup.sql.gz',
mimetype: 'application/gzip',
location: 'backups/project-1/backup.sql.gz',
size: 1024,
storageConfig: 1,
});
Rate Limiting
Protect your API routes with flexible rate limiting using Redis sliding window algorithm.
const result = await $ctx.$helpers.$rateLimit.byIp({
maxRequests: 100,
perSeconds: 60
});
if (!result.allowed) {
$ctx.$throw['429'](`Rate limit exceeded. Try again in ${result.retryAfter}s`);
}
Rate Limit Templates
| Method | Key Format | Description |
|---|---|---|
byIp(options) |
ip:{ip}:{route} |
Rate limit by client IP per route |
byUser(options) |
user:{userId}:{route} |
Rate limit by authenticated user per route |
byRoute(options) |
route:{route} |
Rate limit globally per route (all users/IPs share limit) |
byIpGlobal(options) |
ip:{ip} |
Rate limit by IP across all routes |
byUserGlobal(options) |
user:{userId} |
Rate limit by user across all routes |
check(key, options) |
Custom key | Rate limit with custom key |
reset(key) |
- | Reset rate limit for a key |
status(key, options) |
- | Check rate limit status without incrementing |
Options
{
maxRequests: 100, // Maximum requests allowed in the window
perSeconds: 60 // Time window in seconds
}
Result Object
{
allowed: boolean, // Whether the request is allowed
remaining: number, // Remaining requests in window
resetAt: number, // Unix timestamp when window resets
retryAfter: number, // Seconds to wait before retry (0 if allowed)
limit: number, // The max requests limit
window: number // The window in seconds
}
Examples
Rate Limit Login Attempts by IP:
// In preHook for POST /auth/login
const result = await $ctx.$helpers.$rateLimit.byIp({
maxRequests: 5,
perSeconds: 60
});
if (!result.allowed) {
$ctx.$throw['429'](`Too many login attempts. Try again in ${result.retryAfter}s`);
}
Rate Limit API by User:
// In preHook for API routes
const result = await $ctx.$helpers.$rateLimit.byUser({
maxRequests: 1000,
perSeconds: 3600 // 1 hour
});
if (!result.allowed) {
$ctx.$throw['429'](`API rate limit exceeded. Try again in ${result.retryAfter}s`);
}
Skip Rate Limit for Admins:
if (!$ctx.$user?.isRootAdmin) {
const result = await $ctx.$helpers.$rateLimit.byIp({
maxRequests: 100,
perSeconds: 60
});
if (!result.allowed) {
$ctx.$throw['429']('Rate limit exceeded');
}
}
Custom Key for Specific Resource:
const resourceId = $ctx.$params.id;
const result = await $ctx.$helpers.$rateLimit.check(
`resource:${resourceId}:${$ctx.$user?.id || $ctx.$req.ip}`,
{ maxRequests: 10, perSeconds: 60 }
);
if (!result.allowed) {
$ctx.$throw['429']('Too many requests to this resource');
}
Check Status Without Incrementing:
// Check if rate limited without counting the request
const status = await $ctx.$helpers.$rateLimit.status(
`ip:${$ctx.$req.ip}:/api/expensive`,
{ maxRequests: 5, perSeconds: 3600 }
);
if (!status.allowed) {
$ctx.$throw['429']('Daily limit reached');
}
Reset Rate Limit After Successful Action:
// In postHook after successful action
await $ctx.$helpers.$rateLimit.reset(`ip:${$ctx.$req.ip}:/auth/forgot-password`);
Cache
Distributed user-cache and locking operations. All cache functions require await.
$ctx.$cache stores application data created by handlers, hooks, flows, websocket scripts, and the @CACHE macro. Use logical keys such as user:123; do not include NODE_NAME, user_cache:, or any Redis namespace prefix. When Redis user cache is enabled, Enfyra stores those values under the current app namespace as NODE_NAME:user_cache:*.
The user cache has a soft allocation controlled by REDIS_USER_CACHE_LIMIT_MB (default 30). If the allocation is exceeded, Enfyra evicts least-recently-used user-cache keys only. System Redis keys such as runtime cache snapshots, BullMQ queues, Socket.IO, runtime telemetry, and locks are not counted or evicted by this quota.
For high-volume proxy checks, cache only derived catalog or access data with a short explicit TTL. Keep credentials, token revocation, and other security decisions live; a cache hit must never be treated as proof that a credential is still valid.
Get Cached Value
const cachedValue = await $ctx.$cache.get(key);
// Example
const user = await $ctx.$cache.get('user:123');
Set Cached Value
// Set with TTL (time-to-live in milliseconds)
await $ctx.$cache.set(key, value, ttlMs);
// Example - cache for 5 minutes
await $ctx.$cache.set('user:123', userData, 300000);
// Set without expiration
await $ctx.$cache.setNoExpire(key, value);
Prefer set(key, value, ttlMs) with a TTL for operational data. setNoExpire keeps a value persistent from a TTL perspective, but it can still be evicted if the user-cache allocation is exceeded.
Distributed Locking
Acquire and release locks for critical operations.
// Acquire lock
const lockAcquired = await $ctx.$cache.acquire(key, value, ttlMs);
// Release lock
const released = await $ctx.$cache.release(key, value);
// Example
const lockKey = `user-lock:${userId}`;
const lockValue = $ctx.$user.id;
const acquired = await $ctx.$cache.acquire(lockKey, lockValue, 10000); // 10 seconds
if (acquired) {
try {
// Critical operation here
} finally {
await $ctx.$cache.release(lockKey, lockValue);
}
}
Check Cache Exists
const exists = await $ctx.$cache.exists(key, value);
// Example
const lockExists = await $ctx.$cache.exists('user-lock:123', 'user-456');
Delete Cache Key
await $ctx.$cache.deleteKey(key);
// Example
await $ctx.$cache.deleteKey('user:123');
Next Steps
- See Logging & Error Handling for logging and error throwing
- Check Advanced Features for file uploads and more
- Learn about Cache Operations for detailed cache patterns