Optimization Guide
Practical techniques and strategies to optimize your RamAPI application for maximum performance.
Table of Contents
- Quick Wins
- Profiling & Monitoring
- Middleware Optimization
- Database Optimization
- Caching Strategies
- Memory Optimization
- Real-World Examples
Quick Wins
1. Enable uWebSockets
Impact: 2-3x performance improvement Effort: 5 minutes Risk: Low
// Before: 124K req/s
const app = createApp({
adapter: { type: 'node-http' },
});
// After: 350K req/s
const app = createApp({
adapter: { type: 'uwebsockets' },
});
// Or let RamAPI choose automatically
const app = createApp(); // Tries uWebSockets firstInstallation:
npm install uWebSockets.js2. Minimize Middleware
Impact: 15-20% improvement per middleware removed Effort: 10 minutes Risk: Low
// Bad: Many middleware (slower)
app.use(helmet());
app.use(compress());
app.use(bodyParser());
app.use(cookieParser());
app.use(sessionMiddleware());
app.use(corsMiddleware());
app.use(rateLimiter());
app.use(requestLogger());
app.use(responseLogger());
app.use(errorLogger());
// Result: 10 middleware = 50-80% overhead
// Good: Only essential middleware (faster)
app.use(authenticate);
app.use(logger());
// Result: 2 middleware = 15-20% overheadRule of thumb: Each middleware adds ~5-8% latency
3. Use Validation Efficiently
Impact: 10-15% improvement Effort: 5 minutes Risk: None
// Bad: Validate everything (slow)
validate({
body: z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150),
address: z.object({
street: z.string(),
city: z.string(),
zipCode: z.string(),
country: z.string(),
}),
preferences: z.object({
newsletter: z.boolean(),
notifications: z.boolean(),
theme: z.enum(['light', 'dark']),
}),
metadata: z.record(z.string()),
}),
query: z.object({
filter: z.string().optional(),
sort: z.string().optional(),
page: z.number().optional(),
}),
params: z.object({
id: z.string().regex(/^\d+$/),
}),
})
// Good: Validate only what's needed (fast)
validate({
body: z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().positive(),
}),
params: z.object({
id: z.string().regex(/^\d+$/),
}),
})Tip: Validate at the edge, trust internal services
4. Cache Expensive Operations
Impact: 10-100x improvement (depending on operation) Effort: 15 minutes Risk: Medium (cache invalidation)
// Bad: Query on every request (slow)
app.get('/api/config', async (ctx) => {
const config = await db.query('SELECT * FROM config');
ctx.json(config);
});
// Result: 1000 req/s (database bottleneck)
// Good: Cache static data (fast)
let configCache: any = null;
let cacheTime = 0;
const CACHE_TTL = 60000; // 1 minute
app.get('/api/config', async (ctx) => {
const now = Date.now();
if (!configCache || now - cacheTime > CACHE_TTL) {
configCache = await db.query('SELECT * FROM config');
cacheTime = now;
}
ctx.json(configCache);
});
// Result: 50,000+ req/s (memory cache)5. Enable Production Mode
Impact: 5-10% improvement Effort: 1 minute Risk: None
# Development mode (slow)
NODE_ENV=development npm start
# Production mode (fast)
NODE_ENV=production npm startWhat changes in production:
- No stack traces in errors
- Disabled source maps
- Optimized logging
- Disabled debug output
Profiling & Monitoring
Built-in Profiling
Enable profiling to find bottlenecks:
const app = createApp({
observability: {
profiling: {
enabled: true,
slowThreshold: 100, // Flag requests >100ms
autoDetectBottlenecks: true,
sampleRate: 0.1, // Profile 10% of requests
},
},
});View profiling data:
import { getProfiles } from 'ramapi';
app.get('/debug/slow', async (ctx) => {
const profiles = await getProfiles({ slowOnly: true });
ctx.json({ profiles });
});Example output:
{
"profiles": [
{
"operationName": "GET /api/users/:id",
"duration": 245.6,
"breakdown": {
"authenticate": 12.3,
"validate": 8.7,
"database": 220.1,
"handler": 4.5
},
"bottleneck": "database"
}
]
}Performance Metrics
Track key metrics:
import { getMetrics } from 'ramapi';
const app = createApp({
observability: {
metrics: { enabled: true },
},
});
app.get('/metrics', async (ctx) => {
const metrics = getMetrics();
ctx.json({
requestsPerSecond: metrics.requestsPerSecond,
p50Latency: metrics.p50Latency,
p95Latency: metrics.p95Latency,
p99Latency: metrics.p99Latency,
errorRate: metrics.errorRate,
});
});Set up alerts:
setInterval(() => {
const metrics = getMetrics();
// Alert on high latency
if (metrics.p95Latency > 100) {
console.error('⚠️ High latency detected:', metrics.p95Latency);
}
// Alert on high error rate
if (metrics.errorRate > 0.01) {
console.error('⚠️ High error rate:', metrics.errorRate);
}
}, 60000); // Check every minuteNode.js Profiling
CPU profiling:
# Generate CPU profile
node --prof dist/index.js
# Process profile
node --prof-process isolate-0x*.log > profile.txtMemory profiling:
# Generate heap snapshot
node --inspect dist/index.js
# Open Chrome DevTools
# chrome://inspect
# Take heap snapshotFlame graphs:
# Install clinic.js
npm install -g clinic
# Run flame graph
clinic flame -- node dist/index.js
# Open results
open .clinic/*.htmlMiddleware Optimization
Conditional Middleware
Apply middleware only where needed:
// Bad: Apply to all routes (slow)
app.use(authenticate);
app.get('/public', handler); // Doesn't need auth!
app.get('/api/users', handler); // Needs auth
// Good: Apply selectively (fast)
app.get('/public', handler); // No auth overhead
app.get('/api/users', authenticate, handler); // Auth only where neededFast Path for Simple Routes
Skip middleware for high-traffic routes:
// Health check doesn't need middleware
app.get('/health', async (ctx) => {
ctx.json({ status: 'ok' });
});
// Result: 350K req/s (no middleware)
// API routes use middleware
app.get('/api/users', authenticate, logger(), async (ctx) => {
ctx.json({ users: [] });
});
// Result: 285K req/s (with middleware)Async Middleware Optimization
Avoid unnecessary awaits:
// Bad: Awaiting everything (slow)
app.use(async (ctx, next) => {
const start = await Promise.resolve(Date.now());
await next();
const duration = await Promise.resolve(Date.now() - start);
console.log('Duration:', duration);
});
// Good: Only await what's needed (fast)
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const duration = Date.now() - start;
console.log('Duration:', duration);
});Database Optimization
Connection Pooling
Use connection pools for better performance:
import { Pool } from 'pg';
// Bad: New connection per request (slow)
app.get('/api/users', async (ctx) => {
const client = new Client({ connectionString: process.env.DATABASE_URL });
await client.connect();
const result = await client.query('SELECT * FROM users');
await client.end();
ctx.json({ users: result.rows });
});
// Result: 500 req/s (connection overhead)
// Good: Connection pool (fast)
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
app.get('/api/users', async (ctx) => {
const result = await pool.query('SELECT * FROM users');
ctx.json({ users: result.rows });
});
// Result: 5,000+ req/s (reused connections)Query Optimization
Optimize database queries:
// Bad: N+1 query problem (very slow)
app.get('/api/users', async (ctx) => {
const users = await db.query('SELECT * FROM users');
for (const user of users) {
user.posts = await db.query('SELECT * FROM posts WHERE user_id = ?', [user.id]);
}
ctx.json({ users });
});
// Result: 10 users = 11 queries = 100ms latency
// Good: Single query with JOIN (fast)
app.get('/api/users', async (ctx) => {
const users = await db.query(`
SELECT u.*, p.id as post_id, p.title, p.content
FROM users u
LEFT JOIN posts p ON p.user_id = u.id
`);
// Group results
const grouped = groupByUser(users);
ctx.json({ users: grouped });
});
// Result: 1 query = 10ms latencyIndexes
Add indexes for frequently queried columns:
-- Bad: Full table scan (slow)
SELECT * FROM users WHERE email = 'user@example.com';
-- Result: 500ms for 1M users
-- Good: Index lookup (fast)
CREATE INDEX idx_users_email ON users(email);
SELECT * FROM users WHERE email = 'user@example.com';
-- Result: 1ms for 1M usersPrepared Statements
Use prepared statements for repeated queries:
// Bad: Query parsing on every request (slow)
app.get('/api/users/:id', async (ctx) => {
const result = await db.query(
'SELECT * FROM users WHERE id = ' + ctx.params.id // SQL injection risk!
);
ctx.json({ user: result.rows[0] });
});
// Good: Prepared statement (fast + safe)
const getUserStmt = db.prepare('SELECT * FROM users WHERE id = ?');
app.get('/api/users/:id', async (ctx) => {
const user = getUserStmt.get(ctx.params.id);
ctx.json({ user });
});Caching Strategies
In-Memory Cache
Simple in-memory cache:
class SimpleCache<T> {
private cache = new Map<string, { value: T; expires: number }>();
set(key: string, value: T, ttl: number): void {
this.cache.set(key, {
value,
expires: Date.now() + ttl,
});
}
get(key: string): T | undefined {
const item = this.cache.get(key);
if (!item) return undefined;
if (Date.now() > item.expires) {
this.cache.delete(key);
return undefined;
}
return item.value;
}
clear(): void {
this.cache.clear();
}
}
const cache = new SimpleCache\<any\>();
app.get('/api/expensive', async (ctx) => {
const cacheKey = 'expensive-operation';
const cached = cache.get(cacheKey);
if (cached) {
ctx.json(cached);
return;
}
const result = await expensiveOperation();
cache.set(cacheKey, result, 60000); // 1 minute TTL
ctx.json(result);
});Redis Cache
Distributed cache with Redis:
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
app.get('/api/users/:id', async (ctx) => {
const cacheKey = `user:${ctx.params.id}`;
// Check cache
const cached = await redis.get(cacheKey);
if (cached) {
ctx.json(JSON.parse(cached));
return;
}
// Query database
const user = await db.query('SELECT * FROM users WHERE id = ?', [ctx.params.id]);
// Store in cache
await redis.setex(cacheKey, 300, JSON.stringify(user)); // 5 minutes
ctx.json(user);
});Cache Invalidation
Invalidate cache on updates:
// Update user
app.put('/api/users/:id', async (ctx) => {
const user = await db.query(
'UPDATE users SET name = ? WHERE id = ?',
[ctx.body.name, ctx.params.id]
);
// Invalidate cache
await redis.del(`user:${ctx.params.id}`);
ctx.json({ user });
});HTTP Caching
Use HTTP cache headers:
// Cache static responses
app.get('/api/config', async (ctx) => {
const config = await getConfig();
// Cache for 5 minutes
ctx.setHeader('Cache-Control', 'public, max-age=300');
ctx.setHeader('ETag', hashConfig(config));
ctx.json(config);
});
// Conditional requests
app.get('/api/users', async (ctx) => {
const etag = await getUsersETag();
if (ctx.headers['if-none-match'] === etag) {
ctx.status(304); // Not Modified
return;
}
const users = await getUsers();
ctx.setHeader('ETag', etag);
ctx.json({ users });
});Memory Optimization
Avoid Memory Leaks
Common memory leak patterns:
// Bad: Global array keeps growing (leak)
const requestLog: any[] = [];
app.use(async (ctx, next) => {
requestLog.push({ url: ctx.url, timestamp: Date.now() });
await next();
});
// Result: Memory grows forever
// Good: Limited size queue (no leak)
class CircularBuffer<T> {
private buffer: T[] = [];
constructor(private maxSize: number) {}
push(item: T): void {
this.buffer.push(item);
if (this.buffer.length > this.maxSize) {
this.buffer.shift();
}
}
}
const requestLog = new CircularBuffer\<any\>(1000); // Keep last 1000
app.use(async (ctx, next) => {
requestLog.push({ url: ctx.url, timestamp: Date.now() });
await next();
});Stream Large Responses
Stream instead of buffering:
// Bad: Load entire file in memory (high memory)
app.get('/download/:file', async (ctx) => {
const content = await fs.readFile(`/files/${ctx.params.file}`);
ctx.send(content);
});
// Result: 100 concurrent downloads × 10MB = 1GB memory
// Good: Stream file (low memory)
import { createReadStream } from 'fs';
app.get('/download/:file', async (ctx) => {
const stream = createReadStream(`/files/${ctx.params.file}`);
ctx.setHeader('Content-Type', 'application/octet-stream');
ctx.res.pipe(stream);
});
// Result: 100 concurrent downloads × 64KB buffer = 6.4MB memoryGarbage Collection Tuning
Tune V8 garbage collector:
# Increase heap size
node --max-old-space-size=4096 dist/index.js
# More frequent GC (lower memory, slightly slower)
node --max-old-space-size=2048 --gc-interval=100 dist/index.js
# Less frequent GC (higher memory, slightly faster)
node --max-old-space-size=8192 --gc-interval=1000 dist/index.jsReal-World Examples
High-Traffic API
Optimized for 100K+ req/s:
import { createApp } from 'ramapi';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
const app = createApp({
// Use uWebSockets for max performance
adapter: { type: 'uwebsockets' },
// Minimal observability in production
observability: {
metrics: { enabled: true },
logging: { level: 'error' },
},
});
// Cache layer
const cache = {
async get(key: string): Promise\<any\> {
const cached = await redis.get(key);
return cached ? JSON.parse(cached) : null;
},
async set(key: string, value: any, ttl: number): Promise<void> {
await redis.setex(key, ttl, JSON.stringify(value));
},
};
// Minimal middleware
const authenticate = async (ctx: Context, next: () => Promise<void>) => {
const token = ctx.headers.authorization;
if (!token) {
ctx.json({ error: 'Unauthorized' }, 401);
return;
}
ctx.state.userId = await verifyToken(token);
await next();
};
// Optimized endpoints
app.get('/api/users/:id', authenticate, async (ctx) => {
const cacheKey = `user:${ctx.params.id}`;
// Check cache first
const cached = await cache.get(cacheKey);
if (cached) {
ctx.json(cached);
return;
}
// Query database
const user = await db.prepare('SELECT * FROM users WHERE id = ?').get(ctx.params.id);
if (!user) {
ctx.json({ error: 'Not found' }, 404);
return;
}
// Cache for 5 minutes
await cache.set(cacheKey, user, 300);
ctx.json({ user });
});
await app.listen(3000);Result: 85,000+ req/s with database, 300,000+ req/s with cache hits
Cost-Optimized Microservice
Minimize server costs:
import { createApp } from 'ramapi';
const app = createApp({
adapter: { type: 'uwebsockets' },
});
// Aggressive caching
const configCache = await loadConfig();
const dataCache = new Map<string, any>();
// Health check (no overhead)
app.get('/health', async (ctx) => {
ctx.json({ status: 'ok' });
});
// Cached config endpoint
app.get('/api/config', async (ctx) => {
ctx.setHeader('Cache-Control', 'public, max-age=3600');
ctx.json(configCache);
});
// Efficient data endpoint
app.get('/api/data/:key', async (ctx) => {
const cached = dataCache.get(ctx.params.key);
if (cached) {
ctx.json(cached);
return;
}
const data = await fetchData(ctx.params.key);
dataCache.set(ctx.params.key, data);
ctx.json(data);
});
await app.listen(3000);Result: Handle 5x more traffic with same server, reduce costs by 80%
Performance Checklist
- Use uWebSockets adapter
- Minimize middleware (≤ 3 per route)
- Cache expensive operations
- Use connection pooling
- Add database indexes
- Enable production mode
- Use prepared statements
- Implement HTTP caching
- Profile slow endpoints
- Monitor key metrics
- Stream large responses
- Validate efficiently
- Avoid N+1 queries
- Tune garbage collection
- Use Redis for distributed cache
Next Steps
Need help optimizing? Check the Profiling Guide or GitHub Discussions (opens in a new tab).