npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

mini-broker

v2.1.2

Published

Minimal microservices framework for TypeScript with pre-built infrastructure

Downloads

20

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-broker

Step 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.json

Development 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