Skip to main content

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 any types 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 eval or Function constructors)
  • 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 name
  • message - Human-readable description
  • code - Machine-readable error code
  • statusCode - HTTP status code
  • context - Additional structured data
  • timestamp - When the error occurred
  • isOperational - 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: '[email protected]',
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 .env files 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.