Skip to main content

Overview

HTTP Ledger provides full TypeScript support with comprehensive type definitions. All configuration options, log data structures, and callbacks are fully typed.

Installation

npm install http-ledger
Type definitions are included in the package - no additional @types package needed.

Basic TypeScript Usage

1

Import with types

import express from 'express';
import logger, { ApiLoggerOptions, LogData, LogLevel } from 'http-ledger';

const app = express();
2

Configure with type safety

const options: ApiLoggerOptions = {
  logBody: true,
  logResponse: true,
  maskFields: ['password', 'token'],
};

app.use(logger(options));
3

Use typed callbacks

app.use(
  logger({
    customLogLevel: (logData: LogData): LogLevel => {
      return logData.statusCode >= 500 ? 'error' : 'info';
    },
    onLog: async (logData: LogData): Promise<void> => {
      // Your custom log handling
      console.log('Received log:', logData.method, logData.url);
    },
  }),
);

Type Definitions

ApiLoggerOptions

Configuration options for the logger middleware:
interface ApiLoggerOptions {
  logBody?: boolean; // Whether to log the request body
  logResponse?: boolean; // Whether to log the response body
  logQueryParams?: boolean; // Whether to log query parameters
  excludedHeaders?: string[]; // Headers to exclude from logs
  getIpInfo?: (ip: string) => Promise<IpInfo>; // Function to get IP info
  onLog?: (logData: LogData) => void | Promise<void>; // Callback for log data
  maskFields?: string[]; // Fields to mask in logs
  customLogLevel?: (logData: LogData) => LogLevel; // Custom log level function
  customFormatter?: (logData: LogData) => unknown; // Custom formatter function
  autoGenerateRequestId?: boolean; // Auto-generate request IDs
  shouldLog?: (req: Request, res: Response) => boolean; // Function to decide whether to log
  logSampling?: number; // Sampling rate (0-1)
}

LogData

Structure of the log data object:
interface LogData {
  method: string;
  url: string;
  statusCode: number;
  timeTaken: number; // Time in milliseconds
  requestSize: number; // Size in bytes
  responseSize: number; // Size in bytes
  timestamp: Timestamp;
  headers: Record<string, string | string[] | undefined>;
  queryParams: Record<string, unknown>;
  body?: unknown; // Optional request body
  responseBody?: unknown; // Optional response body
  ipInfo?: IpInfo; // Optional IP information
  error?: LogError; // Optional error object
  userAgent?: string; // User-Agent header
  referer?: string; // Referer header
  requestContentType?: string; // Request Content-Type
  responseContentType?: string; // Response Content-Type
  httpVersion?: string; // HTTP protocol version
  requestId?: string; // Request ID
  hostname?: string; // Server hostname
  logLevel?: LogLevel; // Log level
}

LogLevel

Log level type:
type LogLevel = 'info' | 'warn' | 'error';

Timestamp

Timestamp information:
interface Timestamp {
  request: string; // ISO string of request start time
  response?: string; // ISO string of response end time
}

IpInfo

IP information structure:
interface IpInfo {
  ip?: string;
  country?: string;
  region?: string;
  city?: string;
  timezone?: string;
  [key: string]: unknown; // Additional custom fields
}

LogError

Error information in logs:
interface LogError {
  message: string;
  name?: string;
  stack?: string;
  code?: string | number;
  [key: string]: unknown; // Additional error properties
}

Type-Safe Configuration

Basic Configuration

import express from 'express';
import logger, { ApiLoggerOptions } from 'http-ledger';

const app = express();

const loggerConfig: ApiLoggerOptions = {
  logBody: true,
  logResponse: true,
  excludedHeaders: ['authorization', 'cookie'],
  maskFields: ['password', 'token', 'secret'],
};

app.use(logger(loggerConfig));

Custom Log Level Function

import { LogData, LogLevel, ApiLoggerOptions } from 'http-ledger';

const customLogLevel = (logData: LogData): LogLevel => {
  if (logData.statusCode >= 500) return 'error';
  if (logData.statusCode >= 400) return 'warn';
  if (logData.timeTaken > 1000) return 'warn'; // Slow requests
  return 'info';
};

const options: ApiLoggerOptions = {
  customLogLevel,
};

app.use(logger(options));

Custom Formatter with Type Safety

import { LogData } from 'http-ledger';

interface CustomLogData extends LogData {
  environment: string;
  service: string;
  isSlowRequest: boolean;
  requestCategory: 'read' | 'write';
}

const customFormatter = (logData: LogData): CustomLogData => {
  return {
    ...logData,
    environment: process.env.NODE_ENV || 'development',
    service: 'user-api',
    isSlowRequest: logData.timeTaken > 1000,
    requestCategory: logData.method === 'GET' ? 'read' : 'write',
  };
};

app.use(
  logger({
    customFormatter,
  }),
);

Typed IP Info Function

import { IpInfo } from 'http-ledger';

interface GeoIpResponse {
  country_name: string;
  city: string;
  region: string;
  timezone: string;
}

const getIpInfo = async (ip: string): Promise<IpInfo> => {
  try {
    const response = await fetch(`https://ipapi.co/${ip}/json/`);
    const data: GeoIpResponse = await response.json();
    
    return {
      country: data.country_name,
      city: data.city,
      region: data.region,
      timezone: data.timezone,
    };
  } catch (error) {
    console.error('Failed to fetch IP info:', error);
    return {};
  }
};

app.use(
  logger({
    getIpInfo,
  }),
);

Typed onLog Callback

import { LogData } from 'http-ledger';

const onLog = async (logData: LogData): Promise<void> => {
  // Type-safe access to log data
  const { method, url, statusCode, timeTaken } = logData;
  
  // Send to external service
  try {
    await fetch(process.env.LOG_ENDPOINT!, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${process.env.LOG_API_KEY}`,
      },
      body: JSON.stringify({
        timestamp: new Date().toISOString(),
        level: logData.logLevel || 'info',
        message: `${method} ${url} - ${statusCode} (${timeTaken}ms)`,
        data: logData,
      }),
    });
  } catch (error) {
    console.error('Failed to send log:', error);
  }
};

app.use(
  logger({
    onLog,
  }),
);

Typed shouldLog Function

import { Request, Response } from 'express';
import { ApiLoggerOptions } from 'http-ledger';

const shouldLog = (req: Request, res: Response): boolean => {
  // Skip health checks
  if (req.path === '/health') return false;
  
  // Skip successful static file requests
  if (
    req.method === 'GET' &&
    req.path.startsWith('/static/') &&
    res.statusCode === 200
  ) {
    return false;
  }
  
  // Skip OPTIONS requests
  if (req.method === 'OPTIONS') return false;
  
  return true;
};

const options: ApiLoggerOptions = {
  shouldLog,
};

app.use(logger(options));

Complete TypeScript Example

import express, { Request, Response, NextFunction } from 'express';
import logger, {
  ApiLoggerOptions,
  LogData,
  LogLevel,
  IpInfo,
} from 'http-ledger';

const app = express();
app.use(express.json());

const isDevelopment = process.env.NODE_ENV === 'development';

// Type-safe IP info function
const getIpInfo = async (ip: string): Promise<IpInfo> => {
  try {
    const response = await fetch(`https://ipapi.co/${ip}/json/`);
    const data = await response.json();
    return {
      country: data.country_name,
      city: data.city,
      region: data.region,
    };
  } catch (error) {
    return {};
  }
};

// Type-safe log level function
const customLogLevel = (logData: LogData): LogLevel => {
  if (logData.statusCode >= 500) return 'error';
  if (logData.statusCode >= 400) return 'warn';
  if (logData.timeTaken > 1000) return 'warn';
  return 'info';
};

// Type-safe custom formatter
interface EnhancedLogData extends LogData {
  environment: string;
  service: string;
  isSlowRequest: boolean;
}

const customFormatter = (logData: LogData): EnhancedLogData => ({
  ...logData,
  environment: process.env.NODE_ENV || 'development',
  service: 'api-gateway',
  isSlowRequest: logData.timeTaken > 1000,
});

// Type-safe onLog callback
const onLog = async (logData: LogData): Promise<void> => {
  if (logData.logLevel === 'error') {
    // Send to error tracking service
    console.error('Error log:', logData.error);
  }
};

// Type-safe shouldLog function
const shouldLog = (req: Request, res: Response): boolean => {
  return req.path !== '/health' && req.method !== 'OPTIONS';
};

// Type-safe configuration
const loggerOptions: ApiLoggerOptions = {
  logBody: isDevelopment,
  logResponse: isDevelopment,
  logQueryParams: true,
  excludedHeaders: ['authorization', 'cookie'],
  maskFields: ['password', 'token', 'secret', 'apiKey'],
  autoGenerateRequestId: true,
  logSampling: isDevelopment ? 1.0 : 0.1,
  getIpInfo,
  customLogLevel,
  customFormatter,
  onLog,
  shouldLog,
};

app.use(logger(loggerOptions));

// Routes
app.get('/api/users', (req: Request, res: Response) => {
  res.json({ users: [] });
});

app.post('/api/users', (req: Request, res: Response) => {
  res.status(201).json({ id: 1, ...req.body });
});

// Error handling
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  res.status(500).json({
    error: err.message,
    stack: isDevelopment ? err.stack : undefined,
  });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Type Inference

TypeScript automatically infers types in most cases:
// Type inference works automatically
app.use(
  logger({
    // TypeScript knows logData is LogData
    customLogLevel: (logData) => {
      // Auto-complete and type checking work here
      return logData.statusCode >= 500 ? 'error' : 'info';
    },
    // TypeScript knows ip is string
    getIpInfo: async (ip) => {
      return { country: 'US' };
    },
  }),
);

CommonJS with TypeScript

If using CommonJS with TypeScript:
import logger = require('http-ledger');
import type { ApiLoggerOptions, LogData } from 'http-ledger';

const options: ApiLoggerOptions = {
  logBody: true,
};

app.use(logger(options));
All type definitions are exported from the main package entry point. Import them alongside the default export for full type safety.

Build docs developers (and LLMs) love