LunarCore Node
LunarCore Node is a TypeScript-first foundational SDK for Node.js backend development. It provides secure, type-safe utilities without coupling to specific frameworks, making it suitable for any Node.js project regardless of the web framework or architecture chosen.
Design Philosophy
TypeScript-First Approach
Every component in LunarCore Node is designed with TypeScript as the primary language. This means:
- Strict type checking throughout the codebase
- Comprehensive type definitions for all APIs
- Full IntelliSense support in compatible editors
- Type inference that minimizes explicit type annotations
- No use of
anytypes in the public API
Security by Default
Security considerations are built into every utility:
- Input validation using Zod schemas
- Automatic redaction of sensitive data in logs
- Safe error serialization that prevents information leaks
- No dynamic code execution (no
evalorFunctionconstructors) - Secure defaults for all configuration options
Framework Agnostic
LunarCore Node provides foundational utilities that work with any Node.js setup:
- No dependencies on Express, Fastify, Koa, or other frameworks
- Compatible with serverless functions
- Works with traditional servers
- Suitable for microservices and monoliths alike
The philosophy is to provide building blocks, not prescribe architecture.
Modular Architecture
Import only what you need:
// Import specific modules
import { createLogger } from '@lunarcore/node/logger';
import { retry } from '@lunarcore/node/async';
import { loadEnv } from '@lunarcore/node/env';
This tree-shaking-friendly approach ensures minimal bundle size and clear dependencies.
Core Modules
Error Handling
The error system provides type-safe error classes with structured context.
Error Hierarchy
AppError (base)
├── ValidationError // 400 status
├── AuthenticationError // 401 status
├── AuthorizationError // 403 status
├── NotFoundError // 404 status
├── TimeoutError // 408 status
└── ConfigurationError // 500 status
Creating Errors
import { ValidationError, NotFoundError } from '@lunarcore/node';
// With context
throw new ValidationError('Invalid email format', {
email: userInput,
field: 'email',
constraint: 'format'
});
// Simple error
throw new NotFoundError('User not found');
Error Properties
Every AppError includes:
name- Error class namemessage- Human-readable descriptioncode- Machine-readable error codestatusCode- HTTP status codecontext- Additional structured datatimestamp- When the error occurredisOperational- Whether the error is expected/recoverable
Type Guards
import { isAppError, isOperationalError } from '@lunarcore/node';
try {
await operation();
} catch (error) {
if (isAppError(error)) {
// TypeScript knows error is AppError
console.log(error.code, error.statusCode);
if (isOperationalError(error)) {
// Can retry or handle gracefully
await handleOperationalError(error);
} else {
// Programming error, should not retry
throw error;
}
} else {
// Unknown error type
throw error;
}
}
Custom Error Classes
import { AppError } from '@lunarcore/node';
export class PaymentError extends AppError {
constructor(message: string, context?: Record<string, unknown>) {
super(message, 'PAYMENT_ERROR', 402, context);
}
}
Environment Variables
Schema-based environment variable loading with validation.
Basic Usage
import { loadEnv } from '@lunarcore/node';
import { z } from 'zod';
const env = loadEnv(z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
PORT: z.string().transform(Number),
API_KEY: z.string().min(20),
DATABASE_URL: z.string().url(),
}));
// TypeScript knows the exact types
const port: number = env.PORT;
const apiKey: string = env.API_KEY;
Helper Schemas
import { envNumber, envBoolean, envList } from '@lunarcore/node';
const env = loadEnv(z.object({
// Parse numbers with defaults
PORT: envNumber.default(3000),
MAX_CONNECTIONS: envNumber.int().min(1).max(1000),
// Parse booleans (true/false/1/0/yes/no)
ENABLE_CACHE: envBoolean.default(true),
DEBUG_MODE: envBoolean,
// Parse comma-separated lists
ALLOWED_ORIGINS: envList,
ADMIN_EMAILS: envList.transform(emails =>
emails.map(e => e.toLowerCase())
),
}));
Safe Loading
For cases where you want to handle validation errors:
import { safeLoadEnv } from '@lunarcore/node';
const result = safeLoadEnv(schema);
if (result.success) {
const env = result.data;
// Use validated environment
} else {
console.error('Environment validation failed:', result.error);
process.exit(1);
}
Environment-Specific Loading
import { loadEnv } from '@lunarcore/node';
const env = loadEnv(
schema,
process.env.NODE_ENV === 'test' ? '.env.test' : '.env'
);
Configuration Management
Flexible configuration loading with validation and caching.
Basic Configuration Manager
import { ConfigManager, createJsonLoader } from '@lunarcore/node';
const manager = new ConfigManager(
createJsonLoader('./config.json'),
(config) => {
// Validate with Zod
return configSchema.parse(config);
}
);
// Load and cache
const config = await manager.load();
// Get cached config (throws if not loaded)
const cached = manager.get();
// Force reload
await manager.reload();
Multiple Sources
import {
ConfigManager,
createJsonLoader,
createObjectLoader,
CompositeConfigLoader
} from '@lunarcore/node';
// Merge multiple sources (later sources override earlier ones)
const loader = new CompositeConfigLoader([
createJsonLoader('./config.defaults.json'),
createJsonLoader('./config.json'),
createObjectLoader(process.env) // Environment variables override
]);
const manager = new ConfigManager(loader, validator);
Watch for Changes
manager.watch((config) => {
console.log('Config reloaded:', config);
updateApplication(config);
});
Custom Loaders
import { ConfigLoader } from '@lunarcore/node';
class DatabaseConfigLoader implements ConfigLoader {
async load(): Promise<unknown> {
const rows = await db.query('SELECT key, value FROM config');
return Object.fromEntries(rows.map(r => [r.key, r.value]));
}
}
Structured Logging
Production-ready logging with context and multiple transports.
Creating Loggers
import { createLogger, LogLevel } from '@lunarcore/node';
const logger = createLogger({
level: LogLevel.INFO,
defaultContext: {
service: 'api',
version: '1.0.0',
},
});
Log Levels
logger.error('Database connection failed', {
host: 'localhost',
port: 5432
});
logger.warn('Deprecated API endpoint used', {
endpoint: '/api/v1/users'
});
logger.info('Request completed', {
method: 'GET',
path: '/api/users',
duration: 45
});
logger.debug('Cache hit', {
key: 'user:123',
ttl: 3600
});
Child Loggers
Child loggers inherit parent context:
const logger = createLogger({
defaultContext: { service: 'api' }
});
// Each request gets its own logger
app.use((req, res, next) => {
req.logger = logger.child({
requestId: generateId(),
method: req.method,
path: req.path,
});
next();
});
// Logs include requestId, method, path automatically
req.logger.info('Processing request');
Structured Output
const logger = createLogger({
level: LogLevel.INFO,
format: 'json', // or 'pretty' for development
});
// Outputs JSON
logger.info('User login', { userId: 123 });
// {"level":"info","message":"User login","userId":123,"timestamp":"..."}
Custom Transports
import { Logger, LogEntry } from '@lunarcore/node';
class DatabaseTransport {
async write(entry: LogEntry): Promise<void> {
await db.logs.insert({
level: entry.level,
message: entry.message,
context: entry.context,
timestamp: entry.timestamp,
});
}
}
const logger = createLogger({
transports: [
new ConsoleTransport(),
new DatabaseTransport(),
],
});
Sensitive Data Redaction
Automatic redaction of sensitive fields:
logger.info('Login attempt', {
email: 'user@example.com',
password: 'secret123', // Automatically redacted
apiKey: 'sk-abc123', // Shows only prefix
});
// Output: password: "[REDACTED]", apiKey: "sk-ab...[REDACTED]"
Async Utilities
Safe async operations with retry, timeout, and concurrency control.
Retry with Backoff
import { retry } from '@lunarcore/node';
const data = await retry(
async () => {
const response = await fetch('https://api.example.com/data');
if (!response.ok) throw new Error('Request failed');
return response.json();
},
{
maxAttempts: 3,
initialDelay: 1000, // 1 second
maxDelay: 10000, // 10 seconds
backoffMultiplier: 2, // Exponential backoff
retryIf: (error) => {
// Only retry on network errors, not validation errors
return error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT';
},
}
);
Timeout
import { withTimeout } from '@lunarcore/node';
try {
const result = await withTimeout(
longRunningOperation(),
5000, // 5 seconds
'Operation timed out after 5 seconds'
);
} catch (error) {
if (error.name === 'TimeoutError') {
// Handle timeout
}
}
Safe Result Type
import { asyncResult } from '@lunarcore/node';
const result = await asyncResult(riskyOperation());
if (result.ok) {
console.log('Success:', result.value);
} else {
console.error('Failed:', result.error);
}
// No try-catch needed
Concurrency Control
import { promiseLimit } from '@lunarcore/node';
const urls = ['url1', 'url2', /* ...100 more */ ];
// Process 5 at a time
const results = await promiseLimit(
urls,
async (url) => {
const response = await fetch(url);
return response.json();
},
5 // Max concurrent operations
);
Batch Processing
import { batchProcess } from '@lunarcore/node';
const items = [/* thousands of items */];
await batchProcess(
items,
async (batch) => {
// Process batch of 100 items
await database.insertMany(batch);
},
{ batchSize: 100 }
);
Debounce and Throttle
import { debounce, throttle } from '@lunarcore/node';
// Debounce: wait until calls stop
const saveDebounced = debounce(async (data) => {
await saveToDatabase(data);
}, 1000);
// Throttle: execute at most once per interval
const logThrottled = throttle(async (event) => {
await logEvent(event);
}, 5000);
Integration Examples
Express.js Integration
import express from 'express';
import { createLogger, loadEnv, ValidationError } from '@lunarcore/node';
import { z } from 'zod';
const env = loadEnv(z.object({
PORT: z.string().transform(Number).default('3000'),
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
}));
const logger = createLogger({
level: env.LOG_LEVEL,
defaultContext: { service: 'api' },
});
const app = express();
// Request logging middleware
app.use((req, res, next) => {
const start = Date.now();
const requestLogger = logger.child({
requestId: crypto.randomUUID(),
method: req.method,
path: req.path,
});
res.on('finish', () => {
requestLogger.info('Request completed', {
statusCode: res.statusCode,
duration: Date.now() - start,
});
});
req.logger = requestLogger;
next();
});
// Error handling middleware
app.use((err, req, res, next) => {
if (err instanceof ValidationError) {
res.status(err.statusCode).json({
error: err.message,
code: err.code,
context: err.context,
});
} else {
req.logger.error('Unhandled error', { error: err });
res.status(500).json({ error: 'Internal server error' });
}
});
app.listen(env.PORT, () => {
logger.info('Server started', { port: env.PORT });
});
Fastify Integration
import Fastify from 'fastify';
import { createLogger, retry } from '@lunarcore/node';
const logger = createLogger({ level: 'info' });
const fastify = Fastify({ logger });
fastify.addHook('onRequest', async (request) => {
request.requestId = crypto.randomUUID();
});
fastify.get('/users/:id', async (request, reply) => {
const user = await retry(
() => fetchUser(request.params.id),
{ maxAttempts: 3 }
);
return user;
});
await fastify.listen({ port: 3000 });
Serverless Function
import { Handler } from 'aws-lambda';
import { createLogger, ValidationError } from '@lunarcore/node';
const logger = createLogger({ defaultContext: { function: 'processUser' } });
export const handler: Handler = async (event) => {
const requestLogger = logger.child({ requestId: event.requestContext.requestId });
try {
requestLogger.info('Processing request', { body: event.body });
const result = await processData(JSON.parse(event.body));
return {
statusCode: 200,
body: JSON.stringify(result),
};
} catch (error) {
requestLogger.error('Request failed', { error });
if (error instanceof ValidationError) {
return {
statusCode: error.statusCode,
body: JSON.stringify({ error: error.message }),
};
}
return {
statusCode: 500,
body: JSON.stringify({ error: 'Internal server error' }),
};
}
};
Best Practices
Environment Variables
- Use schemas to validate all environment variables at startup
- Provide defaults for non-sensitive values
- Fail fast if required variables are missing
- Never commit
.envfiles to version control
Error Handling
- Use custom error classes for domain-specific errors
- Include context in errors for debugging
- Distinguish between operational and programming errors
- Log errors with appropriate severity levels
Logging
- Use structured logging with consistent field names
- Include correlation IDs for request tracing
- Use appropriate log levels (don't log sensitive data)
- Create child loggers with request-specific context
Async Operations
- Use retry logic for transient failures
- Set timeouts for external operations
- Limit concurrency for batch operations
- Handle errors appropriately (don't silence them)
Configuration
- Separate configuration from code
- Use environment-specific config files
- Validate configuration on load
- Document all configuration options
TypeScript Configuration
Recommended tsconfig.json for use with LunarCore Node:
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Performance Considerations
Bundle Size
LunarCore Node is designed for minimal bundle size:
- Tree-shaking friendly exports
- No unnecessary dependencies
- Modular architecture
Runtime Performance
- Lazy initialization where possible
- Cached computations
- Efficient error handling
- Minimal overhead in logging
Memory Usage
- No memory leaks in long-running processes
- Proper cleanup of resources
- Efficient data structures
Security Best Practices
Input Validation
Always validate external input:
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email(),
age: z.number().int().min(0).max(150),
});
const user = userSchema.parse(untrustedInput);
Secrets Management
Never log or expose secrets:
// Good - secrets are redacted
logger.info('Connecting to database', {
host: dbHost,
password: dbPassword, // Automatically redacted
});
// Bad - secrets in error messages
throw new Error(`Failed to connect with password: ${dbPassword}`);
Error Information Disclosure
Don't expose internal details to clients:
try {
await operation();
} catch (error) {
// Good - generic message to client
res.status(500).json({ error: 'Operation failed' });
// Good - detailed logging internally
logger.error('Operation failed', { error, details: error.stack });
}
Migration from Other Libraries
From Winston
// Winston
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
transports: [new winston.transports.Console()],
});
// LunarCore Node
import { createLogger, LogLevel } from '@lunarcore/node';
const logger = createLogger({ level: LogLevel.INFO });
From dotenv
// dotenv
require('dotenv').config();
const port = parseInt(process.env.PORT);
// LunarCore Node
import { loadEnv, envNumber } from '@lunarcore/node';
const env = loadEnv(z.object({
PORT: envNumber,
}));
const port = env.PORT; // Already parsed as number
API Reference
Complete TypeScript definitions are included with the package. Use your IDE's autocomplete and type checking for full API documentation.
Core Exports
// Error handling
export {
AppError,
ValidationError,
AuthenticationError,
AuthorizationError,
NotFoundError,
TimeoutError,
ConfigurationError,
isAppError,
isOperationalError,
};
// Environment
export {
loadEnv,
safeLoadEnv,
envNumber,
envBoolean,
envList,
};
// Config
export {
ConfigManager,
createJsonLoader,
createObjectLoader,
CompositeConfigLoader,
};
// Logger
export {
createLogger,
Logger,
LogLevel,
ConsoleTransport,
JsonTransport,
};
// Async
export {
retry,
withTimeout,
asyncResult,
promiseLimit,
batchProcess,
debounce,
throttle,
sleep,
};
Troubleshooting
Common Issues
Type Errors After Installation
Run npm install @types/node to ensure Node.js type definitions are available.
Environment Variables Not Loading
Check that your .env file is in the correct location and properly formatted.
Zod Validation Errors
Read the error messages carefully - Zod provides detailed information about what validation failed.
Debug Mode
Enable debug logging to troubleshoot issues:
const logger = createLogger({ level: LogLevel.DEBUG });
Contributing
Contributions are welcome. Please follow the existing code style and include tests for new features.
License
LunarCore Node is released under the MIT License.
Related Projects
- LunarCore Spec - Specification for the LunarCore ecosystem
- LunarCore Fabric - Fabric mod development SDK