Why We Chose Typescript & The Advantages of Generics

Madeleine De Forest-Brown
Unibuddy Technology Blog
4 min readJul 14, 2021
Photo by Kelly Sikkema on Unsplash

One of the perks of working at Unibuddy is that each squad has autonomy over their product’s design and implementation decisions. We’ve moved away from a monolithic architecture and are working towards microservices, this facilitates engineers specifically selecting the best tools they need for the job.

In this blog, I’ll discuss why we’ve chosen Typescript and cover the common advantages that it offers including type safety and the availability of generics, and then we’ll take a look at a Typescript generics example.

So why Typescript?

Typescript is a superset of Javascript which transpiles to Javascript. It implements static typing, which essentially means the code gets checked for certain errors before runtime. Static typing offers a few more advantages than simply checking for mistakes before they crash your application. Type inference means that an IDE knows what to expect and can offer predictive options. In other words, more engineers can work on the service and commit code with more confidence and less cognitive load of having to know the intricacies of the value types.

Generics takes this a step further and allows types, interfaces, and classes to act as parameters. This allows us to reuse code for different use cases where the input parameter varies but the implementation is reusable. So what do generics look like? A common convention is to use T which denotes the unknown variable. A quick example is type Example<T> = T | T[] where <T> defines the generic type and T | T[] denotes the form the generic could be passed in with.

So when would we use this?

Outlined below is an instance where we have written code that could benefit from using generics. In this example, we are implementing a new CacheService that will be used by both a UserService and a UniversityService to cache User and University objects respectively.

When we first created our CacheService it was made as a simple service that interacts with a third-party cache.

This service works fine but we’ve used the type any twice. The Typescript any type can be assigned to literally any variable and it doesn’t offer us much security on what is being saved in our cache. The initial implementation of the CacheService in the UserService and UniversityService is outlined below.

In this instance, keeping the cache generic has the advantage of allowing both our user and university services to implement it, and call the get and set methods, but there is no specification on the type of object we are saving to the cache.

What if we created two separate cache services?

An alternative implementation would be to specify that only the User or University type can be passed to the cache. To do this, we could create a UserCacheService and a UniversityCacheService.

Although we have improved the security of what we are saving to the cache, we have duplicated the amount of code we are writing. One of the advantages of Typescript generics is that it allows us to reduce this boilerplate code.

So let’s implement some generics.

We define a service to be of generic type T by adding <T> after the class name. Notice how the return type of the get method and the data we save in the cache are also of type T.

We specify the unknown variable when we inject the CacheService in the UserService and UniversityService constructors. You can see that the data in the set method is now specified, as is the return value from the get method.

This ensures that when we implement the cache service methods, we must specify the correct type, if we were to populate the set method in the UserService with an object that did not match the User type, then we would see a compile-time error (one of the great advantages of Typescript over Javascript).

We have now successfully reduced the amount of code we need to write whilst specifying what can be saved in the cache by using Typescript generics.

Could we further increase type safety with generics?

A way to take this a step further is to use inheritance with generics to specify what we expect our cache object to look like. For example, we may want to assert that each object in the cache has a name or creation date. The way we achieve this is with an interface (it should be noted that as of TS 2.7 a type can be used throughout the code examples in place of an interface and I highly recommend checking out this Stack Overflow post on the difference), which forms a syntactic contract that ensures the correct fields are present. We simply need to update the generic type to extend an interface with the required and/or optional fields we want.

Now any service that implements the CacheService must implement a type that conforms to the CacheObject. In theUserService below we have implemented a new UserCacheObject in place of the previously used User object.

The UserCacheObject implements the CacheObject which ensures the correct fields are present.

With just a little bit more code we now have the great advantage of ensuring the correct information required is stored in the cache. The same method could be applied to the UniversityService and any other service/object in future that needs to be stored in the cache.

There we have it, a very simple use case for generics that increases the safety of what data we store in our cache whilst reducing the amount of code written.

And that’s it!

This has been a relatively simple introduction to the advantages of Typescript with generics, and there are many more avenues you can explore with it. Primarily, generics offer us more security with less code and the added benefit of a sanity check before runtime.

--

--