Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
••••••••••••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

Type-Safe By Design: Architecting Applications That Make Bugs Impossible

There's a moment in every developer's career when they realize the best bugs are the ones that never make it to production. Better yet are the bugs that never make it past your code editor. TypeScript gives us a powerful tool to achieve…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

There's a moment in every developer's career when they realize the best bugs are the ones that never make it to production. Better yet are the bugs that never make it past your code editor. TypeScript gives us a powerful tool to achieve this: making invalid states unrepresentable.



This isn't about catching typos or getting autocomplete (though those are nice). It's about encoding your business logic directly into the type system so that entire categories of bugs become compile-time errors instead of runtime disasters.






The Philosophy: Make Wrong Code Look Wrong



Consider this common JavaScript pattern:




function processPayment(amount: number, status: string) {
if (status === "pending") {
// charge the payment
} else if (status === "completed") {
// refund the payment
}
}

processPayment(100, "pendingg"); // Typo - runtime error
processPayment(100, "cancelled"); // What should happen here?






This code compiles fine, but it's a minefield. Typos pass unnoticed. Invalid states are possible. The relationship between status and the operations isn't encoded anywhere.



Now consider this:




type PendingPayment = { status: "pending"; amount: number };
type CompletedPayment = { status: "completed"; amount: number; transactionId: string };
type Payment = PendingPayment | CompletedPayment;

function chargePayment(payment: PendingPayment) { /* ... */ }
function refundPayment(payment: CompletedPayment) { /* ... */ }

// This won't compile - type error!
chargePayment({ status: "completed", amount: 100, transactionId: "123" });






We've made it impossible to charge a completed payment. The compiler enforces our business rules.






Pattern 1: Discriminated Unions (Tagged Unions)



Discriminated unions are the foundation of type-safe state machines. They ensure that certain combinations of data are impossible.






Example: API Request States



The classic mistake:




interface ApiState {
loading: boolean;
data: UserData | null;
error: Error | null;
}

// These invalid states are all possible:
const invalid1: ApiState = { loading: true, data: userData, error: null };
const invalid2: ApiState = { loading: false, data: null, error: null };
const invalid3: ApiState = { loading: false, data: userData, error: someError };






The type-safe approach:




type ApiState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };

function renderUser(state: ApiState<UserData>) {
switch (state.status) {
case "idle":
return <div>Click to load</div>;
case "loading":
return <div>Loading...</div>;
case "success":
// TypeScript knows `state.data` exists here
return <div>{state.data.name}</div>;
case "error":
// TypeScript knows `state.error` exists here
return <div>Error: {state.error.message}</div>;
}
}






Now invalid states are literally unrepresentable. You cannot have loading: true with data present. TypeScript ensures exhaustive case handling and knows exactly what properties exist in each branch.






Example: Form Validation






type FormField<T> =
| { status: "pristine" }
| { status: "validating" }
| { status: "valid"; value: T }
| { status: "invalid"; errors: string[] };

type LoginForm = {
email: FormField<string>;
password: FormField<string>;
};

function canSubmit(form: LoginForm): boolean {
return (
form.email.status === "valid" &&
form.password.status === "valid"
);
}

function submit(form: LoginForm) {
if (form.email.status === "valid" && form.password.status === "valid") {
// TypeScript knows form.email.value and form.password.value exist
api.login(form.email.value, form.password.value);
}
}









Pattern 2: Branded Types (Nominal Typing)



TypeScript uses structural typing, which means two types with the same structure are considered identical. Sometimes you want to distinguish between semantically different values even if they're structurally the same.




type UserId = string;
type ProductId = string;

function getUser(id: UserId) { /* ... */ }
function getProduct(id: ProductId) { /* ... */ }

const userId: UserId = "user_123";
const productId: ProductId = "prod_456";

// This compiles but is wrong!
getUser(productId);






Enter branded types:




type Brand<K, T> = K & { __brand: T };

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

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

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

function getUser(id: UserId) { /* ... */ }
function getProduct(id: ProductId) { /* ... */ }

const userId = makeUserId("user_123");
const productId = makeProductId("prod_456");

// Type error! Cannot pass ProductId where UserId is expected
getUser(productId);









Real-World Use Cases for Branded Types



Validated Email Addresses:




type EmailAddress = Brand<string, "EmailAddress">;

function validateEmail(input: string): EmailAddress | null {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(input) ? (input as EmailAddress) : null;
}

function sendEmail(to: EmailAddress, subject: string) {
// We know `to` is a valid email
}

const userInput = "notanemail";
// Type error - can't pass raw string
sendEmail(userInput, "Hello");

const validated = validateEmail(userInput);
if (validated) {
// This works
sendEmail(validated, "Hello");
}






Sanitized HTML:




type SanitizedHTML = Brand<string, "SanitizedHTML">;

function sanitizeHTML(input: string): SanitizedHTML {
// Actual sanitization logic
return sanitized as SanitizedHTML;
}

function renderHTML(html: SanitizedHTML) {
document.innerHTML = html;
}

const userInput = "<script>alert('XSS')</script>";
// Type error - prevents XSS
renderHTML(userInput);

// This works
renderHTML(sanitizeHTML(userInput));






Positive Numbers:




type PositiveNumber = Brand<number, "PositiveNumber">;

function makePositive(n: number): PositiveNumber | null {
return n > 0 ? (n as PositiveNumber) : null;
}

function calculateInterest(principal: PositiveNumber, rate: number) {
// No need to check if principal > 0
return principal * rate;
}









Pattern 3: Phantom Types



Phantom types let you track information at the type level without runtime overhead.




type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

type Request<Method extends HttpMethod> = {
method: Method;
url: string;
body?: Method extends "GET" ? never : unknown;
};

// This works
const postReq: Request<"POST"> = {
method: "POST",
url: "/api/users",
body: { name: "John" }
};

// Type error! GET requests cannot have a body
const getReq: Request<"GET"> = {
method: "GET",
url: "/api/users",
body: { name: "John" }
};









Example: Type-Safe Builder Pattern






type Builder<Built extends boolean> = {
_built: Built;
name?: string;
age?: number;
};

type BuilderMethods<Built extends boolean> = {
setName: (name: string) => BuilderMethods<Built>;
setAge: (age: number) => BuilderMethods<Built>;
build: Built extends false ? () => BuilderMethods<true> & { result: User } : never;
};

type User = { name: string; age: number };

function createBuilder(): BuilderMethods<false> {
let name: string | undefined;
let age: number | undefined;

return {
setName(n: string) {
name = n;
return this;
},
setAge(a: number) {
age = a;
return this;
},
build() {
if (!name || !age) throw new Error("Incomplete");
return {
...this,
result: { name, age }
} as any;
}
};
}

const builder = createBuilder()
.setName("John")
.setAge(30)
.build();

// Type error! Can't build twice
builder.build();

// TypeScript knows result exists
console.log(builder.result.name);









Pattern 4: Const Assertions and Literal Types



Const assertions create exact types instead of widened types, enabling precise type checking.




// Without const assertion
const config = {
apiUrl: "https://api.example.com",
timeout: 5000
};
// Type: { apiUrl: string; timeout: number }

// With const assertion
const config = {
apiUrl: "https://api.example.com",
timeout: 5000
} as const;
// Type: { readonly apiUrl: "https://api.example.com"; readonly timeout: 5000 }









Example: Type-Safe Configuration






const ENVIRONMENTS = ["development", "staging", "production"] as const;
type Environment = typeof ENVIRONMENTS[number];

const config = {
development: { apiUrl: "http://localhost:3000", debug: true },
staging: { apiUrl: "https://staging.api.com", debug: true },
production: { apiUrl: "https://api.com", debug: false }
} as const;

function getConfig(env: Environment) {
return config[env];
}

// Type error!
getConfig("test");

// This works, and TypeScript knows the exact shape
const prodConfig = getConfig("production");
// prodConfig.debug is type `false`, not `boolean`









Example: Type-Safe Event System






const EVENTS = {
USER_LOGGED_IN: (userId: string, timestamp: Date) => ({
type: "USER_LOGGED_IN" as const,
userId,
timestamp
}),
PAYMENT_PROCESSED: (amount: number, currency: string) => ({
type: "PAYMENT_PROCESSED" as const,
amount,
currency
}),
ERROR_OCCURRED: (message: string, code: number) => ({
type: "ERROR_OCCURRED" as const,
message,
code
})
};

type Event = ReturnType<typeof EVENTS[keyof typeof EVENTS]>;

function handleEvent(event: Event) {
switch (event.type) {
case "USER_LOGGED_IN":
// TypeScript knows event.userId and event.timestamp exist
console.log(`User ${event.userId} logged in at ${event.timestamp}`);
break;
case "PAYMENT_PROCESSED":
// TypeScript knows event.amount and event.currency exist
console.log(`Payment of ${event.amount} ${event.currency}`);
break;
case "ERROR_OCCURRED":
// TypeScript knows event.message and event.code exist
console.log(`Error ${event.code}: ${event.message}`);
break;
}
}

// Usage is type-safe
handleEvent(EVENTS.USER_LOGGED_IN("user_123", new Date()));









Pattern 5: Making Invalid States Unrepresentable



This is where everything comes together. Design your types so that invalid states cannot be constructed.






Example: User Authentication State



Bad design:




interface AuthState {
isAuthenticated: boolean;
user: User | null;
token: string | null;
}

// All these invalid states are possible:
// { isAuthenticated: true, user: null, token: null }
// { isAuthenticated: false, user: someUser, token: "token" }






Good design:




type AuthState =
| { status: "anonymous" }
| { status: "authenticated"; user: User; token: string };

function renderHeader(auth: AuthState) {
if (auth.status === "authenticated") {
// TypeScript knows user and token exist
return <div>Welcome, {auth.user.name}</div>;
} else {
return <div>Please log in</div>;
}
}









Example: Feature Flags with Type-Safe Rollout






type FeatureConfig =
| { enabled: false }
| { enabled: true; config: { apiEndpoint: string; timeout: number } };

const FEATURES = {
newDashboard: { enabled: false },
advancedSearch: {
enabled: true,
config: { apiEndpoint: "/api/search/v2", timeout: 10000 }
}
} as const satisfies Record<string, FeatureConfig>;

type Features = typeof FEATURES;

function useFeature<K extends keyof Features>(
key: K
): Features[K] extends { enabled: true } ? Features[K]["config"] : null {
const feature = FEATURES[key];
if (feature.enabled) {
return feature.config as any;
}
return null as any;
}

// TypeScript knows this returns the config object
const searchConfig = useFeature("advancedSearch");
if (searchConfig) {
console.log(searchConfig.apiEndpoint);
}

// TypeScript knows this returns null
const dashboardConfig = useFeature("newDashboard");









Pattern 6: Recursive Types for Validation



Build type-safe validation that scales with your data structures.




type ValidationError = { field: string; message: string };
type ValidationResult<T> =
| { success: true; data: T }
| { success: false; errors: ValidationError[] };

type Validator<T> = (value: unknown) => ValidationResult<T>;

const isString: Validator<string> = (value) => {
if (typeof value === "string") {
return { success: true, data: value };
}
return { success: false, errors: [{ field: "root", message: "Must be string" }] };
};

const isNumber: Validator<number> = (value) => {
if (typeof value === "number") {
return { success: true, data: value };
}
return { success: false, errors: [{ field: "root", message: "Must be number" }] };
};

function isObject<T extends Record<string, Validator<any>>>(
validators: T
): Validator<{ [K in keyof T]: T[K] extends Validator<infer U> ? U : never }> {
return (value) => {
if (typeof value !== "object" || value === null) {
return { success: false, errors: [{ field: "root", message: "Must be object" }] };
}

const obj = value as Record<string, unknown>;
const errors: ValidationError[] = [];
const data: any = {};

for (const key in validators) {
const result = validators[key](obj[key]);
if (result.success) {
data[key] = result.data;
} else {
errors.push(...result.errors.map(e => ({ ...e, field: `${key}.${e.field}` })));
}
}

if (errors.length > 0) {
return { success: false, errors };
}
return { success: true, data };
};
}

const userValidator = isObject({
name: isString,
age: isNumber
});

const result = userValidator({ name: "John", age: 30 });
if (result.success) {
// TypeScript knows result.data is { name: string; age: number }
console.log(result.data.name);
}









Practical Tips for Type-Safe Design






1. Start with States, Not Fields



When designing a type, list all possible states first, then figure out what data belongs to each state.






2. Use the Compiler as a TODO List



If you refactor a discriminated union, the compiler will show you every place that needs updating. This is a feature, not a bug.






3. Don't Fight the Type System



If the types feel awkward, your domain model might need rethinking. Types should clarify, not obscure.






4. Progressive Enhancement



You don't need to make everything type-safe on day one. Start with critical paths (payments, authentication) and expand outward.






5. Document with Types



A well-designed type is better documentation than comments:




// Bad: Comments lie, code doesn't
type Payment = { status: string }; // Should be "pending" or "completed"

// Good: Type enforces the truth
type Payment = { status: "pending" | "completed" };









The Payoff



Type-safe design isn't about being pedantic. It's about:





  • Catching bugs at compile time instead of in production


  • Enabling fearless refactoring because the compiler shows you what breaks


  • Self-documenting code where types tell you what's possible


  • Reducing cognitive load because invalid states aren't even considered


  • Onboarding new team members who can explore your API through autocomplete



The investment in designing types upfront pays dividends every time someone (including future you) interacts with that code.






Conclusion



The best code is code that makes bugs impossible. TypeScript's type system is powerful enough to encode complex business rules, but it requires thinking differently about design. Instead of thinking "what fields does this object need?", think "what states can this object be in?"



Make invalid states unrepresentable. Use discriminated unions for mutually exclusive states. Use branded types to prevent mixing incompatible values. Use phantom types to track compile-time information. Let the compiler be your ally.



Your future self—and your production error logs—will thank you.






What type-safe patterns have saved you from bugs? Share your experiences in the comments.

1. Sofort-Triage & Abwehrmaßnahmen

SOC Incident Playbook: Remote Code Execution (RCE) Defense
Syntax validiert (0 Fehler)
title: Detect Exploitation - Type-Safe By Design: Architecting Applications That Make Bugs Impossible
id: 3f37cdd0-efb3-40e8-b528-fe76978520c9
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-25
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-25"
        description = "YARA Signature for "
    strings:
        $str = "Type-Safe By Design: Architect" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
("Type-Safe By Design Architecting Applica")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
message: "*Type-Safe By Design Architecting Applica*"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where Message has "Type-Safe By Design Architecting Applica"
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc

2. Cyber Threat Intelligence & Forensik

CTI Threat Relationship Graph2 Knoten / 1 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich Type-Safe By Design: Architecting Applic.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Type-Safe By Design: Architecting Applications That Make Bugs Impossible

Thematisch verwandte Begriffe: TypeSafe, Design, Architecting, Applications · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-87722 | Uncontrolled Resource Consumption (CWE-400 / CWE-1333) in regex search q…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel • Rechts: nächster Artikel • unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel TTP ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...
↗ Original-Quelle