Migrating to TypeScript: Type Safety in JavaScript
My practical experience adopting TypeScript in a growing frontend codebase — what I gained, what I lost, and when it actually proved its worth.
As my frontend codebases grew, I kept noticing the same pattern: passing the wrong type to a function parameter, reading a non-existent property — these mistakes surfaced at runtime, often far too late. When I adopted stricter type declarations in PHP 7.x, the confidence it gave me was significant. I wanted the same guarantee on the JavaScript side. By the time I decided to migrate to TypeScript, I felt prepared — but the real experience turned out different from what I expected.
What is TypeScript?
TypeScript is a superset of JavaScript developed by Microsoft that adds a static type system to the language. The TypeScript code you write compiles down to plain JavaScript — no special runtime is needed for the browser or Node.js. TypeScript’s job is to tell you at write time: “this value expects a string, but you’re passing a number.”
Where I started the migration
The least painful way to add TypeScript to an existing project is to start with "strict": false in tsconfig.json and tighten things up gradually. You don’t have to rename every file from .js to .ts; with allowJs: true you can mix both formats freely.
When I decided to write a new component, I started it directly in TypeScript:
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'editor' | 'viewer';
}
function formatUserLabel(user: User): string {
return `${user.name} <${user.email}>`;
}
// This line produces a compile-time error:
// formatUserLabel({ id: 1, name: 'Test' }); // email is missing
In PHP, type declarations live in method signatures; in TypeScript you define the shapes of your data using interfaces or type aliases. It felt a bit strange at first, but I got used to it quickly.
When it proved most valuable
Refactoring confidence. When you rename a field on an interface, TypeScript flags every place that uses it. In plain JavaScript, you would only discover that mistake at runtime — or if your tests happened to catch it.
IDE integration. Autocomplete becomes dramatically more useful with TypeScript. Type user. and the editor already knows which fields exist. Pass the wrong type to a parameter and you get a red underline immediately.
Team communication. Interfaces act as living documentation — they make it explicit what a function expects and what it returns.
Points of friction
TypeScript’s learning curve comes less from the syntax and more from the type system itself. Simple cases are easy to express. But in the real world:
// When the response type from an external API is unknown:
const response = await fetch('/api/users');
const data = await response.json(); // 'any' type — no guarantees
Bringing external data into the type system requires type guards or as type assertions. This is still an area where I’m learning.
function isUser(obj: unknown): obj is User {
return typeof obj === 'object' && obj !== null && 'id' in obj;
}
Also, not every third-party library ships with TypeScript types. The @types/... packages cover most popular libraries, but you sometimes have no choice but to work with any.
strict mode: enable it early or late?
Starting with "strict": false makes the migration easier, but there’s a trap: with strict off, TypeScript may not protect you from real bugs either. Without strictNullChecks, nullable errors go undetected; without noImplicitAny, the any type spreads silently. Turning on strict: true in new files from the very beginning was far less painful than trying to expand coverage later. Gradually tightening the old files is a separate effort — but it is doable.
Abandoning JavaScript?
TypeScript is not an alternative to JavaScript; it sits on top of it. All your existing JavaScript is valid TypeScript. The difference is this: TypeScript doesn’t slow you down — it actually lets you move faster in large codebases. For a small throwaway script, type declarations may be overkill; but for a multi-developer project with hundreds of files, the trade-off clearly favors TypeScript.
I now get the same comfort on the frontend that type safety has always given me in my PHP backend. There is an upfront cost to the migration, but over the long run it is worth it.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.