ahmedallem.
Engineering · 3 min read

Converting JavaScript to TypeScript: A Practical Migration Guide

Converting JavaScript to TypeScript broke everything - temporarily. Six months later it was my best engineering decision. Here's the migration guide I wish I'd had.

Ahmed Allem

Ahmed Allem

Founder & CTO · Aviation, AI & Startups

ShareShare
Converting JavaScript to TypeScript: A Practical Migration Guide

For three years, I wrote JavaScript without types. It was fast, flexible, and occasionally terrifying.

The terrifying part was refactoring. Change a function signature in one file and grep across the codebase hoping to find every caller. Miss one and discover it in production when a user triggers the forgotten code path.

TypeScript promised to make refactoring safe. It also promised to slow me down with type annotations, type errors, and configuration complexity.

Both promises were accurate. The net result was overwhelmingly positive.

The Migration

I chose to migrate my most active product first: Aviation Infinity. The codebase had grown to thousands of lines with dozens of API endpoints, database models, and UI components.

The approach: gradual migration. TypeScript supports allowJs, which means JavaScript files and TypeScript files can coexist. I renamed files from .js to .ts one at a time, adding types as I went.

Week 1: Configuration and types. Set up tsconfig.json. Created type definitions for database models, API responses, and shared interfaces. These types described the existing data shapes. I wasn't redesigning the data model, just documenting it.

Week 2-3: API routes. Converted API route handlers to TypeScript. Added request/response types. Each conversion found at least one inconsistency, like a response field that was named differently than the frontend expected, a request parameter that could be undefined but wasn't handled.

Week 4: Frontend components. Converted React components to TypeScript. Added prop types. Each conversion revealed prop drilling issues, like props passed through three components that no longer existed, optional props treated as required.

Week 5: Cleanup. Fixed all TypeScript errors. Removed any types (mostly). Added strict mode. The codebase was fully typed.

What the Migration Revealed

The migration process found over thirty bugs. Not theoretical bugs, but actual code paths that would fail under specific conditions. Examples:

  • A function that expected a user ID as a string but sometimes received a MongoDB ObjectId
  • An API response that included a createdAt field as a Date object, but the frontend parsed it as a string
  • A conditional that checked if (user.role === 'admin') but the role field was sometimes undefined (not null, not 'user', but undefined)
  • A calculation that multiplied a number by a string (JavaScript silently converts; TypeScript screams)

Each of these bugs existed in production. None had been reported by users. They lived in edge cases that were rarely triggered. TypeScript found them all during compilation.

Six Months Later

After six months of TypeScript, the verdict is clear:

Refactoring is safe. Change a function signature and TypeScript shows every file that needs updating. The compiler is a comprehensive, zero-effort code review. I refactor fearlessly now, which means I refactor more often, which means the code stays cleaner.

Onboarding is faster. When I return to a product after weeks away, the types serve as documentation. function getUser(id: string): Promise<User | null> tells me everything: the input, the output, and the fact that the user might not exist. No reading the implementation needed.

IDE experience is transformed. Autocomplete that works. Inline documentation. Red squiggles on errors before running the code. The development experience with TypeScript in VS Code is qualitatively better than JavaScript.

Writing speed is slightly slower. Type annotations add keystrokes. Generic types require thought. Complex types can be puzzles. The additional effort is real but modest, maybe 10% slower writing speed. The saved debugging time exceeds this cost by an order of magnitude.

The Verdict

Was it worth it? Unequivocally yes. TypeScript is now my default for every new file in every project. The migration cost was a one-time investment. The returns are ongoing and compounding.

The developers who resist TypeScript ("it's too verbose," "it slows me down," "I don't need types") are making the same argument I made for three years. I was wrong then. They're wrong now.