5 Quirks of Converting a Node.js Express API to TypeScript
All in all, it's been a smooth experience but if you're a developer yourself, you know that this type of refactorings never happen on a straight line, there are always bumps and quirks along the way.
1. At transition, module loading can get tricky in Node.js
TypeScript shares the same concept of a module as ES6. TypeScript's module import and export syntax is very similar to its ES6 counterpart but the CommonJS module system that Node.js uses is different.
Look at the code below and see what happens when "ideas.js" has to load a module from "AdminRepository.ts". All of a sudden you have to use the "default" property to be able to access the exported object. Eventually, when the consumer (idea.js) is also converted to TypeScript, the problem will go away.
2. Adding Type Definition Files to a Project
Before 2.0 release of TypeScript, finding and using these type definition files could get a bit painful and confusing - same goes for creating them. TypeScript 2.0 fixed some of these issues by introducing a simplified declaration fileacquisition model.
In node.js, in order to grab the underscore.js package, you'd use:
npm install underscore --save
Now, if you also want to use this package from TypeScript, with strong typing, you'll have to download the necessary type declaration file as well:
npm install @types/underscore --save
Starting with TypeScript 2.0, these type definition files are regular npm packages.
After downloading the type definition file, VS Code stops complaining. Furthermore, I get all type safety features and intellisense while using this library, which is what I want and why I use TypeScript.
On the other hand, sometimes a library you're using might not have a type definition file. For example, I'm using a passport extension library called "passport-github2" which doesn't have a definition file yet (and probably never will). Inside any TypeScript file that uses this library, VSCode complains that it doesn't understand this library. TypeScript compilation also results in the same error. But even though the typing file is missing, the actual library is there and it works during run-time.
But of course we've a problem here. The problem is that the TypeScript compiler will complain about this missing type definition file forever. Being a good developer, what you want to do is to avoid having these kinds of error messages lying around and being ignored.
Having said this, it's worth mentioning that people are complaining about this aggressive behavior of TypeScript, as it eagerly wants all 3rd party packages to have type definition files. But there's probably a solution coming.
Until then, there are 2 possible solutions:
- Another solution is to declare an ambient module
for that library in a separate file - either marking it with 'any' keyword or with proper types.
I chose the latter, because I noticed that "allowJs" flag has far bigger consequences. It silences a bunch of other types of errors/warnings which I would like to be informed about. Here is how those ambient modules look like:
3. Incorrect / Incomplete Type Definition Files
Sometimes a type definition file (.d.ts file) turns out to be incorrect or incomplete. The screenshot below shows a situation where the package "aws-sdk" has an incomplete type definition. It doesn't know that "endpoint" property exists even though it does exist in AWS documentation.
I literally went into the type declaration file I downloaded via npm and added the field "endpoint" in there to fix the problem. Probably I have to commit this file into source control now. Unfortunately I couldn't find a best practice that explains how to properly handle these situations. For now, I'll go with the solution of manually fixing the definition file and adding it into the source control. Below you can see the patch I made to the declaration file of AWS-SDK (line commented out).
The solution is to either declare this object as a new interface or mark it as "any", as suggested here). I've chosen the first, a more type-safe approach, and declared my new type in the "aws-sdk" type declaration file, which solved the problem in a nicer way imho. See the DynamoDBAsyncClient interface definition below:
5. Using import or export in your TypeScript file turns it into a module
Let's first look at a simple case that doesn't make use of modules. Here below there's an interface and its implementation right below it. See how AdminRepository.ts refers to IAdminRepository interface without any ceremony.
Now look at these other two files, in which the interface exposes a third party object and therefore has to import that third party module. Note that index.ts can't just refer to that interface anymore, without actually importing it.
By the way, I also noticed that TypeScript coding guidelines do not recommend using "I" as a prefix for interface names. So I need to hold my C# reflexes.
Thanks for reading.