Performance

Caching

Add caching to any route with one line: automatic TTL, dynamic keys, and smart invalidation, with no manual key management.

Overview

Without caching, every request hits your database or expensive operations. With caching, repeated responses are served instantly, and MoroJS handles the keys, expiration, and invalidation for you.

Add caching with one line
1app.get('/users')
2  .cache({ ttl: 60 }) // Cache for 60 seconds
3  .handler(() => {
4    // This only runs if not cached
5    return getAllUsers();
6  });

Without caching

  • Every request hits the database
  • Slow response times
  • High server load
  • Manual cache management

With MoroJS

  • Instant responses from cache
  • Reduced database load
  • Automatic TTL management
  • One-line setup

Fast

Instant responses from cache. No database queries for cached data.

Automatic

TTL-based expiration. No manual cache management needed.

Simple

One line of code. Dynamic keys. Smart invalidation.

How It Works

MoroJS caching automatically stores responses based on cache keys and TTL (time-to-live) values. When a request comes in, it checks the cache first. If found, it returns immediately. If not, it executes the handler and stores the result.

Route-Level Caching

Basic Route Caching
1import { createApp, z } from '@morojs/moro';
2
3const app = await createApp();
4
5// Simple caching with TTL
6app.get('/users')
7  .query(z.object({
8    limit: z.coerce.number().min(1).max(100).default(10),
9    search: z.string().optional()
10  }))
11  .cache({ ttl: 60, key: 'users-list' }) // Cache for 60 seconds
12  .handler((req, res) => {
13    // This will only run if not cached
14    const users = getAllUsers(req.query);
15    return { success: true, data: users };
16  });
17
18// Dynamic cache keys with parameters
19app.get('/users/:id')
20  .cache({ ttl: 300, key: (req) => `user-${req.params.id}` }) // 5 minutes
21  .handler((req, res) => {
22    const user = getUserById(req.params.id);
23    return { success: true, data: user };
24  });
Different TTLs for Different Data
1// Fast-changing data - short cache
2app.get('/data/fast')
3  .cache({ ttl: 30 }) // 30 seconds
4  .handler(() => getFastChangingData());
5
6// Stable data - long cache
7app.get('/data/slow')
8  .cache({ ttl: 3600 }) // 1 hour
9  .handler(() => getSlowChangingData());

Advanced Caching

For advanced use cases, you can integrate external caching solutions like Redis, implement tag-based invalidation, and manage cache manually.

Invalidating on write
1import { createCacheAdapter } from '@morojs/moro';
2
3// Hold your own adapter reference — there is no global cache accessor.
4const cache = createCacheAdapter('memory');
5
6app.post('/users')
7  .body(CreateUserSchema)
8  .handler(async (req, res) => {
9    const user = await createUser(req.body);
10
11    // Adapters key on strings; delete the entries a write invalidates.
12    await cache.del('users:list');
13    await cache.del(`users:${user.id}`);
14
15    res.json({ user });
16  });
Manual Cache Management
1import { createCacheAdapter, MemoryCacheAdapter } from '@morojs/moro';
2
3// 'memory' | 'redis' | 'file', or construct an adapter directly
4const cache = createCacheAdapter('redis', { url: process.env.REDIS_URL });
5const local = new MemoryCacheAdapter();
6
7// The full CacheAdapter surface
8await cache.set('key', { data: 'value' }, 3600); // ttl in seconds
9const cached = await cache.get('key');
10const present = await cache.exists('key');
11const remaining = await cache.ttl('key');
12await cache.del('key');
13await cache.clear();

Next Steps