Programming

Adopting TypeScript 5's standardized decorators

PRG

TypeScript's decorator support had been stuck behind an experimental flag, implementing an older, pre-standard proposal, for years, which meant every project using decorators, mostly our NestJS services and a handful of class-based state management patterns elsewhere, carried a small but real risk of eventually needing to migrate once the language caught up to the actual ECMAScript standard. This year's alignment with the standardized ECMAScript decorators proposal finally gave us a stable target to migrate toward instead of an indefinitely deferred question.

A handful of dependency injection patterns built on the old experimental decorators needed real rework, since the new standard decorators have meaningfully different semantics, not just a syntax change. The most disruptive difference involved metadata reflection: the experimental decorators relied heavily on the reflect-metadata package to preserve type information at runtime for dependency injection to work, and the new standard decorators don't support that same mechanism in the same way, since the standards process deliberately scoped metadata reflection out of the core proposal.

For our NestJS services specifically, this meant staying on the experimental decorator flag a while longer than other parts of the codebase, since NestJS's own dependency injection system is built around the reflect-metadata pattern and hadn't yet fully adapted to the standardized decorators at the point we did this migration. We migrated our non-NestJS decorator usage, mostly some internal class-based validation helpers, over to standard decorators first, and treated the NestJS migration as a separate, later project once the framework's own support matured further.

The parts of the migration that did land were worth doing. Standard decorators integrate more predictably with other TypeScript tooling and don't require the experimentalDecorators compiler flag that always felt like a slightly uncomfortable admission that we were relying on non-standard behavior. Worth doing, but not a drop-in swap, and we'd recommend budgeting real time for any codebase with decorator usage beyond the most basic class annotations, particularly if dependency injection or metadata reflection is involved anywhere in the pattern.

The validation helper migration itself surfaced a smaller but genuinely useful benefit: standard decorators compose more predictably when stacked on the same class member, since their evaluation order is specified clearly by the standard in a way the older experimental behavior never fully committed to across every tooling implementation. A couple of validation rules that had silently applied in an order we hadn't intended under the old decorators started behaving as expected once we moved to the standard implementation, which we only noticed because a previously-passing edge case test started failing during the migration, a case where the migration itself caught a latent bug rather than introducing one.

We're tracking NestJS's own roadmap toward standard decorator support and plan to revisit the remaining migration once it lands, rather than attempting to force the framework onto standard decorators ahead of official support, which would likely mean maintaining compatibility patches against an internal framework mechanism we don't control.

← Back to the journal

Have a project in mind?
Let’s talk.

Tell us where you are and where you want to go. We'll map the fastest route between the two.

Currently accepting new clients