O Guia Completo de Decorators no TypeScript
TypeScript Decorators são uma técnica para adicionar metadados e comportamento a classes e membros (métodos, propriedades, parâmetros) de forma declarativa. Eles aparecem com frequência em frameworks e bibliotecas (por exemplo, para injeção de dependências, validação, roteamento e serialização), mas também podem ser usados em projetos “puros” para padronizar práticas internas.
Este guia explica o que são, como funcionam, como habilitar, quais tipos existem, exemplos práticos e cuidados, incluindo um olhar de Cyber Segurança sobre riscos comuns quando decorators são usados para automatizar lógica sensível.
O que são TypeScript Decorators (e por que existem)
Em termos práticos, um decorator é uma função aplicada com a sintaxe @algumaCoisa que recebe informações sobre o alvo (por exemplo, a classe ou o método) e pode:
- Anexar metadados (ex.: “este método exige autenticação”).
- Modificar comportamento (ex.: registrar logs, medir tempo, validar argumentos).
- Alterar definições (ex.: substituir um método por outro, ou ajustar o
property descriptor).
Esse recurso é útil porque centraliza regras transversais (logging, auditoria, validação, caching) sem poluir a lógica de negócio com repetição.
Status do recurso: o que você precisa saber sobre compatibilidade
Decorators no TypeScript são historicamente baseados em uma proposta de JavaScript que evoluiu ao longo do tempo. Por isso, existem diferenças entre:
- Decorators “legados” (muito usados por anos em TS + ecossistemas).
- Decorators alinhados à proposta mais recente (com mudanças de assinatura e comportamento).
Na prática, projetos podem estar em transição. Ao ler documentação de frameworks, verifique qual modelo está sendo usado.
Como habilitar decorators no TypeScript (passo a passo)
-
No
tsconfig.json, habilite:experimentalDecorators: true
-
Se o seu projeto usa bibliotecas que dependem de metadados em tempo de execução (comum em DI/reflection), você pode ver também:
emitDecoratorMetadata: true(uso deve ser bem avaliado; ver seção de segurança)
Exemplo de tsconfig.json (apenas as partes relevantes):
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"experimentalDecorators": true
}
}
Se você estiver usando tooling específico (Babel, SWC, bundlers), confirme se o pipeline está configurado para transformar decorators. Em alguns casos, o TypeScript compila o TS, mas o bundler também precisa entender a sintaxe.
Como um decorator funciona “por baixo do capô”
Um decorator é uma função chamada no momento em que a classe é definida, não quando ela é instanciada (na maioria dos casos). O decorator recebe referências ao alvo e, dependendo do tipo, pode receber também:
- o nome do membro
- o
property descriptor - dados do contexto (em modelos mais recentes)
Isso significa que decorators podem:
- interceptar a criação/definição do método
- substituir o método por um wrapper
- registrar metadados em um mapa interno
- aplicar validações automáticas
Tipos de TypeScript Decorators (com exemplos práticos)
A seguir, os tipos mais comuns e o que você normalmente faz com cada um.
1) Class Decorator (Decorator de classe)
Usado para observar ou modificar uma classe como um todo. Pode ser útil para registrar classes em um container, aplicar padrões ou anexar metadados.
Exemplo: marcar uma classe como “serviço” e registrar em um registry simples.
const registry = new Map<string, unknown>();
function Service(name: string) {
return function (constructor: Function) {
registry.set(name, constructor);
};
}
@Service("UserService")
class UserService {
findUser(id: string) {
return { id };
}
}
O que observar: um class decorator pode alterar o construtor ou substituir a classe (dependendo do modelo), então mantenha isso previsível e documentado.
2) Method Decorator (Decorator de método)
Um dos mais usados: permite interceptar chamadas e aplicar comportamento transversal.
Exemplo: medir tempo de execução.
function MeasureTime() {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
const start = performance.now();
try {
return original.apply(this, args);
} finally {
const end = performance.now();
console.log(`[${propertyKey}] ${(end - start).toFixed(2)}ms`);
}
};
return descriptor;
};
}
class ReportService {
@MeasureTime()
generate() {
// ... trabalho pesado
}
}
Boas práticas:
- Preserve
thiscomapply. - Evite logs com dados sensíveis.
- Garanta que exceções não sejam “engolidas”.
3) Property Decorator (Decorator de propriedade)
Aplicado em propriedades. Frequentemente usado para metadados (ex.: serialização, validação). Um detalhe importante: decorators de propriedade não recebem descriptor (no modelo legado), então a alteração direta do getter/setter exige outras técnicas.
Exemplo: criar um metadado de “campo sensível” para mascaramento.
const sensitiveFields = new WeakMap<object, Set<string>>();
function Sensitive() {
return function (target: any, propertyKey: string) {
const set = sensitiveFields.get(target) ?? new Set<string>();
set.add(propertyKey);
sensitiveFields.set(target, set);
};
}
class Customer {
@Sensitive()
document!: string;
name!: string;
}
Isso não mascara automaticamente: apenas marca. Uma função de serialização poderia consultar esse metadado e ocultar campos.
4) Parameter Decorator (Decorator de parâmetro)
Usado para marcar parâmetros (ex.: extrair do request, validar, injetar dependências). Em geral, parameter decorators são sobre metadados, não sobre interceptação de chamada diretamente.
Exemplo: registrar quais parâmetros exigem validação.
const requiredParams = new WeakMap<object, Map<string, number[]>>();
function Required() {
return function (target: any, propertyKey: string, parameterIndex: number) {
const methods = requiredParams.get(target) ?? new Map<string, number[]>();
const indexes = methods.get(propertyKey) ?? [];
indexes.push(parameterIndex);
methods.set(propertyKey, indexes);
requiredParams.set(target, methods);
};
}
class AuthService {
login(@Required() username: string, @Required() password: string) {
return { ok: true };
}
}
Para aplicar a validação, normalmente você combina isso com um method decorator que lê os metadados e valida args.
5) Accessor Decorator (Decorator de getter/setter)
Aplicado a get/set. Útil para controlar leitura/escrita, auditoria, ou regras de acesso.
Exemplo: auditar acesso a um getter.
function AuditRead() {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalGet = descriptor.get;
if (!originalGet) return;
descriptor.get = function () {
console.log(`Leitura de: ${propertyKey}`);
return originalGet.call(this);
};
return descriptor;
};
}
class Vault {
private _secret = "token";
@AuditRead()
get secret() {
return this._secret;
}
}
Um padrão comum: combinar metadados + wrapper
Em sistemas reais, decorators costumam ser usados assim:
- Decorators de parâmetro/propriedade registram metadados (o “o quê”).
- Decorator de método implementa a execução (o “como”), lendo metadados e aplicando regras.
Isso mantém o código modular: marcações são declarativas; execução é centralizada.
Quando usar (e quando evitar) TypeScript Decorators
Use quando:
- Você precisa de comportamento transversal (auditoria, métricas, cache).
- Existe repetição de padrões entre várias classes/métodos.
- O projeto se beneficia de declarações (ex.:
@RequiresRole("admin")).
Evite quando:
- O comportamento fica “mágico” demais e difícil de rastrear.
- Decorators começam a alterar fluxos críticos sem testes e sem logs adequados.
- O time não tem padrões claros para onde ficam metadados e como são aplicados.
Implicações de segurança (Cyber Segurança): riscos e mitigação
Decorators podem criar uma superfície de risco específica porque “injetam” comportamento de forma indireta. Pontos de atenção:
1) Logging e vazamento de dados sensíveis
Decorators de log e auditoria podem registrar:
- tokens
- senhas
- identificadores pessoais
- payloads brutos
Mitigação: padronize mascaramento, classifique campos sensíveis (como no exemplo @Sensitive) e proíba logs de payload completo em produção.
2) Autorização “implícita”
Um decorator como @RequiresRole() pode virar a única barreira de autorização. Se alguém remover a anotação, o método pode ficar exposto.
Mitigação:
- testes automatizados de segurança (incluindo testes de autorização)
- políticas de revisão (code review) com checklist
- fallback por padrão seguro: negar por padrão em endpoints críticos
3) Dependência de reflexão/metadados em runtime
Quando o projeto depende de metadados emitidos e reflexão, cresce o risco de:
- comportamento inesperado em builds diferentes
- inconsistência entre ambientes
- dependência de bibliotecas com histórico de CVEs
Mitigação: minimize reflexão quando possível, fixe versões, use SCA (Software Composition Analysis) e monitore advisories.
4) Wrappers que alteram tratamento de exceção
Um decorator mal implementado pode capturar exceções e retornar “sucesso” indevidamente, ou alterar códigos de erro.
Mitigação: política clara: decorators não devem suprimir exceções sem uma justificativa e testes.
Boas práticas de engenharia para decorators
- Documente: o que cada decorator faz, e em quais módulos pode ser usado.
- Mantenha simples: um decorator deve fazer uma coisa bem.
- Teste o comportamento gerado: não apenas o método original.
- Evite efeitos colaterais globais: registries globais são úteis, mas podem dificultar testes e criar acoplamento.
- Padronize nomes e semântica:
@Cacheable,@Retry,@Transactionaldevem ter contratos claros. - Garanta observabilidade: se um decorator muda fluxo (retry, fallback), registre eventos de forma segura e consistente.
Conclusão
TypeScript Decorators são um recurso poderoso para escrever código declarativo e modular, especialmente quando você precisa aplicar políticas e comportamentos transversais sem repetição. Ao mesmo tempo, eles podem introduzir “mágica” e riscos reais — especialmente em logging, autorização e manipulação de exceções.
Usados com disciplina (padrões, testes e revisão), decorators ajudam a construir sistemas mais consistentes. Em contextos sensíveis, trate decorators como parte do perímetro de segurança do software: eles podem reforçar controles — ou enfraquecê-los se forem invisíveis, mal auditados ou mal testados.