@nest-extended/core

The foundation every other package builds on — the generic CRUD controller, the service events runtime, the dynamic configuration module, the interceptors and the shared types.

$npm install @nest-extended/core nestjs-cls

Key exports

ExportTypePurpose
NestController<T>classBase controller — find / get / create / patch / delete
NestExtendedModulemoduleDynamic module configured via .forRoot(config)
NullResponseInterceptorinterceptorThrows 404 when a GET resolves to null
UseHooksInterceptorinterceptorRuns @UseBefore / @UseAfter — registered by forRoot()
NestServiceBase<D, E>classBase for every adapter's NestService: find/get/create/patch/remove, emit(), whenSettled()
NestServiceEvents<S, D>classBase for a {name}.events.ts hooks class
NestServiceHooks<S, D>interfaceThe optional hook signatures
@ServiceEvents(Service)decoratorAttaches an events class to its service
ServiceEventsRegistryproviderWires @ServiceEvents() classes at boot
EventContext<S>interfaceContext passed to every hook
getEventBus / setEventBusfunctionOptional @nestjs/event-emitter bridge
getCurrentUser<T>()functionRead the authenticated user from CLS
NEST_EXTENDED_CONFIGtokenDI token for the resolved config
NestExtendedConfigtypeRoot config (soft delete, query parser, filters, events)
ServiceOptions<T>interfaceService contract used by NestController
PaginatedResponse<D>type{ total, $limit, $skip, data }

Generic controller

NestController wires the CRUD routes to any service implementing ServiceOptions:

cats.controller.ts
import { Controller } from '@nestjs/common';
import { NestController } from '@nest-extended/core';
import { CatsService } from './cats.service';
 
@Controller('cats')
export class CatsController extends NestController<Cat> {
  constructor(service: CatsService) {
    super(service);
  }
}

Service events

New in 1.5.0. Every NestService exposes two versions of each operation. _find / _get / _create / _patch / _remove do the work; find / get / create / patch / remove do exactly the same and additionally dispatch lifecycle events. Call the underscore ones when one service calls another, the plain ones from controllers.

company.service.ts
import type { CompanyEvents } from './company.events';   // type-only: avoids a runtime cycle
 
@Injectable()
export class CompanyService extends NestService<Company, CompanyDocument, CompanyEvents> {
  constructor(@InjectModel(Company.name) model: Model<CompanyDocument>) {
    super(model, { events: true });   // the default; { events: false } opts out
  }
 
  async approve(id: string) {
    const doc = await this._patch(id, { status: 'approved' });
    this.emit('approve', doc);        // custom event, type-checked against CompanyEvents
    return doc;
  }
}
company.events.ts
import { EventContext, NestServiceEvents, ServiceEvents } from '@nest-extended/core';
import { CompanyService } from './company.service';
 
@Injectable()
@ServiceEvents(CompanyService)
export class CompanyEvents extends NestServiceEvents<CompanyService, CompanyDocument> {
  // Inline and awaited — what you return replaces the input.
  beforeCreate(data: any, ctx: EventContext<CompanyService>) {
    return { ...data, slug: slugify(data.name) };
  }
 
  // Detached — the response has already been sent, so the return value is ignored
  // and a throw is logged rather than surfaced to the client.
  async onCreate(company: CompanyDocument, ctx: EventContext<CompanyService>) {
    await this.profileModel.create({ company: company._id, createdBy: ctx.user?._id });
  }
}

Both classes go in the module's providers, and the app must import NestExtendedModule.forRoot() — that is what discovers and wires them. Look for the boot log line confirming each wiring:

[NestExtendedEvents] CompanyEvents -> CompanyService

See Service Events for every hook, the context object, broadcasting and the limitations.

Module configuration

app.module.ts
NestExtendedModule.forRoot({
  queryParser: { depth: 20, arrayLimit: 100 },
  filters: [MongooseValidationExceptionFilter],
  events: true,   // default — set false to disable service events and controller hooks app-wide
});

Next steps