June 25, 2021

This article is part of Programming TypeScript architecture series.

Programming utilities with TypeScript

In this guide I’ll introduce an utility concept and how to implement it with TypeScript.

This pattern can be used for any type of software – eg. frontend or backend, or even inside Nginx’s NJS or PostgreSQL’s plv8.

Code Example

In it’s core it’s simply public functions in a namespace which share a common context.

export class FooUtils {

  public static strip (value: string) : string {
    return value.replace(/ +/, "");
  }

}

export default FooUtils;

…and use it:

import FooUtils from './FooUtils.ts';

FooUtils.strip('a b c');

What’s an utility class?

What makes a class utility class is the fact that it has no internal state except the values you provide to it as arguments.

Eg. it shouldn’t use any other internal state which may change the result of the function. Hence, constants are fine to use, but global variables are not.

The second important part of the architecture is the context. You should place your functions based on a common context. Then you’ll know where to look when you need it next time – and so does your IDE. It also makes it easier to find a place to put your next new function.

But why not use import/export?

This would be of course the standard EcmaScript way. It’s also completely fine to do so. Just be consistent, unless there’s a better reason not to be.

In this case you would just create a file fooUtils.ts like:

export function strip (value: string) : string {
  return value.replace(/ +/, "");
}

…and use it simply:

import fooUtils from './fooUtils.ts';

fooUtils.strip('a b c');

There are actually some benefits like the fact a correctly implemented ES import system can include only parts which you imported and let others uncompiled. However, with a correctly configured minifizing build system it should happen for unused parts of classes also.

The most likely problem with this style is that it relies on files to group functions together. The static class style can be compiled as a single file and debugged without loosing the context of grouped functions.

But TypeScript already has namespaces!

Yes, TypeScript has an actual namespace concept, which you’re of course free to utilize in the same manner, but I find a class much simpler concept for this particular use case.

Especially I like the class style because of private, protected and public methods and generally much wider support – eg. namespaces are not supported directly in ES systems like NodeJS and other Google v8 -based systems (at the time when this article was written).

There is also this article about avoiding namespaces, since it may be unlikely namespaces are introduced to the standard while there’s already a concept to implement them. It’s of course just speculation. You’re always free to go with actual namespaces. Just be consistent.

Why not implement directly inside the actual Foo class?

This is a good question. In the end it’s just an architectural design pattern, which probably looks useless in a small and minimal application.

However, for larger projects with multiple people working on the same code, these small architectural choices are much more important, and may even have an impact on how the project will succeed and how often [git] conflicts are made.

So, what is the point of an utility class? In the end, like most architectural choices, it’s just a way to divide different parts of the code into smaller files.

Let me explain with some examples from (almost) real world applications.

Let’s assume you’re developing a frontend application that uses a backend. You get the specification which kind of JSON the backend uses – you didn’t design it, it was made by another team or even another company.

So in the end these architectural choices are just decoupling different parts of the software into different blocks in order to let different developers easily maintain and work on the same source code separately.

It also reduces duplicate source code lines in the application, which may reduce bugs, since the fix will not be forgot to apply to a duplicated code somewhere else in the app.