CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-serve-handler

HTTP request routing and static file serving library that powers the serve package

Pending
Overview
Eval results
Files

file-system.mddocs/

File System Integration

Pluggable file system abstraction that allows custom implementations for different storage backends, cloud storage, or specialized file handling requirements.

Capabilities

Methods Override Object

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>;
}

File Statistics

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);

Symbolic Link Resolution

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);
  }
};

File Content Streaming

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();
  }
};

Directory Listing

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);
  }
};

Error Handling

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);
    }
  }
};

Complete Custom Implementation Example

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

docs

configuration.md

file-system.md

index.md

request-handling.md

tile.json