Refactoring Summary: Timestamp Normalization, Structured Logging, ETag Support, and Rate Limit Improvements
This refactoring addresses four major production readiness issues across the StellarKit API:
- Inconsistent timestamp formats across endpoints
- Unstructured console logging without log level control
- Missing ETag support for cached endpoints
- Basic rate limiter error responses without proper headers
Files Modified:
package.json- Added pino and pino-pretty dependenciessrc/utils/logger.js- New file, creates logger instance with NODE_ENV awareness.env.example- Added LOG_LEVEL configurationsrc/middleware/errorHandler.js- Replaced console.error with structured loggersrc/websocket.js- Replaced 5 console calls with loggersrc/services/cache.js- Replaced 2 console.debug calls with loggersrc/routes/stream.js- Replaced 7 console calls with loggersrc/index.js- Updated cache warming functions to use logger
Features:
- Respects
LOG_LEVELenvironment variable (fatal, error, warn, info, debug, trace) - JSON output in production (NODE_ENV=production)
- Pretty-printed output with timestamps in development
- Structured logging with context objects instead of string concatenation
19 total console calls replaced across the codebase
Files Created:
src/middleware/etag.js- New ETag middleware that:- Generates SHA256-based ETags from response bodies
- Sets ETag header on successful responses
- Returns 304 Not Modified with no body when If-None-Match matches
- Sets Cache-Control headers appropriately
Files Modified:
src/index.js- Applied etagMiddleware to cached routes:/network-status/fee-estimate/account/asset/dex/liquidity-pools/claimable-balances/network
Tests Added:
tests/cache.integration.test.js- Added comprehensive ETag tests:- Verifies ETag header format (SHA256 hex)
- Tests 304 response when If-None-Match matches
- Tests 200 response with new ETag when mismatch occurs
Files Modified:
src/middleware/rateLimiter.js- Reimplemented with:- Custom handler function for better error control
- Retry-After header (in seconds until reset)
- X-RateLimit-Limit header
- X-RateLimit-Remaining header (always 0 when rate limited)
- X-RateLimit-Reset header (ISO timestamp)
- Structured error response:
{ success: false, error: { type: "RateLimitExceeded", message, retryAfter, resetAt } } - Skip rate limiting for /health endpoint
Tests Updated:
tests/rateLimiter.test.js- Updated to verify:- New error response format (type: "RateLimitExceeded")
- Presence of all rate limit headers
- Proper retry-after and reset values
Status: Already properly implemented
- All endpoints use
toISOTimestamp()utility fromsrc/utils/response.js - Field names consistently use camelCase: createdAt, closedAt, updatedAt, etc.
- All timestamps returned as ISO 8601 strings
- Tests verify proper timestamp formatting
- All timestamp fields across all endpoints converted using
toISOTimestamp() - Handles multiple input formats (Unix seconds, milliseconds, ISO strings, Date objects)
- Returns ISO 8601 format (e.g., "2024-01-15T10:30:00Z")
- createdAt (not created_at, timestamp, or numeric values)
- closedAt (not closed_at)
- updatedAt (not last_modified)
- lastModifiedLedger (consistent camelCase for ledger references)
- 19 total replacements across 5 files
- LOG_LEVEL environment variable controls verbosity
- JSON output in production, pretty-printed in development
- Structured logging with context objects
- ETag generated from response body hash
- Requests with matching If-None-Match return 304 Not Modified with no body
- Requests with non-matching or absent If-None-Match return full response with new ETag
- Tests cover both 304 and 200 paths
- Retry-After header set to seconds until limit resets (900 seconds)
- X-RateLimit-Limit header shows max requests allowed
- X-RateLimit-Remaining header shows remaining requests (0 when limited)
- X-RateLimit-Reset header shows ISO timestamp of reset
- Response body includes { type: "RateLimitExceeded", message, retryAfter, resetAt }
Add to .env:
# Controls logger verbosity: fatal, error, warn, info, debug, trace
LOG_LEVEL=info
# Optional: Control rate limiting (defaults shown)
RATE_LIMIT_MAX=100
{
"dependencies": {
"pino": "^8.18.0"
},
"devDependencies": {
"pino-pretty": "^10.3.2"
}
}Run tests to verify all changes:
npm test -- tests/cache.integration.test.js # ETag tests
npm test -- tests/rateLimiter.test.js # Rate limit tests
npm test -- tests/utils.toISOTimestamp.test.js # Timestamp tests
npm test # All tests- ETags can now be used to reduce bandwidth: implement If-None-Match header to get 304 responses
- Rate limit errors now include Retry-After header for proper backoff implementation
- All timestamp fields are guaranteed to be ISO 8601 strings
- No breaking changes to successful response bodies
- Logger output is structured JSON in production for easy parsing and aggregation
- LOG_LEVEL can be dynamically changed via environment variables
- Rate limiter provides more detailed information through headers
- All error responses follow consistent format:
{ success: false, error: {...} }
None. All changes are backward compatible for successful responses. Rate limiter error format changed slightly but still returns 429 status.