My Experience after Porting a JavaScript Project to TypeScript

After much resistance and many, many weeks, I finally embraced TypeScript in a JavaScript project recently. I did this kicking and screaming, foaming at my mouth.

Why all the resistance? Well, I didn’t like the complexity TypeScript introduced; I didn’t like learning another oddly designed and OOP-first, complicated language, just to have it compiled down to JavaScript; the team I’m leading comprises junior developers who’d scream at the idea; I thought the issues I was running into could be solved by the IDE it I could configure it well enough; and so on . . .

Why I even considered TypeScript

What led me to embrace TypeScript was the sheer lack of guardrails while coding in Node.js with JavaScript. The consequences of even a minor refactoring (changing an import or function signature) were impossible to catch until the code was actually running, which was not only frustrating but was compounding my anxiety as the code got more and more complicated and had to be changed in places every time. Some would argue that testing should overcome this, but I have two objections: 1) testing cannot cover everything; 2) testing isn’t always feasible (business constraints, junior team, and all that).

The alternatives

I spent considerably time trying to make pure-JS work. I use VS Code, and while it adds some conveniences (renaming imports if you move a file, for example), it leaves a lot to be desired. It didn’t warn me when not passing the right arguments, for example, and it wouldn’t warn me if wasn’t using the returned objects properly. And so, I thought, there has to be some way, some tool, somewhere that can make this amazing JS-aware IDE more strict and help prevent me from going insane.

I consulted someone and they suggested ESLint. I don’t know if ESLint was the right answer because I never could get it set up and working despite several attempts. Since some of the libraries I was using weren’t even TS-friendly (Knex and Fastify, for example) I even considered moving to a pure-TS experience such as NestJS, but quickly discarded it as I don’t like opaque tools and unnecessary layers of abstractions.

I cursed my fate as the realization weighed down on me: only TypeScript could save me.

The process

Honestly, it wasn’t as hard as I was thinking first. Sure, there were some rough edges to iron out (setting ES3 as the target level since Node 16 couldn’t understand import), but surprisingly, it turned out that just renaming all files to .ts files was a good first step and resulting in a clean build! All in all, I was needlessly worried about not having the type definition for Knex and Fastify. Such a relief!

After that, I slowly went about changing the imports in every file. Now, to ensure that function arguments and return types were unambiguous, I defined interfaces for those functions and applied the right argument and return types.

The workflow

Once everything was set up and running, all I needed to do was:

Conclusion

I didn’t go heavy on TypeScript at all. I don’t think it was necessary in my project and I don’t think it’s necessary in general. I didn’t use enums, and instead, relied on good, old objects to hold constants. I didn’t create any classes. And so on. And I got such pleasant benefits in return!

To conclude, TypeScript is healvily recommended for all JS projects. Just make sure you start slow and move up step by step.