Skip to main content

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

  1. Type Safety: Catch 15-30% of bugs at compile time (Microsoft research)
  2. Developer Experience: Best-in-class IDE support (VS Code, Cursor)
  3. Ecosystem: 90%+ of npm packages have TypeScript types
  4. Hiring: Larger talent pool than alternatives (Rust, Go)
  5. 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

  1. Type Safety

    • Catch bugs at compile time (null checks, type mismatches)
    • Refactoring confidence (rename symbol across codebase)
    • Autocomplete and IntelliSense
  2. Developer Experience

    • Single language for frontend, backend, tooling
    • Excellent IDE support (VS Code, Cursor)
    • Rich ecosystem (DefinitelyTyped has 8000+ type definitions)
  3. Maintainability

    • Self-documenting code (types as documentation)
    • Easier onboarding for new developers
    • Safer refactoring
  4. Hiring

    • Large talent pool (TypeScript #3 most loved language - Stack Overflow 2024)
    • Easy to train JavaScript developers
  5. Performance

    • Fast enough for API server (<50ms response times achievable)
    • V8 JIT optimization
    • Comparable to Go for I/O-bound workloads

Negative

  1. Compilation Overhead

    • Adds 1-5 seconds to build time
    • Mitigation: Use incremental compilation, tsx for dev mode
  2. Learning Curve

    • Developers must learn type system (generics, unions, etc.)
    • Mitigation: Team already knows TypeScript
  3. Type Definition Maintenance

    • Must keep types in sync with runtime behavior
    • Mitigation: Use Zod for runtime validation + type inference
  4. 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 any types 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

References