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 🙏

© 2026 – Pkg Stats / Ryan Hefner

nest-mediator

v0.0.5

Published

A mediator pattern implementation for NestJS with pipeline behaviors support

Downloads

498

Readme

nest-mediator

A mediator pattern implementation for NestJS with pipeline behaviors support. This library extends @nestjs/cqrs to add middleware-like pipeline behaviors that run before/after command and query execution.

Installation

npm install nest-mediator

Peer Dependencies

This library requires the following peer dependencies:

npm install @nestjs/common @nestjs/core @nestjs/cqrs reflect-metadata rxjs

Usage

Basic Setup

Import the MediatorModule in your app module:

import { Module } from '@nestjs/common';
import { MediatorModule } from 'nest-mediator';

@Module({
  imports: [MediatorModule.forRoot()],
})
export class AppModule {}

Creating Commands

Commands represent actions that change state. Create a command class and its handler:

// commands/create-user.command.ts
import { Command } from '@nestjs/cqrs';

export class CreateUserCommand extends Command<{ id: string; name: string }> {
  constructor(
    public readonly name: string,
    public readonly email: string,
  ) {
    super();
  }
}
// commands/create-user.handler.ts
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { CreateUserCommand } from './create-user.command';

@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
  async execute(command: CreateUserCommand) {
    // Your business logic here
    const user = {
      id: 'generated-id',
      name: command.name,
      email: command.email,
    };
    // Save to database, etc.
    return { id: user.id, name: user.name };
  }
}

Creating Queries

Queries represent read operations that don't change state:

// queries/get-user.query.ts
import { Query } from '@nestjs/cqrs';

export class GetUserQuery extends Query<{
  id: string;
  name: string;
  email: string;
} | null> {
  constructor(public readonly userId: string) {
    super();
  }
}
// queries/get-user.handler.ts
import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { GetUserQuery } from './get-user.query';

@QueryHandler(GetUserQuery)
export class GetUserHandler implements IQueryHandler<GetUserQuery> {
  async execute(query: GetUserQuery) {
    // Your business logic here
    // Fetch from database, etc.
    return {
      id: query.userId,
      name: 'John Doe',
      email: '[email protected]',
    };
  }
}

Registering Handlers

Don't forget to register your handlers as providers in your module:

import { Module } from '@nestjs/common';
import { MediatorModule } from 'nest-mediator';
import { CreateUserHandler } from './commands/create-user.handler';
import { GetUserHandler } from './queries/get-user.handler';

@Module({
  imports: [MediatorModule.forRoot()],
  providers: [CreateUserHandler, GetUserHandler],
})
export class UserModule {}

Using the Mediator

Inject the Mediator service and use it to execute commands and queries:

import { Injectable } from '@nestjs/common';
import { Mediator } from 'nest-mediator';
import { CreateUserCommand } from './commands/create-user.command';
import { GetUserQuery } from './queries/get-user.query';

@Injectable()
export class UserService {
  constructor(private readonly mediator: Mediator) {}

  async createUser(name: string, email: string) {
    // Execute a command (write operation)
    return this.mediator.execute(new CreateUserCommand(name, email));
  }

  async getUser(userId: string) {
    // Execute a query (read operation)
    return this.mediator.execute(new GetUserQuery(userId));
  }
}

Execute Options

You can pass options to the execute method that will be forwarded to all pipeline behaviors:

@Injectable()
export class UserService {
  constructor(private readonly mediator: Mediator) {}

  async createUser(name: string, email: string) {
    // Pass options to pipeline behaviors
    return this.mediator.execute(new CreateUserCommand(name, email), {
      skipValidation: true,
      userId: 'admin-123',
    });
  }

  async getUser(userId: string) {
    // Pass caching options
    return this.mediator.execute(new GetUserQuery(userId), {
      cache: true,
      ttl: 3600,
    });
  }
}

Behaviors can then use these options to conditionally execute logic:

@Injectable()
export class ConditionalValidationBehavior implements IPipelineBehavior {
  async handle<T>(
    request: Command<T> | Query<T>,
    next: () => Promise<T>,
    options?: { skipValidation?: boolean },
  ): Promise<T> {
    if (options?.skipValidation) {
      // Skip validation and proceed directly
      return next();
    }

    // Perform validation...
    return next();
  }
}

Pipeline Behaviors

Pipeline behaviors allow you to add cross-cutting concerns like logging, validation, or transactions:

import { Injectable } from '@nestjs/common';
import { IPipelineBehavior } from 'nest-mediator';
import { Command, Query } from '@nestjs/cqrs';

@Injectable()
export class LoggingBehavior implements IPipelineBehavior {
  async handle<T>(
    request: Command<T> | Query<T>,
    next: () => Promise<T>,
  ): Promise<T> {
    console.log(`Executing: ${request.constructor.name}`);
    const start = Date.now();

    const result = await next();

    console.log(
      `Completed: ${request.constructor.name} in ${Date.now() - start}ms`,
    );
    return result;
  }
}

Register behaviors in the module:

import { Module } from '@nestjs/common';
import { MediatorModule } from 'nest-mediator';
import { LoggingBehavior } from './behaviors/logging.behavior';
import { ValidationBehavior } from './behaviors/validation.behavior';

@Module({
  imports: [
    MediatorModule.forRoot({
      pipelineBehaviors: [
        LoggingBehavior, // Runs first
        ValidationBehavior, // Runs second
      ],
    }),
  ],
})
export class AppModule {}

Behaviors execute in the order they are registered, wrapping around the actual command/query handler.

Validation Behavior with Joi

Create a validation behavior using separate validator classes with Joi schemas. The decorator is placed on the validator class (similar to @CommandHandler):

1. Create the ValidatorFor Decorator

// decorators/validator-for.decorator.ts
import { Type } from '@nestjs/common';
import { Command, Query } from '@nestjs/cqrs';
import { Schema } from 'joi';

export interface IValidator {
  schema: Schema;
}

type RequestType = Type<Command<unknown> | Query<unknown>>;

const validatorRegistry = new Map<RequestType, Type<IValidator>>();

export function ValidatorFor(request: RequestType): ClassDecorator {
  return (target) => {
    validatorRegistry.set(request, target as Type<IValidator>);
  };
}

export function getValidatorFor(request: object): Type<IValidator> | undefined {
  return validatorRegistry.get(request.constructor as RequestType);
}

2. Create the Validator Class

// commands/create-user.validator.ts
import * as Joi from 'joi';
import {
  ValidatorFor,
  IValidator,
} from '../decorators/validator-for.decorator';
import { CreateUserCommand } from './create-user.command';

@ValidatorFor(CreateUserCommand)
export class CreateUserValidator implements IValidator {
  schema = Joi.object({
    name: Joi.string().min(2).max(50).required(),
    email: Joi.string().email().required(),
  });
}

3. Create the Command (no decorator needed)

// commands/create-user.command.ts
import { Command } from '@nestjs/cqrs';

export class CreateUserCommand extends Command<{ id: string; name: string }> {
  constructor(
    public readonly name: string,
    public readonly email: string,
  ) {
    super();
  }
}

4. Create the Validation Behavior

// behaviors/validation.behavior.ts
import { Injectable, BadRequestException } from '@nestjs/common';
import { IPipelineBehavior } from 'nest-mediator';
import { Command, Query } from '@nestjs/cqrs';
import { getValidatorFor } from '../decorators/validator-for.decorator';

@Injectable()
export class ValidationBehavior implements IPipelineBehavior {
  async handle<T>(
    request: Command<T> | Query<T>,
    next: () => Promise<T>,
  ): Promise<T> {
    const ValidatorClass = getValidatorFor(request);

    if (ValidatorClass) {
      const validator = new ValidatorClass();
      const { error, value } = validator.schema.validate(request, {
        abortEarly: false,
        stripUnknown: true,
      });

      if (error) {
        const messages = error.details.map((detail) => detail.message);
        throw new BadRequestException({
          message: 'Validation failed',
          errors: messages,
        });
      }

      Object.assign(request, value);
    }

    return next();
  }
}

5. Register Validators and Behavior

import { Module } from '@nestjs/common';
import { MediatorModule } from 'nest-mediator';
import { ValidationBehavior } from './behaviors/validation.behavior';
import { LoggingBehavior } from './behaviors/logging.behavior';
import { CreateUserValidator } from './commands/create-user.validator';

@Module({
  imports: [
    MediatorModule.forRoot({
      pipelineBehaviors: [LoggingBehavior, ValidationBehavior],
    }),
  ],
  providers: [CreateUserValidator], // Register validators as providers
})
export class AppModule {}

Now any validator decorated with @ValidatorFor(CommandOrQuery) will be automatically used to validate the corresponding command or query before the handler executes.

API

MediatorModule

  • forRoot(options?: IMediatorOptions) - Configures the mediator with optional pipeline behaviors

IMediatorOptions

interface IMediatorOptions {
  pipelineBehaviors: Type<IPipelineBehavior>[];
}

Mediator

  • execute<T>(commandOrQuery: Command<T> | Query<T>, options?: any): Promise<T> - Executes a command or query through the pipeline with optional options passed to behaviors

IPipelineBehavior

Interface for implementing pipeline behaviors:

interface IPipelineBehavior {
  handle<T>(
    request: Command<T> | Query<T>,
    next: () => Promise<T>,
    options?: any,
  ): Promise<T>;
}

License

MIT