High Performance Expressjs APIs with Native SQLite Caching
Build a high performance cache in Node.js using the native SQLite module. Implement persistent caching with Express.js. No Redis or external services required
On this page 12 sections
In the world of web development, performance is king. Users expect lightning-fast responses, and every millisecond counts. While external caching solutions like Redis are popular, they introduce additional complexity and infrastructure requirements. What if I told you that Nodejs 22.5.0+ now includes native SQLite support that can provide robust, persistent caching without any external dependencies?

In this comprehensive guide, I’ll show you how to build a production-ready caching layer using Nodejs’s native SQLite module and integrate it seamlessly with Express.js.
The Problem: Why Traditional Caching Falls Short
Most developers reach for in-memory caching (storing data in JavaScript objects) or external solutions like Redis. However, these approaches have significant drawbacks:
In-Memory Caching:
- Data lost on server restart
- Memory usage grows unbounded
- Not suitable for production environments
External Caching (Redis/Memcached):
- Additional infrastructure to maintain
- Network latency for cache operations
- More complexity in deployment and monitoring
- Additional costs for managed services
The Solution: Native SQLite Caching
Nodejs 22.5.0 introduced native SQLite support through the node:sqlite module. This game-changing feature allows us to build persistent, efficient caching without external dependencies.
Why SQLite for Caching?
- Zero Configuration - No separate database server to manage
- ACID Compliance - Reliable transactions and data integrity
- High Performance - Optimized C library with prepared statements
- Persistent Storage - Data survives application restarts
- Built-in - No external dependencies to maintain
Read about SQLite Nodejs Guide: Native Module Tutorial & Examples
Building the SQLite Cache Layer
Let’s start by creating a robust caching class that leverages SQLite’s strengths:
Core Features
Our cache implementation includes:
- TTL (Time-To-Live) Support - Automatic expiration of cached data
- Hit Counting - Track cache usage patterns
- Prepared Statements - Maximum performance for repeated operations
- Automatic Cleanup - Background process to remove expired entries
- Transaction Support - Atomic operations for data consistency
Key Implementation Details
class SQLiteCache {
constructor(dbPath = ':memory:', options = {}) {
this.defaultTTL = options.defaultTTL || 3600; // 1 hour
this.cleanupInterval = options.cleanupInterval || 300000; // 5 minutes
this.cleanupInterval = options.cleanupInterval || 300000;
this.db = null;
this.cleanupTimer = null;
this.statements = {};
// ... initialization code
}
}The cache uses a simple but effective schema:
CREATE TABLE cache (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
expires_at INTEGER NOT NULL,
created_at INTEGER DEFAULT (unixepoch()),
accessed_at INTEGER DEFAULT (unixepoch()),
hit_count INTEGER DEFAULT 1
);This schema enables:
- Fast lookups by key (primary key index)
- Efficient cleanup of expired entries (expires_at index)
- Usage analytics (hit_count, access tracking)
Express.js Integration: Smart Caching Middleware
The real magic happens when we integrate our cache with Express.js through intelligent middleware:
function createCacheMiddleware(cache, options = {}) {
const {
keyGenerator = (req) => `${req.method}:${req.originalUrl}`,
ttl = 300, // 5 minutes default
condition = () => true
} = options;
return (req, res, next) => {
const cacheKey = keyGenerator(req);
// Try cache first
const cached = cache.get(cacheKey);
if (cached) {
res.set('X-Cache-Hit', 'true');
return res.json(cached);
}
// Intercept response to cache it
const originalJson = res.json;
res.json = function(data) {
cache.set(cacheKey, data, ttl);
res.set('X-Cache-Hit', 'false');
return originalJson.call(this, data);
};
next();
};
}Smart Cache Key Generation
The middleware automatically generates cache keys based on:
- HTTP method
- Request path
- Query parameters
This ensures that GET /api/users?page=1 and GET /api/users?page=2 are cached separately, while identical requests share the same cache entry.
Real-World Performance Results
Let’s look at the dramatic performance improvements our caching provides:
Before Caching:
- Response Time: 2.1 seconds (expensive computation)
- Database Queries: Every request hits the database
- Server Load: High CPU usage for repeated calculations
After Caching:
- Response Time: ~5ms (99.8% improvement!)
- Database Queries: Only on cache misses
- Server Load: Minimal CPU usage for cached responses
Production Features
Cache Management API
Our implementation includes a complete cache management API:
// Get cache statistics
GET /api/cache/stats
{
"total": 150,
"active": 140,
"expired": 10,
"avg_size": 2048,
"total_hits": 1250,
"hit_rate": "8.33"
}
// View all cached entries
GET /api/cache/entries
// Manual cache control
POST /api/cache/mykey
DELETE /api/cache/mykey
DELETE /api/cache (clear all)Monitoring and Observability
The cache provides detailed metrics for monitoring:
- Hit Rates - Track cache effectiveness
- Entry Counts - Monitor cache size growth
- Access Patterns - Identify frequently accessed data
- Cleanup Statistics - Monitor expired entry removal
Advanced Use Cases
1. API Response Caching
Perfect for caching expensive database queries or external API calls:
app.get('/api/users', apiCache, async (req, res) => {
// First request: slow database query
// Subsequent requests: instant cache response
const users = await getUsersFromDatabase();
res.json(users);
});2. Computed Results Caching
Cache expensive calculations that don’t change frequently:
app.get('/api/analytics', cacheMiddleware({ ttl: 3600 }), async (req, res) => {
// Complex analytics computation cached for 1 hour
const analytics = await generateAnalytics();
res.json(analytics);
});3. Configuration Caching
Cache application configuration with long TTL:
cache.set('app:config', await loadConfiguration(), 86400); // 24 hours
Best Practices and Optimization Tips
1. Choose Appropriate TTL Values
- Static Data: Long TTL (hours/days)
- User Data: Medium TTL (minutes/hours)
- Real-time Data: Short TTL (seconds/minutes)
2. Use Conditional Caching
const smartCache = createCacheMiddleware(cache, {
condition: (req) => {
// Skip caching for authenticated requests
return !req.headers.authorization;
}
});3. Monitor Cache Performance
setInterval(async () => {
const stats = cache.stats();
console.log(`Cache hit rate: ${stats.hit_rate}%`);
if (parseFloat(stats.hit_rate) < 50) {
console.warn('Low cache hit rate - review TTL settings');
}
}, 60000);4. Handle Cache Warming
// Warm up frequently accessed data on startup
async function warmCache() {
const popularData = await getPopularData();
cache.set('popular:data', popularData, 3600);
}Deployment Considerations
Database Location
For production deployments:
- Use file-based SQLite database:
./cache/app-cache.db - Mount cache directory as a volume in containerized deployments
- Consider backup strategies for cache persistence
Performance Tuning
// Enable WAL mode for better concurrency
this.db.exec('PRAGMA journal_mode = WAL');
// Optimize SQLite settings
this.db.exec('PRAGMA synchronous = NORMAL');
this.db.exec('PRAGMA cache_size = 10000');Monitoring in Production
// Log cache statistics periodically
setInterval(() => {
const stats = cache.stats();
logger.info('Cache stats', stats);
}, 300000); // Every 5 minutes
Comparing with Alternatives
| Feature | SQLite Cache | Redis | In-Memory |
|---|---|---|---|
| Setup Complexity | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Performance | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Persistence | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐ |
| Memory Usage | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| Infrastructure Cost | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| Scalability | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
Getting Started
Ready to implement SQLite caching in your Express.js application? Here’s how:
Prerequisites
- Nodejs 22.5.0 or later
- Express.js application
Installation
# No external dependencies needed!
npm install expressQuick Start
- Copy the SQLite cache implementation
- Initialize the cache in your Express app
- Add caching middleware to your routes
- Monitor performance improvements
Note: Complete code is checked in at express_sqlite_cache
Conclusion
Native SQLite caching represents a paradigm shift in how we approach caching in Nodejs applications. By leveraging the built-in SQLite support, we can achieve:
- 99%+ performance improvements for cached responses
- Zero external dependencies for caching infrastructure
- Production-ready features like TTL, monitoring, and cleanup
- Persistent storage that survives application restarts
- Cost-effective scaling without additional infrastructure
The combination of Express.js and native SQLite caching provides an optimal balance of simplicity, performance, and reliability. Whether you’re building a small API or a large-scale application, this approach offers enterprise-grade caching without the complexity.
Start implementing SQLite caching in your next Express.js project and experience the dramatic performance improvements firsthand. Your users (and your infrastructure costs) will thank you!