Skip to main content

Branded Types | Typescript

  • Branded Types | Typescript

Branded types (ou nominal typing) em TypeScript são uma técnica para criar tipos distintos mesmo quando compartilham a mesma estrutura — algo que o TypeScript não faz nativamente, já que ele usa structural typing.

Eles são extremamente úteis para evitar erros como confundir:

  • UserId com ProductId
  • Celsius com Fahrenheit
  • Email com string comum
  • etc.

✅ Como criar Branded Types

type Branded<T, B> = T & { __brand: B };

Exemplo: IDs distintos

type UserId = Branded<string, "UserId">;
type ProductId = Branded<string, "ProductId">;

function getUser(id: UserId) {}
function getProduct(id: ProductId) {}

const userId = "123" as UserId;
const productId = "123" as ProductId;

✔️ Isso funciona:

getUser(userId);
getProduct(productId);

❌ Isso NÃO funciona:

getUser(productId); // erro de tipo

Mesmo ambos sendo string, o TypeScript passa a tratá-los como tipos diferentes.


🔒 Evitando que o usuário crie IDs “do nada”

Se quiser restringir ainda mais, crie factory functions:

function makeUserId(id: string): UserId {
return id as UserId;
}

function makeProductId(id: string): ProductId {
return id as ProductId;
}

Agora:

const x = "abc" as UserId; // possível, mas feio
const y = makeUserId("abc"); // recomendado

⚖️ Branded Types para números

type Celsius = Branded<number, "Celsius">;
type Fahrenheit = Branded<number, "Fahrenheit">;

const tempC = 20 as Celsius;
const tempF = 68 as Fahrenheit;

function toFahrenheit(c: Celsius): Fahrenheit {
return ((c * 9) / 5 + 32) as Fahrenheit;
}

🧠 Boas práticas

✔️ Use nomes de brands descritivos

type OrderId = Branded<string, "OrderId">;

✔️ Crie helper genéricos

type Brand<B extends string> = { __brand: B };

type Branded<T, B extends string> = T & Brand<B>;

✔️ Esconda as brands (elas não existem em runtime)

Branded types são zero-cost: não mudam nada no JavaScript final.


⭐ Brand vs. Nominal Types vs. Opaque Types

Brand

“Marca” só para o TypeScript → técnica mais comum.

Opaque Types (estilo FP)

Você não consegue acessar o tipo interno fora do módulo:

// module A
type Email = string & { readonly __brand: unique symbol };
export function makeEmail(v: string): Email {
return v as Email;
}

// module B
const x: Email = "a@b.com"; // erro → criar só com makeEmail