Home/Middleware

Middleware

Middleware allows you to intercept, inspect, and transform messages as they flow between host and worker. This is useful for logging, validation, timing, and adding metadata.

How Middleware Works

Middleware functions receive each message and a direction indicator ('outgoing' or 'incoming'). They can:

  • Inspect messages for debugging/logging
  • Transform messages by returning modified versions (or return void to leave unchanged)
  • Validate message structure before processing
  • Track timing and performance metrics

Middleware is applied in order, creating a pipeline where each function processes the message before passing it to the next.

Message Sealing

Messages are sealed (frozen) before being passed to middleware. You can modify existing message properties but cannot add new ones.

Creating Middleware

A middleware function has the signature:

typescript
type Middleware<T> = (
  message: AnyMessage<T>,
  direction: 'outgoing' | 'incoming'
) => AnyMessage<T> | void | Promise<AnyMessage<T> | void>;

Returning void passes the original message through unchanged.

Here's a logging middleware that tracks all message traffic:

host.ts
/**
 * Logging middleware - logs all messages with direction
 */
const loggingMiddleware: Middleware<Messages> = (message, direction) => {
  const arrow = direction === 'outgoing' ? '>>>' : '<<<';
  console.log(
    `[${direction.toUpperCase()}] ${arrow} ${message.type}`,
    message.payload
  );
  return message;
};

You can also create middleware that tracks timing:

host.ts
/**
 * Timing middleware - tracks how long messages take
 * Note: This is a simplified example; real timing would need request correlation
 */
const timingMiddleware: Middleware<Messages> = (message, direction) => {
  if (direction === 'outgoing') {
    console.log(`[TIMING] Request sent at: ${new Date().toISOString()}`);
  } else {
    console.log(`[TIMING] Response received at: ${new Date().toISOString()}`);
  }
  return message;
};

Using Middleware on the Host

Pass middleware to createWorker as an array. Middleware executes in array order:

host.ts
// Create worker with middleware pipeline
  // Middleware is applied in order: logging -> timing
  const worker = await createWorker<Messages>({
    script: join(__dirname, 'worker.ts'),
    timeout: 10000,
    middleware: [loggingMiddleware, timingMiddleware],
  });

Using Middleware on the Worker

Workers also support middleware pipelines:

worker.ts
/**
 * Worker-side logging middleware
 */
const workerLoggingMiddleware: Middleware<Messages> = (message, direction) => {
  console.log(`[WORKER ${direction}] Processing: ${message.type}`);
  return message;
};

Configure it when starting the worker server:

worker.ts
const server = await startWorkerServer(handlers, {
    middleware: [workerLoggingMiddleware],
  });

Middleware Use Cases

Logging and Debugging

Log all messages for debugging during development:

typescript
const debugMiddleware: Middleware<Messages> = (message, direction) => {
  console.log(
    `[${direction}] ${message.type}:`,
    JSON.stringify(message.payload)
  );
  return message;
};

Read-Only Middleware

For middleware that only inspects messages without modification, you can return void:

typescript
const debugMiddleware: Middleware<Messages> = (message, direction) => {
  console.log(
    `[${direction}] ${message.type}:`,
    JSON.stringify(message.payload)
  );
  // Returning void is equivalent to returning the original message
  // No modification needed
};

This pattern is useful for logging, monitoring, and debugging without affecting message flow.

Validation

Validate message structure before processing:

typescript
const validationMiddleware: Middleware<Messages> = (message, direction) => {
  if (direction === 'outgoing' && !message.payload) {
    throw new Error(`Message ${message.type} missing payload`);
  }
  return message;
};

Adding Metadata

Add tracing IDs or timestamps to messages:

typescript
const tracingMiddleware: Middleware<Messages> = (message, direction) => {
  if (direction === 'outgoing') {
    return {
      ...message,
      payload: {
        ...message.payload,
        _traceId: crypto.randomUUID(),
      },
    };
  }
  return message;
};

Order of Execution

For outgoing messages, middleware executes in array order:

plaintext
[middleware1, middleware2, middleware3]
     ↓           ↓           ↓
  first       second       third

For incoming messages, the same order applies - no automatic reversal.

See Also

  • {% example-link middleware %} - Complete middleware example
  • Error Handling - How errors propagate through the system