Back to DocumentationSoftware Development

TypeScript Best Practices

Our TypeScript coding standards, type safety patterns, and error handling conventions used across all Vithon projects.

Core Principles

Strict Mode Always

Enable strict: true in tsconfig.json. This catches null/undefined errors, enforces explicit types, and prevents common bugs.

Prefer Types Over Interfaces

Use type aliases for unions, intersections, and mapped types. Use interfaces only when you need declaration merging.

Avoid "any" at All Costs

Use unknown for values with uncertain types, then narrow with type guards. Use "as const" for literal type inference.

Validate at Boundaries

Use Zod schemas to validate data at API boundaries, form inputs, and external data sources. Infer types from schemas.

Error Handling Pattern

We use a Result pattern inspired by Rust for operations that can fail. This makes error handling explicit and eliminates unhandled exceptions.

Result Type

Define a Result<T, E> type as { success: true; data: T } | { success: false; error: E }

Usage

Functions that can fail return Result instead of throwing. Callers must check .success before accessing .data

Async

Wrap async operations in a tryCatch utility that catches exceptions and returns a Result automatically

Propagation

Chain Results with a flatMap utility, similar to .then() but for Result types

Useful Utility Types

  • Prettify<T> - Flatten intersections for readable hover types in your IDE
  • StrictOmit<T, K> - Like Omit but K must be a key of T (prevents typos)
  • NonEmptyArray<T> - A tuple type guaranteeing at least one element
  • DeepPartial<T> - Recursively make all nested properties optional
  • Brand<T, B> - Nominal typing for IDs and other special strings (UserId, Email, etc.)

Want to improve your TypeScript codebase? Get in touch for a code review or architecture consultation.