For AI agents: the complete documentation index is available at /bull-board/llms.txt, the full documentation bundle is available at /bull-board/llms-full.txt, and this page is available as Markdown at /bull-board/server-adapters/nestjs.md.

NestJS

NestJS. bull-board ships a NestJS module plus a plain adapter you can wire manually.

Install

npm install @bull-board/api @bull-board/nestjs

Also install the adapter for the HTTP platform your Nest app uses (Express is the default):

npm install @bull-board/express
# or, for Fastify:
npm install @bull-board/fastify

Supported NestJS versions

@bull-board/nestjs supports NestJS 9, 10, 11 and 12. The suite runs against both 11 and 12 on every CI build.

NestJS 12 ships as ESM only, so a Nest 12 application has to be ESM itself. @bull-board/nestjs is published as CommonJS and its named exports are importable from an ESM app, so nothing about the setup below changes on Nest 12.

Register BullBoardModule.forRoot() in your root module, then BullBoardModule.forFeature() per queue from the feature module.

// app.module.ts
import { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bullmq';
import { BullBoardModule } from '@bull-board/nestjs';
import { ExpressAdapter } from '@bull-board/express';
import { FeatureModule } from './feature/feature.module';

@Module({
  imports: [
    BullModule.forRoot({
      connection: { host: 'localhost', port: 6379 },
    }),
    BullBoardModule.forRoot({
      route: '/queues',
      adapter: ExpressAdapter, // or FastifyAdapter from '@bull-board/fastify'
    }),
    FeatureModule,
  ],
})
export class AppModule {}
// feature/feature.module.ts
import { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bullmq';
import { BullBoardModule } from '@bull-board/nestjs';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';

@Module({
  imports: [
    BullModule.registerQueue({ name: 'feature_queue' }),
    BullBoardModule.forFeature({
      name: 'feature_queue',
      adapter: BullMQAdapter, // or BullAdapter for Bull v3
    }),
  ],
})
export class FeatureModule {}

forRoot() options:

  • route: base path where the dashboard is mounted.
  • adapter: server adapter class (ExpressAdapter or FastifyAdapter).
  • boardOptions: forwarded to createBullBoard (e.g. uiConfig, uiBasePath).
  • middleware: optional Express/Fastify middleware (basic auth, etc.).

forFeature() options (pass either name or queue):

  • name: queue name registered with BullModule.registerQueue. The module resolves the instance from Nest's DI container.
  • queue: a queue instance to register directly, instead of resolving it by name. See Queues with the same name below.
  • adapter: BullMQAdapter or BullAdapter.
  • options: queue adapter options like readOnlyMode or description.

To register several queues at once, pass multiple option objects:

BullBoardModule.forFeature(
  { name: 'emails', adapter: BullMQAdapter },
  { name: 'billing', adapter: BullMQAdapter },
);

Queues with the same name

@nestjs/bullmq builds a queue's DI token from its name alone, and the prefix is not part of it. So if you run the same queue name under two prefixes (a common multi-tenant setup), both share one DI token and a name lookup can only ever return one of them. Registering both by name makes one queue shadow the other on the board.

Pass the instances directly via queue instead. Hold the queues somewhere you control (a provider, a service, wherever you created them) and hand them to forFeature:

@Module({
  imports: [
    BullBoardModule.forFeature(
      { queue: emailsTenantA, adapter: BullMQAdapter, options: { prefix: 'tenant-a:' } },
      { queue: emailsTenantB, adapter: BullMQAdapter, options: { prefix: 'tenant-b:' } },
    ),
  ],
})
export class FeatureModule {}

The board keys entries by prefix + name, so the two show up as tenant-a:emails and tenant-b:emails. Set each adapter's prefix to match the queue's own prefix so the labels line up.

There's also BullBoardModule.forRootAsync() which accepts useFactory, imports, inject for dynamic config.

You can inject the board instance anywhere:

import { Controller } from '@nestjs/common';
import { BullBoardInstance, InjectBullBoard } from '@bull-board/nestjs';

@Controller('ops')
export class OpsController {
  constructor(@InjectBullBoard() private readonly board: BullBoardInstance) {}
}

Plain adapter setup

If you'd rather wire the server adapter yourself (custom middleware, existing Nest conventions), do it in a module's configure():

import {
  DynamicModule,
  MiddlewareConsumer,
  Module,
  NestModule,
} from '@nestjs/common';
import { BullModule } from '@nestjs/bullmq';
import { createBullBoard } from '@bull-board/api';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
import { ExpressAdapter } from '@bull-board/express';
import { Queue } from 'bullmq';

@Module({})
export class QueuesModule implements NestModule {
  static register(): DynamicModule {
    return {
      module: QueuesModule,
      imports: [
        BullModule.forRoot({
          connection: { host: 'localhost', port: 6379 },
        }),
        BullModule.registerQueue({ name: 'test' }),
      ],
    };
  }

  constructor(private readonly testQueue: Queue) {}

  configure(consumer: MiddlewareConsumer) {
    const serverAdapter = new ExpressAdapter();
    serverAdapter.setBasePath('/queues');

    createBullBoard({
      queues: [new BullMQAdapter(this.testQueue)],
      serverAdapter,
    });

    consumer.apply(serverAdapter.getRouter()).forRoutes('/queues');
  }
}

Full runnable examples

Next steps