ADR-0005: TypeScript as Primary Programming Language
Status
Accepted - 2025-01-26
Context
TVL Platform requires a programming language for both frontend and backend development with the following requirements:
Business Requirements
- Fast time-to-market (< 3 months for MVP.0)
- Small team (2-3 engineers initially)
- Codebase must be maintainable as team grows
- Need to hire developers easily (large talent pool)
Technical Requirements
- Type safety to catch bugs at compile time
- Strong IDE support (autocomplete, refactoring)
- Excellent ecosystem for web development
- Compatible with modern frameworks (React, Node.js)
- Good performance for API server and background workers
Constraints
- Team has JavaScript/TypeScript experience
- React and Node.js already selected (TypeScript-native)
- Must support strict null checking
- Need clear migration path from JavaScript if needed
Decision
TypeScript 5.3+ with strict mode enabled for all code (frontend, backend, workers, tooling).
Configuration
// tsconfig.json (base)
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "lib": ["ES2022"],
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "incremental": true
  }
}
Rationale
- Type Safety: Catch 15-30% of bugs at compile time (Microsoft research)
- Developer Experience: Best-in-class IDE support (VS Code, Cursor)
- Ecosystem: 90%+ of npm packages have TypeScript types
- Hiring: Larger talent pool than alternatives (Rust, Go)
- Performance: V8 JIT compiles to native code (fast enough for API server)
Alternatives Considered
Alternative 1: JavaScript (Plain)
Rejected
Pros:
- No compilation step (faster iteration)
- Simpler setup
- Runtime flexibility
Cons:
- No type safety (bugs caught at runtime)
- Poor refactoring support
- Difficult to maintain as codebase grows
- Team would add JSDoc types anyway (defeats purpose)
Decision: Type safety benefits outweigh compilation overhead.
Alternative 2: Go
Rejected
Pros:
- Excellent performance (compiled, concurrent)
- Simple language (easy to learn)
- Great for backend services
Cons:
- Cannot use for frontend (React requires JavaScript/TypeScript)
- Would need two languages (Go backend, TypeScript frontend)
- Smaller ecosystem for web development
- Harder to hire (smaller talent pool)
Decision: Single language for full stack reduces cognitive load.
Alternative 3: Rust
Rejected
Pros:
- Maximum performance and safety
- No garbage collection
- Excellent for systems programming
Cons:
- Steep learning curve (ownership, lifetimes)
- Overkill for CRUD API and background workers
- Cannot use for frontend
- Very difficult to hire (tiny talent pool)
- Slower development velocity
Decision: Performance not critical enough to justify complexity.
Alternative 4: Python
Rejected
Pros:
- Excellent for data processing and ML
- Large ecosystem
- Easy to learn
Cons:
- Poor type safety (mypy not as good as TypeScript)
- Slower than Node.js for API servers
- Cannot use for frontend
- Less suitable for real-time systems
Decision: Node.js/TypeScript better for web applications.
Consequences
Positive
- 
Type Safety - Catch bugs at compile time (null checks, type mismatches)
- Refactoring confidence (rename symbol across codebase)
- Autocomplete and IntelliSense
 
- 
Developer Experience - Single language for frontend, backend, tooling
- Excellent IDE support (VS Code, Cursor)
- Rich ecosystem (DefinitelyTyped has 8000+ type definitions)
 
- 
Maintainability - Self-documenting code (types as documentation)
- Easier onboarding for new developers
- Safer refactoring
 
- 
Hiring - Large talent pool (TypeScript #3 most loved language - Stack Overflow 2024)
- Easy to train JavaScript developers
 
- 
Performance - Fast enough for API server (<50ms response times achievable)
- V8 JIT optimization
- Comparable to Go for I/O-bound workloads
 
Negative
- 
Compilation Overhead - Adds 1-5 seconds to build time
- Mitigation: Use incremental compilation, tsxfor dev mode
 
- 
Learning Curve - Developers must learn type system (generics, unions, etc.)
- Mitigation: Team already knows TypeScript
 
- 
Type Definition Maintenance - Must keep types in sync with runtime behavior
- Mitigation: Use Zod for runtime validation + type inference
 
- 
Build Complexity - Need TypeScript compiler in build pipeline
- Mitigation: Standard tooling (Vite, Nx) handles this
 
Implementation Guidelines
1. Strict Mode Enabled
All projects must enable strict mode:
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true
  }
}
2. No any Types
Forbidden:
// ❌ Bad
function process(data: any) { ... }
Allowed:
// ✅ Good
function process(data: unknown) {
  if (typeof data === 'string') {
    // TypeScript knows data is string here
  }
}
3. Explicit Return Types
Required for all public functions:
// ✅ Good
export function calculateTotal(price: number, tax: number): number {
  return price + (price * tax);
}
// ❌ Bad (return type inferred)
export function calculateTotal(price: number, tax: number) {
  return price + (price * tax);
}
4. Runtime Validation
Use Zod for runtime validation + type inference:
import { z } from 'zod';
const BookingSchema = z.object({
  guestName: z.string().min(1).max(255),
  checkIn: z.string().datetime(),
  checkOut: z.string().datetime(),
  totalCents: z.number().int().positive()
});
type Booking = z.infer<typeof BookingSchema>; // Type inferred from schema
Validation Checklist
- All packages use TypeScript 5.3+
- Strict mode enabled in all tsconfig.json files
-  No anytypes in codebase (ESLint rule enforced)
- Explicit return types on public functions
- Zod schemas for runtime validation
- Type definitions for all dependencies
- Build succeeds with zero type errors
- IDE autocomplete works for all symbols