HTTP request routing and static file serving library that powers the serve package
—
Pluggable file system abstraction that allows custom implementations for different storage backends, cloud storage, or specialized file handling requirements.
Custom file system method implementations that override the default Node.js fs operations.
interface Methods {
/** Custom lstat implementation for getting file/directory stats */
lstat?: (path: string, fromDir?: boolean) => Promise<Stats>;
/** Custom realpath implementation for resolving symbolic links */
realpath?: (path: string) => Promise<string>;
/** Custom read stream creator for file content streaming */
createReadStream?: (path: string, options?: object) => ReadableStream;
/** Custom directory reader for listing directory contents */
readdir?: (path: string) => Promise<string[]>;
/** Custom error handler for generating error responses */
sendError?: (absolutePath: string, response: ServerResponse, acceptsJSON: boolean, current: string, handlers: object, config: Config, spec: ErrorSpec) => Promise<void>;
}Custom file stat implementation for getting file and directory information.
/**
* Custom lstat implementation for getting file/directory stats
* @param path - Absolute path to the file or directory
* @param fromDir - Whether the call originated from directory listing (optional)
* @returns Promise resolving to file stats object
*/
lstat?: (path: string, fromDir?: boolean) => Promise<Stats>;
interface Stats {
/** File size in bytes */
size: number;
/** Last modified time */
mtime: Date;
/** Whether this is a directory */
isDirectory(): boolean;
/** Whether this is a symbolic link */
isSymbolicLink(): boolean;
}Usage Examples:
const handler = require('serve-handler');
// Custom lstat for cloud storage
const methods = {
lstat: async (path, fromDir) => {
const cloudFile = await cloudStorage.stat(path);
return {
size: cloudFile.size,
mtime: new Date(cloudFile.lastModified),
isDirectory: () => cloudFile.type === 'directory',
isSymbolicLink: () => false
};
}
};
await handler(req, res, {}, methods);Custom implementation for resolving symbolic links to their target paths.
/**
* Custom realpath implementation for resolving symbolic links
* @param path - Path to resolve
* @returns Promise resolving to the real path
*/
realpath?: (path: string) => Promise<string>;Usage Examples:
const methods = {
realpath: async (path) => {
// Custom symlink resolution logic
return await customResolveSymlink(path);
}
};Custom read stream implementation for streaming file content to clients.
/**
* Custom read stream creator for file content streaming
* @param path - Absolute path to the file
* @param options - Stream options including start/end for range requests
* @returns Readable stream of file content
*/
createReadStream?: (path: string, options?: StreamOptions) => ReadableStream;
interface StreamOptions {
/** Start byte position for range requests */
start?: number;
/** End byte position for range requests */
end?: number;
}Usage Examples:
const methods = {
createReadStream: (path, options = {}) => {
// Custom stream implementation (e.g., from database or cloud storage)
return cloudStorage.createReadStream(path, {
start: options.start,
end: options.end
});
}
};
// Example with S3
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const methods = {
createReadStream: (path, options = {}) => {
const params = {
Bucket: 'my-bucket',
Key: path.replace('/public/', '')
};
if (options.start !== undefined || options.end !== undefined) {
params.Range = `bytes=${options.start || 0}-${options.end || ''}`;
}
return s3.getObject(params).createReadStream();
}
};Custom directory reading implementation for listing directory contents.
/**
* Custom directory reader for listing directory contents
* @param path - Absolute path to the directory
* @returns Promise resolving to array of file/directory names
*/
readdir?: (path: string) => Promise<string[]>;Usage Examples:
const methods = {
readdir: async (path) => {
// Custom directory listing (e.g., from database)
const files = await database.listFiles(path);
return files.map(f => f.name);
}
};
// Example with cloud storage
const methods = {
readdir: async (path) => {
const objects = await cloudStorage.listObjects({
prefix: path.replace('/public/', ''),
delimiter: '/'
});
return objects.map(obj => obj.name);
}
};Custom error response handler for generating custom error pages and responses.
/**
* Custom error handler for generating error responses
* @param absolutePath - Absolute path that caused the error
* @param response - HTTP response object
* @param acceptsJSON - Whether client accepts JSON responses
* @param current - Current working directory
* @param handlers - Handler utilities object
* @param config - Configuration object
* @param spec - Error specification object
* @returns Promise that resolves when error response is sent
*/
sendError?: (
absolutePath: string,
response: ServerResponse,
acceptsJSON: boolean,
current: string,
handlers: object,
config: Config,
spec: ErrorSpec
) => Promise<void>;
interface ErrorSpec {
/** HTTP status code */
statusCode: number;
/** Error code identifier */
code: string;
/** Error message */
message: string;
/** Original error object (optional) */
err?: Error;
}Usage Examples:
const methods = {
sendError: async (absolutePath, response, acceptsJSON, current, handlers, config, spec) => {
// Custom error handling
if (spec.statusCode === 404) {
response.statusCode = 404;
response.setHeader('Content-Type', 'text/html');
response.end(`
<html>
<body>
<h1>Custom 404 Page</h1>
<p>The file ${absolutePath} was not found.</p>
</body>
</html>
`);
} else {
// Log error to external service
await errorLogger.log({
path: absolutePath,
error: spec.err,
statusCode: spec.statusCode
});
// Use default error handling
await handlers.sendError(absolutePath, response, acceptsJSON, current, handlers, config, spec);
}
}
};const handler = require('serve-handler');
const AWS = require('aws-sdk');
const redis = require('redis');
const s3 = new AWS.S3();
const redisClient = redis.createClient();
// Complete custom file system implementation for S3 + Redis caching
const methods = {
lstat: async (path, fromDir) => {
// Check cache first
const cached = await redisClient.get(`stat:${path}`);
if (cached) {
return JSON.parse(cached);
}
// Get from S3
try {
const object = await s3.headObject({
Bucket: 'my-static-site',
Key: path.replace('/public/', '')
}).promise();
const stats = {
size: object.ContentLength,
mtime: object.LastModified,
isDirectory: () => false,
isSymbolicLink: () => false
};
// Cache for 5 minutes
await redisClient.setex(`stat:${path}`, 300, JSON.stringify(stats));
return stats;
} catch (err) {
if (err.code === 'NotFound') {
// Check if it's a "directory" (prefix)
const objects = await s3.listObjectsV2({
Bucket: 'my-static-site',
Prefix: path.replace('/public/', '') + '/',
MaxKeys: 1
}).promise();
if (objects.Contents.length > 0) {
return {
size: 0,
mtime: new Date(),
isDirectory: () => true,
isSymbolicLink: () => false
};
}
}
throw err;
}
},
createReadStream: (path, options = {}) => {
const params = {
Bucket: 'my-static-site',
Key: path.replace('/public/', '')
};
if (options.start !== undefined || options.end !== undefined) {
params.Range = `bytes=${options.start || 0}-${options.end || ''}`;
}
return s3.getObject(params).createReadStream();
},
readdir: async (path) => {
const objects = await s3.listObjectsV2({
Bucket: 'my-static-site',
Prefix: path.replace('/public/', '') + '/',
Delimiter: '/'
}).promise();
const files = [];
// Add files in this directory
objects.Contents.forEach(obj => {
const name = obj.Key.split('/').pop();
if (name) files.push(name);
});
// Add subdirectories
objects.CommonPrefixes.forEach(prefix => {
const name = prefix.Prefix.split('/').slice(-2)[0];
if (name) files.push(name);
});
return files;
},
sendError: async (absolutePath, response, acceptsJSON, current, handlers, config, spec) => {
// Log to CloudWatch
const cloudwatch = new AWS.CloudWatchLogs();
await cloudwatch.putLogEvents({
logGroupName: '/aws/lambda/static-site',
logStreamName: 'errors',
logEvents: [{
timestamp: Date.now(),
message: JSON.stringify({
path: absolutePath,
statusCode: spec.statusCode,
message: spec.message
})
}]
}).promise();
// Use default error handling
await handlers.sendError(absolutePath, response, acceptsJSON, current, handlers, config, spec);
}
};
// Use with custom methods
await handler(request, response, { public: '/public' }, methods);Install with Tessl CLI
npx tessl i tessl/npm-serve-handler