Performance Tuning

Optimize your MoroJS applications for maximum performance. Learn caching strategies, database optimization, and monitoring techniques for production workloads.

Performance Monitoring

Simple Performance Monitoring

typescript

1import { createApp } from '@morojs/moro';
2
3const app = createApp({
4  cors: true,
5  compression: true, // Built-in performance feature
6  helmet: true
7});
8
9// Simple performance middleware (custom implementation)
10app.use((req, res, next) => {
11  const start = Date.now();
12  
13  // Add request timing
14  req.startTime = start;
15  
16  // Override res.json to add timing header
17  const originalJson = res.json;
18  res.json = function(data) {
19    const duration = Date.now() - start;
20    res.setHeader('X-Response-Time', `${duration}ms`);
21    
22    // Log slow requests
23    if (duration > 1000) {
24      console.warn(`Slow request: ${req.method} ${req.path} - ${duration}ms`);
25    }
26    
27    return originalJson.call(this, data);
28  };
29  
30  next();
31});
32
33// Health check with performance metrics
34app.get('/health', (req, res) => {
35  const memUsage = process.memoryUsage();
36  
37  return {
38    status: 'healthy',
39    timestamp: new Date().toISOString(),
40    uptime: process.uptime(),
41    memory: {
42      rss: Math.round(memUsage.rss / 1024 / 1024) + 'MB',
43      heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024) + 'MB',
44      heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024) + 'MB'
45    },
46    nodeVersion: process.version
47  };
48});
49
50// Performance endpoint from feature-showcase
51app.get('/config', (req, res) => {
52  const config = app.getConfig();
53  return {
54    success: true,
55    data: {
56      environment: config.server.environment,
57      compression: config.security.compression,
58      caching: {
59        enabled: config.modules.cache.enabled,
60        defaultTtl: config.modules.cache.defaultTtl
61      }
62    }
63  };
64});

Advanced Caching Strategies

Simple Caching with MoroJS

typescript

1// Built-in caching with chainable API (actual implementation)
2app.get('/users')
3  .query(z.object({
4    limit: z.coerce.number().default(10),
5    search: z.string().optional()
6  }))
7  .cache({ ttl: 60, key: 'users-list' }) // Cache for 60 seconds
8  .handler((req, res) => {
9    const users = getAllUsers(req.query);
10    return { success: true, data: users };
11  });
12
13// Dynamic cache keys
14app.get('/users/:id')
15  .cache({ ttl: 300, key: (req) => `user-${req.params.id}` }) // 5 minutes
16  .handler((req, res) => {
17    const user = getUserById(req.params.id);
18    return { success: true, data: user };
19  });
20
21// Configuration-driven caching
22app.get('/data')
23  .cache({ ttl: app.getConfig().modules.cache.defaultTtl })
24  .handler((req, res) => {
25    return { data: getExpensiveData() };
26  });
27
28// External Redis caching (for advanced use cases)
29import Redis from 'redis';
30
31const redis = Redis.createClient({ url: process.env.REDIS_URL });
32await redis.connect();
33
34// Custom Redis middleware
35const redisCache = (ttl) => {
36  return async (req, res, next) => {
37    const cacheKey = `cache:${req.method}:${req.path}`;
38    
39    try {
40      const cached = await redis.get(cacheKey);
41      if (cached) {
42        res.setHeader('X-Cache', 'HIT');
43        return res.json(JSON.parse(cached));
44      }
45      
46      const originalJson = res.json;
47      res.json = function(data) {
48        redis.setEx(cacheKey, ttl, JSON.stringify(data));
49        res.setHeader('X-Cache', 'MISS');
50        return originalJson.call(this, data);
51      };
52      
53      next();
54    } catch (error) {
55      console.error('Cache error:', error);
56      next();
57    }
58  };
59};
60
61// Use Redis caching
62app.get('/expensive-data', redisCache(300), (req, res) => {
63  const data = performExpensiveOperation();
64  return { success: true, data };
65});

Database Optimization

Query Optimization

typescript

1// Query performance monitoring
2const queryMonitor = {
3  slowQueryThreshold: 1000, // 1 second
4  
5  async logSlowQuery(query: string, params: any[], duration: number) {
6    if (duration > this.slowQueryThreshold) {
7      console.warn('Slow query detected:', {
8        query,
9        params,
10        duration: `${duration}ms`,
11        timestamp: new Date().toISOString()
12      });
13      
14      // Send to monitoring service
15      await sendMetric('slow_query', {
16        query_hash: hashQuery(query),
17        duration,
18        timestamp: Date.now()
19      });
20    }
21  }
22};
23
24// Optimized database middleware
25app.use(async (context, next) => {
26  const originalQuery = context.db.query;
27  
28  context.db.query = async (sql: string, params: any[] = []) => {
29    const start = Date.now();
30    
31    try {
32      const result = await originalQuery.call(context.db, sql, params);
33      const duration = Date.now() - start;
34      
35      await queryMonitor.logSlowQuery(sql, params, duration);
36      
37      return result;
38    } catch (error) {
39      console.error('Database query error:', { sql, params, error: error.message });
40      throw error;
41    }
42  };
43  
44  await next();
45});
46
47// Connection pool optimization
48app.get('/users/:id', {
49  handler: async ({ params, db }) => {
50    // Use read replica for read-only queries
51    const user = await db.replica.query(
52      'SELECT id, name, email FROM users WHERE id = $1',
53      [params.id]
54    );
55    
56    if (!user[0]) {
57      return { error: 'User not found' };
58    }
59    
60    // Use cache for frequently accessed data
61    const cacheKey = `user_profile:${params.id}`;
62    let profile = await cache.get(cacheKey);
63    
64    if (!profile) {
65      profile = await db.replica.query(
66        'SELECT bio, avatar_url, preferences FROM user_profiles WHERE user_id = $1',
67        [params.id]
68      );
69      
70      await cache.set(cacheKey, profile[0], { ttl: '15m' });
71    }
72    
73    return {
74      ...user[0],
75      profile: profile[0]
76    };
77  }
78});

Next Steps