Static file serving middleware for Koa.js applications with compression, caching, and security features.
npx @tessl/cli install tessl/npm-koa-send@5.0.0koa-send is a static file serving middleware for Koa.js applications that provides secure, efficient delivery of static assets to web clients. It offers comprehensive file serving capabilities including automatic compression support (gzip and brotli), configurable browser caching, path normalization and security protections against directory traversal attacks, and flexible content type detection.
npm install koa-sendconst send = require('koa-send');For ES modules:
import send from 'koa-send';const send = require('koa-send');
const Koa = require('koa');
const app = new Koa();
// Basic file serving
app.use(async (ctx) => {
if (ctx.path === '/') {
ctx.body = 'Try GET /package.json';
return;
}
await send(ctx, ctx.path);
});
// Serve from a root directory
app.use(async (ctx) => {
await send(ctx, ctx.path, { root: __dirname + '/public' });
});
app.listen(3000);Serves static files with comprehensive security and performance features.
/**
* Send static file with given options to Koa context
* @param {Context} ctx - Koa context object (required)
* @param {string} path - File path to serve (required)
* @param {SendOptions} opts - Options object (optional)
* @returns {Promise<string>} - Resolved file path that was served
* @throws {Error} 400 if path decode fails
* @throws {Error} 404 if file not found
* @throws {Error} 500 for other file system errors
* @throws {TypeError} if setHeaders is not a function
*/
async function send(ctx, path, opts = {});Usage Examples:
// Basic usage
await send(ctx, 'path/to/file.txt');
// With root directory
await send(ctx, ctx.path, { root: '/public' });
// With compression and caching
await send(ctx, ctx.path, {
root: __dirname + '/static',
maxage: 86400000, // 24 hours
gzip: true,
brotli: true,
immutable: true
});
// With custom headers
await send(ctx, ctx.path, {
root: '/assets',
setHeaders: (res, path, stats) => {
res.setHeader('X-Served-By', 'koa-send');
}
});/**
* Configuration options for the send function
*/
interface SendOptions {
/** Root directory to restrict file access (defaults to '') */
root?: string;
/** Browser cache max-age in milliseconds (defaults to 0) */
maxage?: number;
/** Alias for maxage (defaults to 0) */
maxAge?: number;
/** Mark resource as immutable for caching (defaults to false) */
immutable?: boolean;
/** Allow transfer of hidden files (defaults to false) */
hidden?: boolean;
/** Name of index file for directories (defaults to undefined) */
index?: string;
/** Format path for directory handling (defaults to true) */
format?: boolean;
/** File extensions to try when no extension provided (defaults to false) */
extensions?: Array<string> | false;
/** Enable brotli compression support (defaults to true) */
brotli?: boolean;
/** Enable gzip compression support (defaults to true) */
gzip?: boolean;
/** Custom headers function (defaults to undefined) */
setHeaders?: (res: ServerResponse, path: string, stats: Stats) => void;
}root)Specifies the root directory from which to serve files. The path is resolved and normalized for security.
// Serve files from public directory
await send(ctx, ctx.path, { root: __dirname + '/public' });
// Serve specific file without user input
await send(ctx, 'path/to/my.js');maxage, immutable)Control browser caching behavior:
// Cache for 24 hours
await send(ctx, ctx.path, {
maxage: 86400000,
immutable: true // Resource never changes
});gzip, brotli)Automatic compression based on client support:
// Enable both compression methods (default)
await send(ctx, ctx.path, {
gzip: true,
brotli: true
});
// Disable compression
await send(ctx, ctx.path, {
gzip: false,
brotli: false
});extensions)Try multiple extensions for extensionless requests:
// Try .html, .js extensions
await send(ctx, ctx.path, {
extensions: ['html', 'js']
});
// Also works with dots
await send(ctx, ctx.path, {
extensions: ['.html', '.js']
});index)Serve index files for directory requests:
// Serve index.html for directory requests
await send(ctx, ctx.path, {
index: 'index.html',
format: true // Handle trailing slashes
});setHeaders)Set custom response headers:
await send(ctx, ctx.path, {
setHeaders: (res, path, stats) => {
// Only modify Cache-Control or Last-Modified here
// Other headers should be set before calling send()
if (path.endsWith('.js')) {
res.setHeader('Cache-Control', 'max-age=31536000, immutable');
}
}
});koa-send automatically protects against directory traversal attacks:
..) are handled securelyresolve-path library provides additional securityBy default, hidden files (starting with .) are not served:
// Allow hidden files (use with caution)
await send(ctx, ctx.path, { hidden: true });The send function throws specific HTTP errors:
setHeaders option (not a function)app.use(async (ctx, next) => {
try {
await send(ctx, ctx.path, { root: '/public' });
} catch (err) {
if (err.status === 404) {
ctx.body = 'File not found';
} else {
throw err;
}
}
});Files are automatically compressed when:
.gz or .br)Files are streamed directly to the client using Node.js streams:
Configurable browser caching with:
max-age directive for cache durationimmutable directive for permanent cachingLast-Modified headers/**
* Node.js ServerResponse object
*/
interface ServerResponse {
setHeader(name: string, value: string): void;
removeHeader(name: string): void;
}
/**
* Node.js fs.Stats object
*/
interface Stats {
size: number;
mtime: Date;
isDirectory(): boolean;
}
/**
* Koa Context object (subset of properties used by send)
*/
interface Context {
path: string;
res: ServerResponse;
response: {
get(field: string): string;
};
body?: any;
type?: string;
set(field: string, val: string): void;
throw(status: number, message?: string): void;
acceptsEncodings(...encodings: string[]): string | false;
}