High-performance Koa middleware for serving static files with Apache-like directory listing, HTTP caching, template engine support, and comprehensive security fixes
npm install koa-classic-server๐ Production-ready Koa middleware for serving static files with Apache2-like directory listing, sortable columns, HTTP caching, template engine support, and enterprise-grade security.


![Tests]()
---
The 2.X series brings major performance improvements, enhanced security, and powerful new features while maintaining full backward compatibility.
โ
URL Rewriting Support - Compatible with i18n and routing middleware via useOriginalUrl option
โ
Improved Caching Controls - Clear browserCacheEnabled and browserCacheMaxAge options
โ
Development-Friendly Defaults - Caching disabled by default for easier development
โ
Production Optimized - Enable caching in production for 80-95% bandwidth reduction
โ
Sortable Directory Columns - Click Name/Type/Size to sort (Apache2-like)
โ
File Size Display - Human-readable file sizes (B, KB, MB, GB, TB)
โ
HTTP Caching - ETag and Last-Modified headers with 304 responses
โ
Async/Await - Non-blocking I/O for high performance
โ
Performance Optimized - 50-70% faster directory listings
โ
Enhanced Index Option - Array format with RegExp support
โ
Template Engine Support - EJS, Pug, Handlebars, Nunjucks, and more
โ
Enterprise Security - Path traversal, XSS, race condition protection
โ
Comprehensive Testing - 197 tests passing with extensive coverage
โ
Complete Documentation - Detailed guides and examples
---
koa-classic-server is a high-performance middleware for serving static files with Apache2-like behavior, making file browsing intuitive and powerful.
- ๐๏ธ Apache2-like Directory Listing - Sortable columns (Name, Type, Size)
- ๐ Static File Serving - Automatic MIME type detection with streaming
- ๐ Sortable Columns - Click headers to sort ascending/descending
- ๐ File Sizes - Human-readable display (B, KB, MB, GB, TB)
- โก HTTP Caching - ETag, Last-Modified, 304 responses
- ๐จ Template Engine Support - EJS, Pug, Handlebars, Nunjucks, etc.
- ๐ Enterprise Security - Path traversal, XSS, race condition protection
- โ๏ธ Highly Configurable - URL prefixes, reserved paths, index files
- ๐ High Performance - Async/await, non-blocking I/O, optimized algorithms
- ๐งช Well-Tested - 153 passing tests with comprehensive coverage
- ๐ฆ Dual Module Support - CommonJS and ES Modules
---
``bash`
npm install koa-classic-server
Requirements:
- Node.js >= 12.0.0
- Koa >= 2.0.0
---
`javascript
const Koa = require('koa');
const koaClassicServer = require('koa-classic-server');
const app = new Koa();
// Serve files from "public" directory
app.use(koaClassicServer(__dirname + '/public'));
app.listen(3000);
console.log('Server running on http://localhost:3000');
`
`javascript
const Koa = require('koa');
const koaClassicServer = require('koa-classic-server');
const app = new Koa();
app.use(koaClassicServer(__dirname + '/public', {
showDirContents: true,
index: ['index.html', 'index.htm'],
urlPrefix: '/static',
browserCacheMaxAge: 3600,
browserCacheEnabled: true
}));
app.listen(3000);
`
---
`javascript
// CommonJS
const koaClassicServer = require('koa-classic-server');
// ES Modules
import koaClassicServer from 'koa-classic-server';
`
Serve static files from a directory:
`javascript
const Koa = require('koa');
const koaClassicServer = require('koa-classic-server');
const app = new Koa();
app.use(koaClassicServer(__dirname + '/public', {
showDirContents: true,
index: ['index.html']
}));
app.listen(3000);
`
What it does:
- Serves files from /public directoryindex.html
- Shows directory listing when accessing folders
- Looks for in directories
- Sortable columns (Name, Type, Size)
- File sizes displayed in human-readable format
Serve files under a specific URL path:
`javascript`
app.use(koaClassicServer(__dirname + '/assets', {
urlPrefix: '/static',
showDirContents: true
}));
Result:
- http://localhost:3000/static/image.png โ serves /assets/image.pnghttp://localhost:3000/static/
- โ shows /assets directory listing
Protect specific directories from being accessed:
`javascript`
app.use(koaClassicServer(__dirname + '/www', {
urlsReserved: ['/admin', '/config', '/.git', '/node_modules']
}));
Result:
- /admin/* โ passed to next middleware (not served)/config/*
- โ protected
- Other paths โ served normally
Dynamically render templates with data:
`javascript
const ejs = require('ejs');
app.use(koaClassicServer(__dirname + '/views', {
template: {
ext: ['ejs', 'html.ejs'],
render: async (ctx, next, filePath) => {
const data = {
title: 'My App',
user: ctx.state.user || { name: 'Guest' },
items: ['Item 1', 'Item 2', 'Item 3'],
timestamp: new Date().toISOString()
};
ctx.body = await ejs.renderFile(filePath, data);
ctx.type = 'text/html';
}
}
}));
`
Template example (views/dashboard.ejs Generated at: <%= timestamp %>):
`html`
Welcome, <%= user.name %>!
<% items.forEach(item => { %>
<% }); %>
See complete guide: Template Engine Documentation โ
Enable aggressive caching for static files:
`javascript`
app.use(koaClassicServer(__dirname + '/public', {
browserCacheEnabled: true, // Enable ETag and Last-Modified
browserCacheMaxAge: 86400, // Cache for 24 hours (in seconds)
}));
โ ๏ธ Important: Production Recommendation
The default value for browserCacheEnabled is false to facilitate development (where you want changes to be immediately visible). For production environments, it is strongly recommended to set browserCacheEnabled: true to benefit from:
- 80-95% bandwidth reduction
- 304 Not Modified responses for unchanged files
- Faster page loads for returning visitors
- Reduced server load
See details: HTTP Caching Optimization โ
Search for multiple index files with custom order:
`javascript`
app.use(koaClassicServer(__dirname + '/public', {
index: [
'index.html', // First priority
'index.htm', // Second priority
/index\.[eE][jJ][sS]/, // Third: index.ejs (case-insensitive)
'default.html' // Last priority
]
}));
See details: Index Option Priority โ
Real-world configuration for production:
`javascript
const Koa = require('koa');
const koaClassicServer = require('koa-classic-server');
const ejs = require('ejs');
const path = require('path');
const app = new Koa();
// Serve static assets with caching
app.use(koaClassicServer(path.join(__dirname, 'public'), {
method: ['GET', 'HEAD'],
showDirContents: false, // Disable directory listing in production
index: ['index.html', 'index.htm'],
urlPrefix: '/assets',
urlsReserved: ['/admin', '/api', '/.git'],
browserCacheEnabled: true,
browserCacheMaxAge: 86400, // 24 hours
}));
// Serve dynamic templates
app.use(koaClassicServer(path.join(__dirname, 'views'), {
showDirContents: false,
template: {
ext: ['ejs'],
render: async (ctx, next, filePath) => {
const data = {
env: process.env.NODE_ENV,
user: ctx.state.user,
config: ctx.state.config
};
try {
ctx.body = await ejs.renderFile(filePath, data);
ctx.type = 'text/html';
} catch (error) {
console.error('Template error:', error);
ctx.status = 500;
ctx.body = 'Internal Server Error';
}
}
}
}));
app.listen(3000);
`
---
Creates a Koa middleware for serving static files.
Parameters:
- rootDir (String, required): Absolute path to the directory containing files
- options (Object, optional): Configuration options
Returns: Koa middleware function
`javascript
{
// HTTP methods allowed (default: ['GET'])
method: ['GET', 'HEAD'],
// Show directory contents (default: true)
showDirContents: true,
// Index file configuration
// Array format (recommended):
// - Strings: exact matches ['index.html', 'default.html']
// - RegExp: pattern matches [/index\.html/i]
// - Mixed: ['index.html', /INDEX\.HTM/i]
// Priority determined by array order (first match wins)
// See docs/INDEX_OPTION_PRIORITY.md for details
index: ['index.html', 'index.htm'],
// URL path prefix (default: '')
// Files served under this prefix
urlPrefix: '/static',
// Reserved paths (default: [])
// First-level directories passed to next middleware
urlsReserved: ['/admin', '/api', '/.git'],
// Template engine configuration
template: {
// Template rendering function
render: async (ctx, next, filePath) => {
// Your rendering logic
ctx.body = await yourEngine.render(filePath, data);
},
// File extensions to process
ext: ['ejs', 'pug', 'hbs']
},
// Browser HTTP caching configuration
// NOTE: Default is false for development. Set to true in production for better performance!
browserCacheEnabled: false, // Enable ETag & Last-Modified (default: false)
browserCacheMaxAge: 3600, // Cache-Control max-age in seconds (default: 3600 = 1 hour)
// URL resolution
useOriginalUrl: true, // Use ctx.originalUrl (default) or ctx.url
// Set false for URL rewriting middleware (i18n, routing)
// DEPRECATED (use new names above):
// enableCaching: use browserCacheEnabled instead
// cacheMaxAge: use browserCacheMaxAge instead
}
`
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| method | Array | ['GET'] | Allowed HTTP methods |showDirContents
| | Boolean | true | Show directory listing |index
| | Array/String | [] | Index file patterns (array format recommended) |urlPrefix
| | String | '' | URL path prefix |urlsReserved
| | Array | [] | Reserved directory paths (first-level only) |template.render
| | Function | undefined | Template rendering function |template.ext
| | Array | [] | Extensions for template rendering |browserCacheEnabled
| | Boolean | false | Enable browser HTTP caching headers (recommended: true in production) |browserCacheMaxAge
| | Number | 3600 | Browser cache duration in seconds |useOriginalUrl
| | Boolean | true | Use ctx.originalUrl (default) or ctx.url for URL resolution |enableCaching
| ~~~~ | Boolean | false | DEPRECATED: Use browserCacheEnabled instead |cacheMaxAge
| ~~~~ | Number | 3600 | DEPRECATED: Use browserCacheMaxAge instead |
#### useOriginalUrl (Boolean, default: true)
Controls which URL property is used for file resolution:
- true (default): Uses ctx.originalUrl (immutable, reflects the original request)false
- : Uses ctx.url (mutable, can be modified by middleware)
When to use false:
Set useOriginalUrl: false when using URL rewriting middleware such as i18n routers or path rewriters that modify ctx.url. This allows koa-classic-server to serve files based on the rewritten URL instead of the original request URL.
Example with i18n middleware:
`javascript
const Koa = require('koa');
const koaClassicServer = require('koa-classic-server');
const app = new Koa();
// i18n middleware that rewrites URLs
app.use(async (ctx, next) => {
if (ctx.path.match(/^\/it\//)) {
ctx.url = ctx.path.replace(/^\/it/, ''); // /it/page.html โ /page.html
}
await next();
});
// Serve files using rewritten URL
app.use(koaClassicServer(__dirname + '/www', {
useOriginalUrl: false // Use ctx.url (rewritten) instead of ctx.originalUrl
}));
app.listen(3000);
`
How it works:
- Request: GET /it/page.htmlctx.originalUrl
- : /it/page.html (unchanged)ctx.url
- : /page.html (rewritten by middleware)useOriginalUrl: false
- With : Server looks for /www/page.html โ
useOriginalUrl: true
- With (default): Server looks for /www/it/page.html โ 404
---
Click on column headers to sort:
- Name - Alphabetical sorting (A-Z or Z-A)
- Type - Sort by MIME type (directories always first)
- Size - Sort by file size (directories always first)
Visual indicators:
- โ - Ascending order
- โ - Descending order
Human-readable format:
- 1.5 KB - Kilobytes2.3 MB
- - Megabytes1.2 GB
- - Gigabytes-
- - Directories (no size)
- Click folder name - Enter directory
- Click file name - Download/view file
- Parent Directory - Go up one level
---
koa-classic-server includes enterprise-grade security:
#### 1. Path Traversal Protection
Prevents access to files outside rootDir:
`javascript`
// โ Blocked requests
GET /../../../etc/passwd โ 403 Forbidden
GET /../config/database.yml โ 403 Forbidden
GET /%2e%2e%2fpackage.json โ 403 Forbidden
#### 2. XSS Protection
All filenames and paths are HTML-escaped:
`javascript`
// Malicious filename: .txt
// Displayed as: <script>alert('xss')</script>.txt
// โ
Safe - script doesn't execute
#### 3. Reserved URLs
Protect sensitive directories:
`javascript`
app.use(koaClassicServer(__dirname, {
urlsReserved: ['/admin', '/config', '/.git', '/node_modules']
}));
#### 4. Race Condition Protection
File access is verified before streaming:
`javascript`
// File deleted between check and access?
// โ
Returns 404 instead of crashing
See full security audit: Security Tests โ
---
Version 2.x includes major performance improvements:
- Async/Await - Non-blocking I/O, event loop never blocked
- Array Join - 30-40% less memory vs string concatenation
- HTTP Caching - 80-95% bandwidth reduction
- Single stat() Call - No double file system access
- Streaming - Large files streamed efficiently
Performance results on directory with 1,000 files:
``
Before (v1.x): ~350ms per request
After (v2.x): ~190ms per request
Improvement: 46% faster
See detailed benchmarks: Performance Analysis โ
---
Run the comprehensive test suite:
`bashRun all tests
npm test
Test Coverage:
- โ
197 tests passing
- โ
Security tests (path traversal, XSS, race conditions)
- โ
EJS template integration tests
- โ
Index option tests (strings, arrays, RegExp)
- โ
Performance benchmarks
- โ
Directory sorting tests
---
Complete Documentation
$3
- DOCUMENTATION.md - Complete API reference and usage guide
- FLOW_DIAGRAM.md - Visual flow diagrams and code execution paths
- CHANGELOG.md - Version history and release notes
$3
- TEMPLATE_ENGINE_GUIDE.md - Complete guide to template engine integration
- Progressive examples (simple to enterprise)
- EJS, Pug, Handlebars, Nunjucks support
- Best practices and troubleshooting
$3
- INDEX_OPTION_PRIORITY.md - Detailed priority behavior for
index option
- String vs Array vs RegExp formats
- Priority order examples
- Migration guide from v1.x- EXAMPLES_INDEX_OPTION.md - 10 practical examples of
index option with RegExp
- Case-insensitive matching
- Multiple extensions
- Complex patterns$3
- PERFORMANCE_ANALYSIS.md - Performance optimization analysis
- Before/after comparisons
- Memory usage analysis
- Bottleneck identification
- PERFORMANCE_COMPARISON.md - Detailed performance benchmarks
- Request latency
- Throughput metrics
- Concurrent request handling
- OPTIMIZATION_HTTP_CACHING.md - HTTP caching implementation details
- ETag generation
- Last-Modified headers
- 304 Not Modified responses
- BENCHMARKS.md - Benchmark results and methodology
$3
- CODE_REVIEW.md - Code quality analysis and review
- Security audit
- Best practices
- Standardization improvements
- DEBUG_REPORT.md - Known limitations and debugging info
- Reserved URLs behavior
- Edge cases
- Troubleshooting tips
---
Migration Guide
$3
Breaking Changes:
-
index option: String format deprecated (still works), use array formatMigration:
`javascript
// v1.x (deprecated)
{
index: 'index.html'
}// v2.x (recommended)
{
index: ['index.html']
}
`New Features:
- HTTP caching (enabled by default)
- Sortable directory columns
- File size display
- Enhanced index option with RegExp
See full migration guide: CHANGELOG.md
---
Examples
$3
`javascript
const Koa = require('koa');
const koaClassicServer = require('koa-classic-server');const app = new Koa();
app.use(koaClassicServer(__dirname + '/public'));
app.listen(3000);
`$3
`javascript
const Koa = require('koa');
const koaClassicServer = require('koa-classic-server');const app = new Koa();
// Serve static assets
app.use(koaClassicServer(__dirname + '/public', {
urlPrefix: '/static',
showDirContents: false
}));
// Serve user uploads
app.use(koaClassicServer(__dirname + '/uploads', {
urlPrefix: '/files',
showDirContents: true
}));
app.listen(3000);
`$3
`javascript
const Koa = require('koa');
const koaClassicServer = require('koa-classic-server');
const ejs = require('ejs');const app = new Koa();
// Development mode - show directories
app.use(koaClassicServer(__dirname + '/src', {
showDirContents: true,
template: {
ext: ['ejs'],
render: async (ctx, next, filePath) => {
ctx.body = await ejs.renderFile(filePath, {
dev: true,
timestamp: Date.now()
});
ctx.type = 'text/html';
}
}
}));
app.listen(3000);
`---
Troubleshooting
$3
Issue: 404 errors for all files
Check that
rootDir is an absolute path:`javascript
// โ Wrong (relative path)
koaClassicServer('./public')// โ
Correct (absolute path)
koaClassicServer(__dirname + '/public')
koaClassicServer(path.join(__dirname, 'public'))
`Issue: Reserved URLs not working
Reserved URLs only work for first-level directories:
`javascript
urlsReserved: ['/admin'] // โ
Blocks /admin/*
urlsReserved: ['/admin/users'] // โ Doesn't work (nested)
`Issue: Directory sorting not working
Make sure you're accessing directories without query params initially. The sorting is applied when you click headers.
See full troubleshooting: DEBUG_REPORT.md
---
Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Ensure all tests pass (
npm test`)---
- Reserved URLs only work for first-level directories
- Template rendering is synchronous per request
See DEBUG_REPORT.md for technical details.
---
MIT License - see LICENSE file for details
---
Italo Paesano
---
- npm Package - Official npm package
- GitHub Repository - Source code
- Issue Tracker - Report bugs
- Full Documentation - Complete reference
---
See CHANGELOG.md for version history.
---
โ ๏ธ Security Notice: Always use the latest version for security updates and bug fixes.