Writing Scalable Types in TypeScript
At Vim we’re using TypeScript heavily and through our TypeScript journey I’ve found a few patterns that helps organise and improve types declarativity.
There are a few pain points I’d like to discuss and offer some useful tricks and strategies to confront them
- Context-less types
Sometimes the type name itself doesn’t help us enough to understand what it should be used for - Common type smells
Like code smells but for types! 🙃 - Duplicate type declarations
For example declaring the same API type both in backend service and in client application
Context-less types
Context is everything, and without it it’s easy to get lost.
What do I mean?
Given this type “PatientPayload”, it might be hard to understand where it’s used and should be used,
We can guess that it has something to do with an API call, but we can’t really be sure if it’s the input the API receives or maybe it’s a result from the API, heck maybe it’s used in both, the problem is - we need to guess.
The obvious solution is adding context with a longer type name,
so for example PatientPayload
might become ApiPatientPayloadInput
.
The problem with that is it’s easy to not follow that standard- which we don’t want, also the code becomes more verbose than needed.
Wouldn’t it be nice if we could manage type categories and sub categories and so on? that’s possible!
since TypeScript 3.8 there is a cool nifty feature called “Export * as” which allows re-exporting another module with a name.
And then we could achieve something like:
It’s great because it allows us to be really organised!
First we can categorise our types into folders (which we should do anyway),
and each folder we can re-export with a name!
Now the type is very explicit and there is little room for error 🚀
While context is important, you shouldn’t overdue it
it’s extremely useful in scale, but there is always a time tradeoff
for how long it takes to add context in a way that is elegant and structured.
It’s extremely important at scale, but in smaller projects it might not be the most important thing - just add context in the type name.
Even if implementation takes longer, be sure that it will improve readability.
Common type smells
The following issues are things you might want to look at when reviewing your code.
any
any
can be extremely useful, but it’s a double-edged sword.
It basically removes type checks and that’s not the point of using TypeScript.
It can result in runtime bugs that could have been captured while transpiling,
and also it raises questions about how we should use that type.
My two cents, use it when dealing with 3rd party libraries that are defined improperly (maybe even contribute to those packages and fix these types yourself! 😇).
Also in the scenario that it becomes a burden to define the needed type, and it won’t affect the usage - “any” can be a good usage.
Try to use it on the most specific part of the type/argument as possible.
Strict mode
Enable in the tsconfig.json
the strict
option, it will catch many bugs at transpile time.
More info about it is available here
Autocompletion failure
If TypeScript can’t help us check types for some code we wrote - we’re missing the whole point of TypeScript.
We want to achieve that in our IDE, that TypeScript compiler will understands what valid interactions are possible with our code.
Understanding through example
In this example, it’s not particularly bad, but chances are we have a predefined set of “actions” we can activate, and each “action” probably has its specific “payload”.
So what do I mean by autocompletion failure? in this example TypeScript can’t tell us what valid input the function can receive, so we have to help TypeScript to help us.
There are a few built-in tools in TypeScript that we can use to get autocompletion and type enforcements, the most useful ones are covered in here
I’ve used “Generics”, “Type Argument Inference” and “Indexed Access Types” to improve the previous function and ended up with this beauty 😍:
Unfortunately there is no single solution for this, and you should take the “solution per problem” approach, but these following guidelines should help us tackle them:
- Using string enums to declare the common set of possibilities
- Indexed Access Types are really useful
- When using Generic types infer them from the function input
- Conditional types with “never” are very powerful but try to avoid them because they are not trivial to understand
GodType files
A single file with more than 200 hundred loc for types is probably a type smell.
The solution is fairly easy, split everything, the more folders and files the merrier, and don’t be shy add context to the mix 👵🏻.
That being said it should still be a single index file that exports all of our split folders and files.
Duplicate type declarations
It’s a problem as old as time, well at least since the time when strongly typed languages started to exist.
Having the luxury of TypeScript both in the backend and in the frontend is great but it’s pretty common to end up with the same data entities types declared in multiple projects.
The biggest con in multiple type declarations for the same data entity is each change to the data entity needs to propagate to all the type declarations making it easy to miss stuff, which can cause bugs, failed builds and so on.
The main solution we found for this problem is type packages for your data entities there are a few ways to incorporate that in your CI/CD flows
- pnpm/npm/yarn workspaces — provides a relatively easy way to link npm packages inside a monorepo
- publish type package to npm — might require a more complex devops setup
In summary,
Leveraging types in our code helps us to sync better, simplify reading, and help in finding errors in an early stage.
I hope this article will help you utilise the full power of TypeScript to improve your usage, assist you to scale and write code along with multiple team members.
I know that here at Vim we love TypeScript and use it as a key tool to tackle real life problems in the US health-care industry.
If you love TypeScript as-well and would like to join us on our adventure don’t hesitate to contact us at getvim.com/careers
Make sure to follow us to get our latest articles