Modern examples of diverse examples of TypeScript decorators
Before definitions or history, let’s start with actual code. When teams talk about examples of diverse examples of TypeScript decorators, they usually mean patterns like these:
- Logging and metrics decorators around methods
- Validation decorators on DTOs and form models
- Caching decorators for expensive calls
- Authorization decorators for routes and services
- Dependency injection and configuration decorators
- Rate limiting and retry decorators around API clients
You’ll see all of those below, written in modern TypeScript, with a bias toward patterns that survive code review in a serious codebase.
Note: TypeScript’s legacy experimental decorators are being replaced by the standardized decorators proposal backed by TC39 and documented by Microsoft at typescriptlang.org. New projects should track that syntax, but most production code in 2024 still uses the experimental style you’ll see here.
Logging and metrics: the classic example of a method decorator
When people ask for examples of diverse examples of TypeScript decorators, logging is usually the first example of a method decorator that makes sense to everyone on the team.
function LogExecutionTime(label?: string) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = async function (...args: any[]) {
const start = performance.now();
try {
const result = await original.apply(this, args);
return result;
} finally {
const end = performance.now();
const name = label || `\({target.constructor.name}.}\(propertyKey}`;
console.info(`[Timing] \({name}: }\((end - start).toFixed(2)} ms`);
}
};
return descriptor;
};
}
class ReportService {
@LogExecutionTime('monthly-report')
async generateMonthlyReport(userId: string) {
// expensive work here
}
}
This is one of the best examples of decorators earning their keep: every method wrapped by @LogExecutionTime gets consistent metrics without copy‑pasting timing code. In a monitoring‑heavy environment, you might forward those measurements to Prometheus, Datadog, or OpenTelemetry.
For teams concerned with performance and reliability trends, the broader topic of measurement and monitoring is covered well in resources like the NIST performance engineering guidance at nist.gov.
Validation decorators on DTOs and form models
The next example of practical decorator use is validation on data transfer objects (DTOs). Instead of scattering if (!value) checks everywhere, decorators attach rules directly to class properties.
interface ValidationRule {
propertyKey: string;
validator: (value: any) => string | null;
}
const validationMetadata = new WeakMap<object, ValidationRule[]>();
function Required() {
return function (target: any, propertyKey: string) {
const rules = validationMetadata.get(target) || [];
rules.push({
propertyKey,
validator: (value) =>
value === undefined || value === null || value === ''
? `${propertyKey} is required`
: null,
});
validationMetadata.set(target, rules);
};
}
function MinLength(length: number) {
return function (target: any, propertyKey: string) {
const rules = validationMetadata.get(target) || [];
rules.push({
propertyKey,
validator: (value) =>
typeof value === 'string' && value.length < length
? `\({propertyKey} must be at least }\(length} characters`
: null,
});
validationMetadata.set(target, rules);
};
}
function validate(obj: any): string[] {
const rules = validationMetadata.get(Object.getPrototypeOf(obj)) || [];
return rules
.map((rule) => rule.validator((obj as any)[rule.propertyKey]))
.filter((msg): msg is string => msg !== null);
}
class RegisterUserDto {
@Required()
@MinLength(3)
username!: string;
@Required()
@MinLength(8)
password!: string;
}
const dto = new RegisterUserDto();
dto.username = 'ab';
console.log(validate(dto));
// [
// 'username must be at least 3 characters',
// 'password is required'
// ]
Frameworks like NestJS and Angular offer similar decorators out of the box. This pattern is one of the best examples of decorators improving readability: the constraints live right next to the fields they protect.
If you’re working with user‑facing forms in healthcare or other regulated fields, validation isn’t just nice to have; it’s part of protecting users from bad data. Organizations like the U.S. Office of the National Coordinator for Health Information Technology (healthit.gov) publish guidance on data quality and validation in clinical software, which often maps directly onto patterns like this.
Caching expensive calls with method decorators
Another set of examples of diverse examples of TypeScript decorators comes from caching. When a method is expensive but deterministic, a decorator can handle memoization.
function Memoize(ttlMs: number = 0) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
const cache = new Map<string, { value: any; expiresAt: number }>();
descriptor.value = function (...args: any[]) {
const key = JSON.stringify(args);
const now = Date.now();
const cached = cache.get(key);
if (cached && (ttlMs === 0 || cached.expiresAt > now)) {
return cached.value;
}
const result = original.apply(this, args);
const expiresAt = ttlMs === 0 ? Number.POSITIVE_INFINITY : now + ttlMs;
cache.set(key, { value: result, expiresAt });
return result;
};
return descriptor;
};
}
class GeoService {
@Memoize(5 * 60 * 1000) // 5 minutes
getCityForCoordinates(lat: number, lng: number) {
// Imagine an expensive lookup or remote API call here
}
}
This is a clean example of a decorator that hides a cross‑cutting concern (caching) without cluttering business logic. In 2024, this pattern shows up everywhere from frontend state management to server‑side API clients.
Access control decorators for routes and services
Authorization is where decorators start to feel like real infrastructure. Many of the best examples of TypeScript decorators in production codebases are access control decorators on routes or service methods.
interface UserContext {
id: string;
roles: string[];
}
function getCurrentUser(): UserContext | null {
// imagine pulling from a request context or auth token
return { id: '123', roles: ['user'] };
}
function RequireRole(role: string) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
const user = getCurrentUser();
if (!user || !user.roles.includes(role)) {
throw new Error(`Forbidden: ${role} role required`);
}
return original.apply(this, args);
};
return descriptor;
};
}
class AdminController {
@RequireRole('admin')
deleteUser(userId: string) {
// delete user logic
}
}
This pattern keeps the authorization story consistent and visible. When you scan AdminController, you immediately see which methods are locked down. In security‑sensitive domains like healthcare or finance, that clarity matters.
If you’re designing access control around protected health information or other regulated data, the security principles described by the U.S. Department of Health & Human Services at hhs.gov are worth reviewing; decorators are one of many tools to enforce those principles in code.
Dependency injection and configuration decorators
Some of the most widely used examples of diverse examples of TypeScript decorators live in dependency injection (DI) systems. The exact syntax varies by framework, but the pattern is consistent: decorators mark classes as injectable and describe how to resolve their dependencies.
Here’s a minimal DI flavor inspired by NestJS‑style decorators:
const container = new Map<any, any>();
function Injectable() {
return function (target: any) {
container.set(target, new target());
};
}
function Inject(token: any) {
return function (target: any, propertyKey: string) {
Object.defineProperty(target, propertyKey, {
get() {
return container.get(token);
},
enumerable: true,
configurable: true,
});
};
}
@Injectable()
class EmailService {
send(to: string, subject: string, body: string) {
// send email
}
}
class UserNotifier {
@Inject(EmailService)
private emailService!: EmailService;
notifyWelcome(userEmail: string) {
this.emailService.send(userEmail, 'Welcome', 'Thanks for signing up');
}
}
Real‑world frameworks add scopes, async providers, and lifecycle hooks, but the decorator idea is the same: annotate what should be injectable, and let the container handle wiring.
Rate limiting and retry decorators for API clients
Modern apps talk to a lot of external APIs, and those APIs are not always friendly. A very practical example of a decorator is a wrapper that adds retry and rate‑limit behavior.
function Retry(maxAttempts: number = 3, delayMs: number = 200) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = async function (...args: any[]) {
let attempt = 0;
let lastError: unknown;
while (attempt < maxAttempts) {
try {
return await original.apply(this, args);
} catch (err) {
lastError = err;
attempt++;
if (attempt >= maxAttempts) break;
await new Promise((res) => setTimeout(res, delayMs));
}
}
throw lastError;
};
return descriptor;
};
}
class WeatherApiClient {
@Retry(5, 500)
async getCurrentConditions(zipCode: string) {
// fetch from weather API
}
}
This is one of the best examples of decorators handling resilience. You can layer this with a rate‑limit decorator that tracks calls per minute and throws or queues when limits are reached.
Property decorators for configuration and feature flags
Not all decorators wrap behavior at call time. Some examples include property decorators that configure how values are loaded, persisted, or toggled.
Here’s a simple feature flag decorator that pulls from a global config object:
const featureFlags: Record<string, boolean> = {
'checkout.newFlow': true,
'profile.betaLayout': false,
};
function FeatureFlag(flagKey: string) {
return function (target: any, propertyKey: string) {
Object.defineProperty(target, propertyKey, {
get() {
return featureFlags[flagKey] ?? false;
},
enumerable: true,
configurable: true,
});
};
}
class UiExperiments {
@FeatureFlag('checkout.newFlow')
isNewCheckoutEnabled!: boolean;
}
const exp = new UiExperiments();
console.log(exp.isNewCheckoutEnabled); // true
This style of decorator keeps configuration concerns centralized while still making the consuming code easy to read.
Class decorators for metadata and auto‑registration
The last category in this set of examples of diverse examples of TypeScript decorators is class decorators that attach metadata or auto‑register classes with some runtime system.
A common pattern is building a lightweight HTTP routing layer:
interface RouteDefinition {
method: 'GET' | 'POST';
path: string;
handlerName: string;
}
const routes: RouteDefinition[] = [];
function Controller(basePath: string) {
return function (constructor: Function) {
Reflect.defineMetadata('basePath', basePath, constructor);
};
}
function Get(path: string) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const constructor = target.constructor;
const basePath = Reflect.getMetadata('basePath', constructor) || '';
routes.push({
method: 'GET',
path: basePath + path,
handlerName: propertyKey,
});
};
}
@Controller('/users')
class UserController {
@Get('/')
listUsers() {
// return list
}
}
console.log(routes);
// [ { method: 'GET', path: '/users/', handlerName: 'listUsers' } ]
This is functionally similar to how frameworks like NestJS or Angular build routing and module metadata under the hood. It’s a clear example of decorators turning “just classes” into framework‑aware components.
Note: If you use metadata‑driven decorators like this, you’ll need the
reflect-metadatashim andemitDecoratorMetadataenabled intsconfig.json. Details are in the official TypeScript docs at typescriptlang.org.
How these patterns fit into the 2024–2025 TypeScript ecosystem
In 2024 and heading into 2025, decorators are in a strange but interesting place:
- The TC39 decorators proposal has advanced, and TypeScript is aligning with that standard.
- Frameworks like Angular and NestJS still rely heavily on decorators and are adapting their syntax as the standard settles.
- Tooling around decorators—lint rules, reflection helpers, metadata libraries—continues to mature.
When you look for the best examples of TypeScript decorators to copy, focus on patterns that:
- Encapsulate cross‑cutting concerns (logging, metrics, validation, security)
- Stay thin and predictable (no hidden magic that surprises new team members)
- Are easy to test without needing the decorator itself
The examples include in this article are deliberately biased toward those criteria. They’re not just “cool”; they’re the kind of decorators that survive a tough code review.
FAQ: short answers about real examples of TypeScript decorators
Q: What are some real‑world examples of TypeScript decorators used in production apps?
Teams commonly use decorators for request validation on DTOs, logging and timing around service methods, authorization checks on controllers, dependency injection wiring, caching and memoization of expensive calls, and configuration or feature‑flag access on properties. The examples of diverse examples of TypeScript decorators shown above mirror those production patterns.
Q: Can I use these examples with the new standard decorators proposal?
Yes, but some syntax changes. The high‑level ideas—wrapping methods, annotating properties, tagging classes with metadata—still apply. You’ll want to track the latest guidance from the TypeScript team at typescriptlang.org to adjust the implementation details.
Q: Are decorators safe to use in security‑sensitive code?
They’re as safe as the code you put inside them. For access control, input validation, and logging around sensitive operations, decorators can actually improve safety by centralizing logic. Just avoid hiding complex or risky behavior behind cute syntax. For broader security practices, the OWASP Foundation at owasp.org is a good reference.
Q: Do decorators hurt performance?
There is overhead, because they add another function call layer and sometimes extra bookkeeping. In most web and API services, that cost is small compared with network and I/O latency. If you’re writing ultra‑low‑latency code, measure the impact with and without decorators and decide based on data.
Q: How do I decide whether to introduce a decorator versus a plain helper function?
Use a decorator when you want a concern to be visually and structurally attached to a class, method, or property—logging around a method, validation on a field, or a role requirement on a route. Use a plain helper when the logic is tightly coupled to a single call site or would be confusing if hidden behind annotation syntax.
If you’re evaluating examples of diverse examples of TypeScript decorators for your own stack, start with one or two concerns—typically logging and validation—and see how they affect readability and testability. Decorators are a tool, not a religion; the best examples are the ones that make your codebase easier to reason about six months from now.
Related Topics
Modern examples of diverse examples of TypeScript decorators
Modern examples of diverse examples of TypeScript generics
Modern examples of TypeScript integration with Node.js examples
Practical examples of TypeScript configuration examples for modern projects
Modern examples of TypeScript integration with React examples
Explore More TypeScript Code Snippets
Discover more examples and insights in this category.
View All TypeScript Code Snippets