@smmorshed/server-utils v0.0.1
Server Utils
A professional utility package for Express.js-based Node servers, written in TypeScript with modern best practices.
Features
- 📦 Dual ESM + CommonJS support
- 🛠️ Tree-shakable (only import what you need)
- 💪 Fully typed with TypeScript
- ✅ Comprehensive test coverage
Installation
npm install @smmorshed/server-utils
Utilities
Logger
A lightweight, customizable logger for Node.js applications with multiple transport options.
Features
- Multiple Log Levels: debug, info, warn, error
- Multiple Transports: console, file, memory
- Timestamp Support: automatic or custom timestamp formats
- Context Support: attach persistent contextual data to logs
- Asynchronous Logging: non-blocking log operations with buffering
- Customizable Formatters: fully control log output format
- Tree-shakable: only import the features you need
Basic Usage
import { Logger } from '@smmorshed/server-utils';
// Create with default settings
const logger = new Logger();
// Basic usage
logger.info('Server started on port 3000');
logger.warn('Memory usage high', { memoryUsage: process.memoryUsage() });
logger.error('Database connection failed', { retryCount: 3 });
// Change log level at runtime
logger.setLevel('debug');
Advanced Configuration
import { Logger } from '@smmorshed/server-utils';
// Configure with options
const customLogger = new Logger({
level: 'debug', // 'debug' | 'info' | 'warn' | 'error'
timestamp: true, // include timestamps in logs
formatter: (level, message, meta) => {
// Custom formatter
return `${level.toUpperCase()}: ${message} ${meta ? JSON.stringify(meta) : ''}`;
},
context: { // Persistent context data for all logs
service: 'payment-service',
version: '1.0.0'
},
transports: [
{ type: 'console', colors: true },
{ type: 'file', filePath: 'logs/app.log', maxSize: 10485760, maxFiles: 5 }
],
async: true, // Use non-blocking logging
bufferSize: 100, // Max number of logs to buffer before flushing
flushInterval: 1000 // Flush interval in milliseconds
});
Using Multiple Transports
import { Logger } from '@smmorshed/server-utils';
const multiTransportLogger = new Logger({
// Use multiple transports
transports: [
{ type: 'console' }, // Console output
{ type: 'file', filePath: 'logs/app.log' }, // File output
{ type: 'memory', maxEntries: 1000 } // In-memory storage (useful for testing)
]
});
multiTransportLogger.info('This message goes to all transports');
Context Support for Request Tracking
import { Logger } from '@smmorshed/server-utils';
import { v4 as uuidv4 } from 'uuid';
// Create a base logger
const baseLogger = new Logger();
// In an Express middleware
app.use((req, res, next) => {
const requestId = uuidv4();
// Create a logger with context for this request
req.logger = baseLogger.withContext({
requestId,
path: req.path,
method: req.method,
ip: req.ip
});
next();
});
// Later in a route handler
app.get('/users', (req, res) => {
req.logger.info('Getting users', { query: req.query });
// Log will include requestId, path, method, and ip automatically
// ...process request
});
Express.js Application Example
import express from 'express';
import { Logger } from '@smmorshed/server-utils';
const app = express();
const logger = new Logger({ level: process.env.NODE_ENV === 'production' ? 'info' : 'debug' });
// Request logging middleware
app.use((req, res, next) => {
const start = Date.now();
logger.debug('Request received', {
method: req.method,
path: req.path,
query: req.query,
});
res.on('finish', () => {
const duration = Date.now() - start;
const logLevel = res.statusCode >= 400 ? 'error' : 'info';
logger[logLevel]('Response sent', {
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration: `${duration}ms`,
});
});
next();
});
// Route handlers
app.get('/api/users', (req, res) => {
try {
logger.debug('Fetching users');
// Database operations...
const users = [{ id: 1, name: 'John' }];
logger.info('Users fetched successfully', { count: users.length });
res.json(users);
} catch (error) {
logger.error('Failed to fetch users', {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined
});
res.status(500).json({ error: 'Internal server error' });
}
});
// Server startup
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
logger.info(`Server started`, { port: PORT, env: process.env.NODE_ENV });
});
Structured JSON Logging
import { Logger } from '@smmorshed/server-utils';
// Create a JSON formatter for structured logging
const logger = new Logger({
formatter: (level, message, meta) => {
const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
...meta,
service: 'payment-api',
environment: process.env.NODE_ENV,
};
return JSON.stringify(logEntry);
},
});
// Now logs will be in JSON format for easy parsing by log aggregation tools
logger.info('Payment processed', {
paymentId: '12345',
amount: 99.99,
currency: 'USD',
userId: 'user_abc123',
});
// Output: {"timestamp":"2023-04-16T12:34:56.789Z","level":"info","message":"Payment processed","paymentId":"12345","amount":99.99,"currency":"USD","userId":"user_abc123","service":"payment-api","environment":"production"}
Asynchronous Logging
import { Logger } from '@smmorshed/server-utils';
// Create logger with async logging enabled
const asyncLogger = new Logger({
async: true,
bufferSize: 50, // Flush after 50 log entries
flushInterval: 2000 // Or flush every 2 seconds
});
// These logs will be buffered and won't block the main thread
for (let i = 0; i < 1000; i++) {
asyncLogger.info(`Processing item ${i}`);
}
// Make sure to close the logger when your application exits
process.on('SIGTERM', () => {
asyncLogger.close(); // This will flush any remaining logs
process.exit(0);
});
API Documentation
Logger
A class that provides leveled logging functionality with customizable formatting and multiple transports.
Constructor
constructor(options?: LoggerOptions)
Parameters:
options
(optional): Configuration options for the logger
LoggerOptions Interface
interface LoggerOptions {
level?: LogLevel; // Minimum log level ('debug', 'info', 'warn', 'error')
timestamp?: boolean; // Whether to include timestamps in logs
formatter?: (level: LogLevel, message: string, meta?: Record<string, unknown>) => string;
context?: Record<string, unknown>; // Context data for all logs
transports?: TransportConfig[]; // Where logs should be sent
async?: boolean; // Whether logging should be asynchronous
bufferSize?: number; // Buffer size for async logging
flushInterval?: number; // Flush interval in ms for async logging
}
Transport Types
The Logger supports the following transport configurations:
// Console Transport
{
type: 'console',
colors?: boolean // Whether to use colors (not implemented yet)
}
// File Transport
{
type: 'file',
filePath: string, // Path to log file
maxSize?: number, // Max file size before rotation (default: 10MB)
maxFiles?: number // Max number of files to keep (default: 5)
}
// Memory Transport (useful for testing)
{
type: 'memory',
maxEntries?: number // Max log entries to keep in memory (default: 1000)
}
Methods
debug(message, meta?)
debug(message: string, meta?: Record<string, unknown>): void
Logs a debug message.
Parameters:
message
: The message to logmeta
(optional): Additional metadata to include with the log
info(message, meta?)
info(message: string, meta?: Record<string, unknown>): void
Logs an info message.
Parameters:
message
: The message to logmeta
(optional): Additional metadata to include with the log
warn(message, meta?)
warn(message: string, meta?: Record<string, unknown>): void
Logs a warning message.
Parameters:
message
: The message to logmeta
(optional): Additional metadata to include with the log
error(message, meta?)
error(message: string, meta?: Record<string, unknown>): void
Logs an error message.
Parameters:
message
: The message to logmeta
(optional): Additional metadata to include with the log
withContext(context)
withContext(context: Record<string, unknown>): Logger
Creates a new logger with the provided context merged with the existing logger's context.
Parameters:
context
: Context data to include with all logs from the new logger- Returns: A new Logger instance with the combined context
setLevel(level)
setLevel(level: LogLevel): void
Sets the minimum log level.
Parameters:
level
: The new minimum log level ('debug', 'info', 'warn', or 'error')
getLevel()
getLevel(): LogLevel
Gets the current log level.
Returns: The current log level
flush()
flush(): void
Flushes any buffered log entries. Mainly useful with asynchronous logging.
close()
close(): void
Closes the logger, releasing any resources and flushing any buffered logs.
Development
# Install dependencies
npm install
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Build the package
npm run build
# Generate API documentation
npm run docs
License
MIT