mini-broker
v2.1.2
Published
Minimal microservices framework for TypeScript with pre-built infrastructure
Downloads
20
Maintainers
Readme
miniBroker
A minimal microservices framework for TypeScript/JavaScript.
Small surface area, zero magic, type-safe RPC and Pub/Sub.
✨ Philosophy
Microservices shouldn't require a PhD to understand. miniBroker gives you four concepts: Broker, Service, Transport, Schema. That's it.
No decorators. No dependency injection. No magic. Just explicit, type-safe code that does what it says.
🎯 Goals
- Minimal API surface: Broker, Service, Transport, Schema
- Type-safe RPC + Pub/Sub with zero runtime surprises
- Pluggable transports: in-memory (dev), Redis (production)
- Explicit lifecycle hooks - no black boxes
- Batteries included: CLI, TypeScript, Docker, logging, metrics
🚫 Non-Goals
- Competing with NestJS or Moleculer feature-for-feature
- Auto-wired decorators, opinionated ORMs, DI containers
- Enterprise orchestration platform
📦 Install
npm install mini-broker🛠 Getting Started
Step 1: Install
npm install mini-brokerStep 2: Create Your First Service
Create a file user.service.ts:
import { defineService, createSchema } from 'mini-broker';
// Define type-safe schemas
const userSchema = createSchema<{ name: string; email: string; age: number }>((data: any) => {
if (!data.name || typeof data.name !== 'string') throw new Error('Invalid name');
if (!data.email || !data.email.includes('@')) throw new Error('Invalid email');
if (!data.age || typeof data.age !== 'number') throw new Error('Invalid age');
return data;
});
export const userService = defineService({
name: 'users',
// RPC actions
actions: {
create: async (params, ctx) => {
const user = userSchema.validate(params);
ctx.logger.info('Creating user', { user });
// Simulate database save
const savedUser = { ...user, id: Math.floor(Math.random() * 1000) };
return savedUser;
},
getById: async ({ id }, ctx) => {
ctx.logger.debug('Getting user', { id });
return { id, name: 'John Doe', email: '[email protected]', age: 30 };
}
},
// Event handlers
events: {
'user.created': async (user, ctx) => {
ctx.logger.info('User created event received', { userId: user.id });
},
'notification.*': async (data, ctx) => {
ctx.logger.info('Notification event', { event: ctx.event, data });
}
},
// Lifecycle hooks
hooks: {
before: async (ctx) => {
ctx.logger.debug('Before action', { action: ctx.action });
},
after: async (ctx, result) => {
ctx.logger.debug('After action', { action: ctx.action, hasResult: !!result });
},
error: async (ctx, error) => {
ctx.logger.error('Action failed', { action: ctx.action, error: error.message });
}
}
});Step 3: Create and Configure Broker
Create a file app.ts:
import { createBroker } from 'mini-broker';
import { userService } from './user.service';
// Create broker instance
const broker = createBroker({
nodeId: 'my-app-node',
transport: 'in-memory', // Use 'redis' for production
logger: {
level: 'info',
format: 'pretty' // Use 'json' for production
}
});
// Load your services
broker.loadService(userService);
export { broker };Step 4: Start and Use the Broker
Create a file main.ts:
import { broker } from './app';
async function main() {
// Start the broker
await broker.start();
console.log('🚀 Broker started!');
try {
// Make RPC calls
const user = await broker.call('users.create', {
name: 'Alice Johnson',
email: '[email protected]',
age: 28
});
console.log('User created:', user);
// Get user by ID
const foundUser = await broker.call('users.getById', { id: 123 });
console.log('User found:', foundUser);
// Emit events
await broker.emit('user.created', user);
await broker.emit('notification.email', { to: '[email protected]', subject: 'Welcome!' });
} catch (error) {
console.error('Error:', error.message);
} finally {
// Graceful shutdown
await broker.stop();
console.log('🛑 Broker stopped');
}
}
main().catch(console.error);Step 5: Run Your Application
# Install TypeScript runner (if not already installed)
npm install -g tsx
# Run your application
npx tsx main.ts📋 Quick Reference
Core API
// Create broker
const broker = createBroker({ nodeId, transport, logger });
// Define service
const service = defineService({ name, actions, events, hooks });
// Create schema
const schema = createSchema<T>((data) => { /* validate */ return data; });
// Broker methods
await broker.start(); // Start broker
await broker.stop(); // Stop broker
broker.loadService(service); // Load service
const result = await broker.call('service.action', params); // RPC call
await broker.emit('event', data); // Emit event🔎 Logging & Observability
Debugging microservices without proper logging is like playing darts blindfolded.
Structured Logging
- Powered by pino - fast, structured JSON logs
- Correlation IDs - trace requests across services
- Configurable levels: debug, info, warn, error
- Multiple formats: pretty (dev) or json (production)
Example Log Output
{
"level": "info",
"time": "2025-01-11T12:00:00.000Z",
"node": "node-1",
"service": "users",
"action": "create",
"duration_ms": 2,
"correlationId": "abc-123"
}Configure Logging
const broker = createBroker({
nodeId: 'my-node',
transport: 'in-memory',
logger: {
level: 'debug', // debug | info | warn | error
format: 'pretty' // pretty | json
}
});🚀 Development Workflow
Project Structure
my-microservice/
├── src/
│ ├── services/
│ │ ├── user.service.ts
│ │ └── notification.service.ts
│ ├── app.ts
│ └── main.ts
├── package.json
└── tsconfig.jsonDevelopment Scripts
{
"scripts": {
"dev": "tsx src/main.ts",
"build": "tsc",
"start": "node dist/main.js"
}
}Running in Development
npm run dev🌍 Production Setup
Redis Transport (Recommended)
const broker = createBroker({
nodeId: process.env.NODE_ID || 'prod-node-1',
transport: 'redis',
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
},
logger: {
level: 'info',
format: 'json' // Structured logs for production
}
});Docker Setup
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist ./dist
EXPOSE 3000
CMD ["node", "dist/main.js"]Docker Compose
# docker-compose.yml
version: '3.8'
services:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
app:
build: .
environment:
- NODE_ID=app-node-1
- REDIS_HOST=redis
depends_on:
- redis📡 Transports
In-Memory (Default)
const broker = createBroker({
nodeId: 'dev-node',
transport: 'in-memory', // Single process, perfect for development
});Redis (Production)
const broker = createBroker({
nodeId: 'prod-node-1',
transport: 'redis',
redis: {
host: 'localhost',
port: 6379,
password: 'optional',
},
});Custom Transport
import { Transport } from 'mini-broker';
class MyTransport implements Transport {
async connect() { /* ... */ }
async call(service, action, params, correlationId) { /* ... */ }
// ... implement interface
}
const broker = createBroker({
nodeId: 'custom-node',
transport: new MyTransport(),
});🏗 Architecture
Core Interfaces
interface Broker {
loadService(service: Service): void;
start(): Promise<void>;
stop(): Promise<void>;
call(action: string, params: any): Promise<any>;
emit(event: string, data: any): Promise<void>;
}
interface Service {
name: string;
actions: Record<string, ActionHandler>;
events: Record<string, EventHandler>;
hooks?: LifecycleHooks;
}
interface Transport {
connect(): Promise<void>;
disconnect(): Promise<void>;
call(service: string, action: string, params: any, correlationId: string): Promise<any>;
emit(event: string, data: any, correlationId: string): Promise<void>;
subscribe(pattern: string, handler: EventHandler): Promise<void>;
}
interface Schema<T> {
validate(data: unknown): T;
}Zero Magic Promise
- No decorators - explicit function calls
- No DI containers - pass dependencies explicitly
- No auto-discovery - load services manually
- No hidden behavior - every action is explicit
📚 Complete Example
// services/user.service.ts
import { defineService, createSchema } from 'mini-broker';
const userSchema = createSchema<{ name: string; email: string }>((data: any) => {
if (!data.name) throw new Error('Name required');
if (!data.email?.includes('@')) throw new Error('Valid email required');
return data;
});
export const userService = defineService({
name: 'users',
actions: {
create: async (params, ctx) => {
const user = userSchema.validate(params);
const savedUser = { ...user, id: Date.now() };
// Emit event for other services
await ctx.broker?.emit('user.created', savedUser);
return savedUser;
}
}
});
// services/email.service.ts
export const emailService = defineService({
name: 'email',
actions: {
send: async ({ to, subject }, ctx) => {
ctx.logger.info('Sending email', { to, subject });
return { sent: true, messageId: 'msg-123' };
}
},
events: {
'user.created': async (user, ctx) => {
// Send welcome email when user is created
await ctx.broker?.call('email.send', {
to: user.email,
subject: 'Welcome!'
});
}
}
});
// app.ts
import { createBroker } from 'mini-broker';
import { userService } from './services/user.service';
import { emailService } from './services/email.service';
const broker = createBroker({
nodeId: 'main-app',
transport: 'in-memory',
logger: { level: 'info', format: 'pretty' }
});
broker.loadService(userService);
broker.loadService(emailService);
export { broker };
// main.ts
import { broker } from './app';
async function main() {
await broker.start();
// Create user - this will automatically send welcome email
const user = await broker.call('users.create', {
name: 'Alice',
email: '[email protected]'
});
console.log('User created:', user);
process.on('SIGINT', async () => {
await broker.stop();
process.exit(0);
});
}
main().catch(console.error);🔧 Advanced Features
Error Handling
const service = defineService({
name: 'payments',
actions: {
charge: async ({ amount }, ctx) => {
try {
return { success: true, transactionId: 'txn-123' };
} catch (error) {
ctx.logger.error('Payment failed', { error: error.message });
throw new Error('Payment processing failed');
}
}
},
hooks: {
error: async (ctx, error) => {
await ctx.broker?.emit('payment.failed', {
action: ctx.action,
error: error.message
});
}
}
});🚀 Performance Tips
- Use Redis transport for production distributed systems
- Enable JSON logging for structured log analysis
- Implement graceful shutdown with SIGINT/SIGTERM handlers
- Use correlation IDs for request tracing across services
- Keep actions lightweight - offload heavy work to background jobs
🔍 Troubleshooting
Common Issues
Service not found error:
// Make sure service is loaded before calling
broker.loadService(myService);
await broker.start();Schema validation errors:
// Check your validation logic
const schema = createSchema<T>((data) => {
if (!data.field) throw new Error('Field required');
return data;
});Redis connection issues:
// Check Redis configuration
const broker = createBroker({
transport: 'redis',
redis: {
host: 'localhost', // Verify host
port: 6379, // Verify port
password: 'pass' // If required
}
});📄 License
MIT
