I've been writing production TypeScript for a little over year and as a hobby for a couple of years longer than that. If you've never used TypeScript before, the quick way to describe it is that it's ES2015+ and types thrown together. Ie. modern JavaScript with real typing.
TypeScript is awesome and I love writing it and over time, I've noticed my own style and my own patterns emerge one of which I'd like to share and, hopefully, justify why I stick to those patterns.
Local Interfaces > Global Interfaces
Interfaces in TypeScript are essentially object definitions that describe what an object should minimally look like. For example, if I had a DatabaseConfig
interface, it might look something like this:
interface DatabaseConfig {
host: string,
port: number,
password: string
}
function connectToDb(dbConfig: DatabaseConfig) {
// database connection config
}
What that basically means is that whenever you call the function connectToDb
, you need to pass in an object that looks like the DatabaseConfig
interface (along with the appropriate typings for its properties).
A pattern I picked up from a Golang styleguide article (I can't remember which) was the idea of "local interfaces", interfaces that describe exactly what I need from an object within that single file.
This DatabaseConfig
interface, if shared, will grow exponentially to encompass the needs of every function that might touch this object. A createDatabasePool
function might additionally look for a poolSize
property on that config which will now be required by every function that references this interface, whether they use it or not. Imagine that we also had a function that would return a driver for that particular database so we might need a type
property which no function cares about except the driver one.
Basically, sharing interfaces (or using what I call global interfaces
) causes interfaces to bloat and to impose artificial requirements on properties that might not even be used by the function/code block/whatever that references the interface. It creates a strange "coupling" between possibly unrelated pieces of code.
Instead, what I suggest is writing interfaces local to a file which describe only the necessary properties required to be in the object by the code in that file. Eg. if you have a createPool
function, you might write something like this:
interface PoolConfig {
poolSize: number
}
export function createPool(config: PoolConfig, driver) {
// uses config.poolSize somewhere in the code
}
This way, we're telling the developer working in that file that all we really need is poolSize
and we don't use anything else from that config object.
I've found this to be super useful in keeping with the idea that types are really just documentation that the computer can also view and utilize.
Exceptions
There are a couple of exceptions to this rule.
Those exceptions are that if you're using object Models for your data, you might want to have those models available as interfaces as well to communicate to the developer (and the compiler) that you're really requiring this model.
You might not care about the exact keys, you might care more about getting the actual Model (or something with the exact same shape).
The other exception to the rule is if you have complex objects that require keeping up with its complex shape. Imagine that you have an object that nests 5 levels deep. It's more prudent to have a single interface that you import which describes this rather than writing out, quite uselessly, complex nested interfaces.
Top comments (6)
Hi, thanks for sharing.
I've a doubt related to typescript.
How do I manage a single variable with having simultaneously two types?
For example:
product: Product | string;
where string version would denote productId. It would be very useful when fetching populated fields via mongoose.
good question! I think those two types are too vastly different to be in a union type and be useful.
I'd probably split it out to be
productId: string
andproduct: Product
and just convert one to the other if necessary.Okay I got it, thank you sir.
Well put - this is a powerful realization
Excellent post! I’ve only started picking up TypeScript recently, so little nuggets of wisdom like this are super useful 😄
Hi there! I share this idea too. Good post.