The Good, The Bad, and The Ugly of TypeScript

Is TypeScript really worth it?

Over the last few months, I’ve been working in a couple of Node.js projects written in TypeScript, and it was a bumpy road to follow with ups and downs. What I aim to answer here in this post is: in the end, is it worthy to use TypeScript?

The Good

Let’s start with the upsides!

TypeScript is a superset of JavaScript, which means that if you know how to code in JavaScript, especially ECMAScript 6 or 7, you’ll have no problem whatsoever coding in TypeScript! The most significant difference is the addition of types, which brings static code analysis to the table and improves code readability and maintenance.

TypeScript also brings everything good that comes with newer versions of ECMAScript, including class syntax and arrow functions. It also adds lots of Object-Oriented Programming features, like interfaces, generics, and method access modifiers, without losing the feeling that you are coding in JavaScript.

Look at the code below. It shows how TS can be organized, using classes, types, access modifiers, and yet still be JavaScript-ish:

class Server {
  public app: express.Application;
  public config: Config;
  public static bootstrap(): Server {
    return new Server();
  }
  constructor() {
    this.app = express();
    this.setup();
    this.routes();
  }
  setup(){
    this.app.use(morgan(‘combined’));
  }
  routes(){
    this.app.use(MyRoutes);
  }
  start(){
    const server = this.app.listen(3030, () => {
      const address = server.address();
      logger.info(`Server running at: ${address.address}:${address.port}`);
    });
    return server;
  }
}

With TypeScript, it is easier to debug and find out what is going on with your application. If you make good use of types, you will probably know whether something is wrong before even running your app!

Speaking of types, since TS 2.0 we have the @types package , all made and standardized by DefinitelyTyped. Most of the time, you will have no problem regarding types from third-party JS modules! Look at how easy it is:

npm install @types/angular — save-dev
npm install @types/mocha — save-dev

And that’s it! Just import the desired types package as you would normally do with any other NPM package, and TS will be in charge of making the types available to your application.

If you used TS a while ago, you’ve probably been aware of how chaotic the Typings module used to be, with no standards at all. Using its own CLI application was necessary to install any type package:

npm install typings — global
typings install dt~mocha — global — save
typings install npm~bluebird — global — save

The Bad

As expected, not everything went great down the road.

The first major problem of TS isn’t even its own problem: most JS modules and frameworks have no documentation regarding TypeScript usage, even though it will probably work perfectly after installing the right @types package. Sure, sometimes the API documentation is enough, but very often you will need more info about types, decorators, and interfaces of the module.

Another problem that may occur, as you may have noticed above, is that sometimes the @types package of a framework will not work as expected. In TS, when this happens, you have the option of casting variables into the any type, which is pretty much the wildcard of TS. But that’s far from a good workaround, since the whole reason to use TS is to use the right types, so that you can predict exactly what a method should receive and return. However, the any type states that, well, anything can fit into a variable:

(<any>ExternalModule).method() // ALWAYS avoid this

The Ugly

Some things aren’t necessarily bad, but can be annoying.

If you are a functional programming fella like me, chaining curried methods with promises can be problematic sometimes. That happens because some frameworks make use of native ES Promises, while others use third-party promise libraries such as Bluebird. Even though you would have no problem chaining them in JS, TypeScript sees them as different types and will refuse to even transpile, therefore throwing a TypeError.

Another annoying problem is that, when not done right, generics do not infer the type. If you are not familiarized with generics, they are basic types that get instantiated at a later time, when needed. But in TS, sometimes you have to explicit declare the type again, e.g.:

User.fetchAll<User>()
 .then((result) => {

 });

Overall

Given everything I just said, you’re probably wondering “So, should I stay away from TS?”. Well, actually no! Sure, there is a lot to be done in it, and TS is only a child in the world of programming languages, but it’s growing steadily. In bigger projects, its static analysis and Object-Oriented Programming syntax may result in more mature code and future-proof structure.

Let me show you what exactly this means by comparing two pieces of code, one in JS and another in TS:

class Person {

  constructor(name, age){
    this.name = name;
    this.age = age;
  }

  advanceAge(){
    this.age++;
  }
}

function doBirthday(citizen){
  citizen.advanceAge();
  console.log(`Happy ${citize.age} years!`);
}

const leonardo = new Person('Leonardo', 21);
doBirthday(leonardo);
leonardo.age = 21; // Note that this is possible, there is no way to make a attribute private on JS
leonardo.age = 'twenty'; // This is possible as well, there is no type check
class Person {

  private name: string;
  private age: number;

  constructor(name: string, age: number){
    this.name = name;
    this.age = age;
  }

  advanceAge(){
    this.age++;
  }

  getAge(){
    return this.age;
  }

  passALotOftime(years: number){
    this.age += years;
  }
}

function doBirthday(citizen: Person){
  citizen.advanceAge();
  console.log(`Happy ${citize.getAge()} years!`);
}

const leonardo = new Person('Leonardo', 21); // As you can see, I don't have to explicity declare the type, because TS knows that is the Person type
doBirthday(leonardo);
leonardo.age = 21; // Note that this is not possible, because age is a private attribute
leonardo.passALotOftime('twenty'); // Neither is this, because this method expects a number, not a string

The above TypeScript code would throw errors in its static analyzer and show you what’s wrong before even running it! However, this is a very simple example. Imagine you have to modify code that is way more complex, and also not yours. Which version would you prefer to work with?

If you are wondering, here are some of the companies using TypeScript: https://github.com/trending/typescript

I’m very skeptical about TypeScript replacing JavaScript in the future, but there is no denial of its promising road. Even though it will be bumpy sometimes.