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.
Define a Result<T, E> type as { success: true; data: T } | { success: false; error: E }
Functions that can fail return Result instead of throwing. Callers must check .success before accessing .data
Wrap async operations in a tryCatch utility that catches exceptions and returns a Result automatically
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.