Upgrade to v1.5.0
Everything that changes between 1.0.0 and 1.5.0, the commands that do the work, and what to check when you are done.
None
The underscore methods keep working exactly as before.
10–20 min
One dependency bump, one codemod, one boot check.
2
A package install and nest-cli m events.
Where are you upgrading from?
| Current version | What to do |
|---|---|
| 1.0.0 | Follow the six steps below. |
| 0.0.x beta | Run |
| New project | Nothing to migrate — start from Getting Started. |
The upgrade, step by step
Bump the packages
Every package is released together, so they all move to 1.5.0. Install core, decorators and the adapter you actually use — swap mongoose for prisma or typeorm as needed.
$npm i @nest-extended/core@^1.5.0 @nest-extended/decorators@^1.5.0 @nest-extended/mongoose@^1.5.0
And the CLI, if you have it installed globally:
$npm i -g @nest-extended/cli@^1.5.0
Move to NestJS 12 and TypeScript 6
1.5.0 is built against NestJS 12 and TypeScript 6. Applications still on NestJS 11 will usually run, but the published types target 12 — upgrade both together.
$npm i @nestjs/common@^12 @nestjs/core@^12 @nestjs/platform-express@^12$npm i -D typescript@^6
Add the ORM-specific NestJS integration you use — @nestjs/mongoose@^12 or @nestjs/typeorm@^12.
Pin Prisma to v7
Prisma 8 (“Prisma Next”) is a different product: prisma init --datasource-provider, prisma generate and prisma db push no longer exist, the ORM commands moved under prisma orm, and the client layout changed. Everything the generator emits targets Prisma 7.
$npm i @prisma/client@^7 @prisma/adapter-pg@^7$npm i -D prisma@^7
Swap @prisma/adapter-pg for @prisma/adapter-mariadb (MySQL) or @prisma/adapter-better-sqlite3 (SQLite).
Switch controllers to the event-firing methods
This is the change that turns service events on. Each NestService now has two families: _find / _get / _create / _patch / _remove stay event-free for service-to-service calls, while find / get / create / patch / remove do the same work and dispatch events. The codemod rewrites controllers from the first family to the second.
$cd my-app# see every replacement first — nothing is written$nest-cli m events --dry-run# apply, after confirming$nest-cli m events
The dry run prints each call site:
src/services/company/company.controller.ts
_find -> find (line 24)
_get -> get (line 31)
...
2 file(s), 10 replacement(s).It only touches src/**/*.controller.ts by default, which is the point: an AuthService looking a user up with usersService._find(...) should not fire the users service’s onFind hook.
@Controller('company')
export class CompanyController {
constructor(private readonly service: CompanyService) {}
@Get()
find(@Query() query: Record<string, any>) {
return this.service._find(query);
}
@Post()
create(@ModifyBody(setCreatedBy()) body: CreateCompanyDto) {
return this.service._create(body);
}
}@Controller('company')
export class CompanyController {
constructor(private readonly service: CompanyService) {}
@Get()
find(@Query() query: Record<string, any>) {
return this.service.find(query); // fires beforeFind / onFind
}
@Post()
create(@ModifyBody(setCreatedBy()) body: CreateCompanyDto) {
return this.service.create(body); // fires beforeCreate / onCreate
}
}Add an events class where you want one
Step 4 makes hooks possible; nothing fires until a service has an events class. Generate one for a new resource with nest-cli g service (it is the default — --skip-events opts out), or write it by hand for an existing one.
import { Injectable } from '@nestjs/common';
import { EventContext, NestServiceEvents, ServiceEvents } from '@nest-extended/core';
import { CompanyService } from './company.service';
import { CompanyDocument } from '../../schemas/company.schema';
@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 — runs after the response has been sent.
async onCreate(company: CompanyDocument, ctx: EventContext<CompanyService>) {
await this.mailer.sendWelcome(company, ctx.user);
}
}Register it next to the service in the feature module’s providers — the generator does this for you:
@Module({
imports: [MongooseModule.forFeature([{ name: Company.name, schema: CompanySchema }])],
controllers: [CompanyController],
providers: [CompanyService, CompanyEvents], // <- both classes
})
export class CompanyModule {}To broadcast events to other modules as well, install @nestjs/event-emitter, register EventEmitterModule.forRoot(), and construct the service with { broadcast: 'company' }. nest-cli g service --broadcast wires all three up for you. The full hook list, the context object and the limitations are in Service Events.
Boot and verify
Start the app and look for one wiring line per events class. If a line is missing, that class is not registered or NestExtendedModule.forRoot() is absent.
[Nest] LOG [NestExtendedEvents] CompanyEvents -> CompanyService
[Nest] LOG [NestExtendedEvents] UserEvents -> UsersServiceThen run your test suite. Tests that assert on an on* hook need await service.whenSettled() first, because those hooks are detached.
What changed, and what it means for you
| Change | Impact | Action |
|---|---|---|
Generated controllers call find / create / … instead of _find / _create / … | Identical arguments and responses — the plain methods only add event dispatch. | Run nest-cli m events, or leave the underscore calls and no hook fires. |
NestService constructor options gained events and broadcast | events defaults to true; with no events class attached, nothing changes. | Nothing. Pass { events: false } to opt a service out. |
@nest-extended/core now depends on @nest-extended/decorators | The interceptor reads the decorators’ metadata keys. | Keep both packages on the same version. |
| NestJS 12 and TypeScript 6 | The packages are built against them; NestJS 11 apps should upgrade too. | Bump @nestjs/* to 12 and typescript to 6. |
| Prisma pinned to v7 | Prisma 8 removes prisma generate / db push and moves commands under prisma orm; the generated client layout targets 7. | Pin @prisma/client, the driver adapter and dev prisma to ^7. |
| Scaffolded apps are emitted as CommonJS | Only affects apps generated from 1.5.0 onward. | Nothing for existing apps. |
Migration command reference
| Command | Alias | Purpose |
|---|---|---|
| nest-cli migration events | m events | Switch controllers from _find / _get / … to the event-firing methods. |
| nest-cli m events --dry-run | — | Print every replacement with its file and line, write nothing. |
| nest-cli m events -y | --yes | Apply without the confirmation prompt — for scripted upgrades. |
| nest-cli m events --path 'src/**/*.ts' | — | Widen the scope beyond src/**/*.controller.ts. Rarely what you want: service-to-service calls should stay event-free. |
| nest-cli migration run | m run | Rewrite ModifyBody / User / Public / setCreatedBy imports from @nest-extended/core to @nest-extended/decorators. Needed only when coming from the 0.0.x betas. |
| nest-cli version | v | Confirm which CLI version is on your PATH. |
From the 0.0.x betas
1.0.0 moved ModifyBody, User, Public and setCreatedBy out of @nest-extended/core into @nest-extended/decorators. One codemod rewrites those imports across src/**/*.ts:
$nest-cli m run
import {
NestController,
ModifyBody,
setCreatedBy,
} from '@nest-extended/core';import { NestController } from '@nest-extended/core';
import {
ModifyBody,
setCreatedBy,
} from '@nest-extended/decorators';Then continue with the six steps above to reach v1.5.0.
Troubleshooting
NestExtendedModule.forRoot() is in app.module.ts — it is what discovers @ServiceEvents() classes. Two:both the service and its events class are in the feature module’s providers. Three: the caller uses create(), not _create(). The boot log confirms each wiring:[NestExtendedEvents] CompanyEvents -> CompanyService
on* hooks are detached — they run after the response, on setImmediate. Await service.whenSettled() before asserting:await service.create({ name: 'Acme' });
await service.whenSettled();
expect(mailer.send).toHaveBeenCalled();ctx.user instead of injecting REQUEST.remove() calls _patch internally to set the soft-delete flags, and _patch is on the event-free path — so a soft delete raises exactly one event.prisma CLI ships an 8.0.0 release candidate on the latest npm tag, so an unpinned install lands a pre-release next to a 7.x @prisma/client. Pin all three to ^7 — see step 3.{ events: false } to a service’s super(...), or NestExtendedModule.forRoot({ events: false }) to disable service events and the controller hooks app-wide.Upgraded?
Service events are the reason to be on 1.5.0 — the guide covers every hook, the context object, custom events, broadcasting and the limitations.