DEV Community

loading...
Cover image for Why Zum moved from JavaScript to TypeScript

Why Zum moved from JavaScript to TypeScript

amormysh profile image Andrew Mormysh ・5 min read

Good chances you are reading this because you use JavaScript on your current project. The story below is about why we decided to move to TypeScript and what it took for our NodeJS backend project.

Zum is a great example of a typical Silicon Valley startup. The company began with a bold vision and created a working product within a very short time and raised $5.5M Series A from Sequoia in 2017, then $19M in 2018, and $40M in 2019. All this time, execution speed was an integral part of the force pushing the company to new heights. We realized early that building new features co-exists with the constant improvement of existing code.

In the last year, we have made significant changes to what we build and how we build new features. This included a complete overhaul of our platform infrastructure. We will be writing about our experiences and how our platform has evolved in a series of posts in our blog.

Zum backend lines of code split JS vs. TS

At the beginning (end of 2018), our project was a single npm package written in ES5 syntax. The project structure looked similar to most NodeJS applications: API routers, controllers with business logic, models with database logic.

A typical NodeJS backend project structure

As the codebase grew, and so did the team, we noticed 2 things:

  1. We started to face runtime bugs more and more often - be it an undefined variable or a missing import
  2. Development speed decreased since we started to be afraid of modifying the existing code

A typical error message on an iOS app if "something went wrong"

Why did it happen? Writing the initial first code is simple. An engineer usually tests it works perfectly as they develop a feature. However, the next person who changes the same file later (by adding other features or modifying an existing one) can't be relied on to re-test all the affected code in the file. Unit tests do not completely solve this issue either. We effectively may end up leaving it up to a real user to stumble upon a bug at the runtime.

An example of a simple typo and a missing import

This is where we decided to move from pure JavaScript to TypeScript - a strict syntactical superset of JavaScript which adds optional static typing.

What did we get out of using static typing? Startups are all about execution and, most important - fast execution. Hence, all improvements should be directly connected to the value for the business right away.

Catching most of the trivial errors at the time of packaging a project without a need to have a 100% unit-test coverage - is one of the immediate benefits. Our code doesn't break in runtime just because of a simple typo in a function name.
Maybe an even bigger advantage of the types is moving to the mode where things have a name. Speed of development increased dramatically, especially for the features which required changes in the existing code. Why? It's much faster to understand the function when you only need to read the typed interface. Even better, when the interface is defined with reusable types from the modules you are already familiar with.

"…the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …[Therefore,] making it easy to read makes it easier to write."

- Robert C. Martin, "Clean Code: A Handbook of Agile Software Craftsmanship"

Things move faster also because we are not afraid to change the code anymore. Types make sure code still "compiles" after the change, and with IDEs like WebStorm - refactoring and renaming are automated. Even things like intelligent code completion - may not directly speed up the development, but as engineers, we enjoy it, which brings down the fatigue of writing boilerplate. Eventually, we write more and better since we focus on the logic, not the spelling of variables and methods.

How did we do it? First, our team spent around 3 full days reading cover-to-cover the TypeScript Handbook. It was a bit faster for those who came from typed languages like Java and C#, although it required some mental re-mapping of a few concepts since things are not identical (e.g., duck typing).

Second, no more addition of JavaScript code was allowed. All new code had to be TypeScript only. This rule did not always hold, obviously, but we tried to stick to it as much as possible.

As noted before, initially, all the code was in a single package (let's call it "legacy"). For all new code we started to create separate npm packages in pure TypeScript and import them to the "legacy" package. This became possible by moving to a multi-package monorepo with Yarn Workspaces (follow our blog to stay tuned for a separate post on this topic).

If the new code did not deserve a separate package - it was created in the "legacy" one, but as a new TS file (even if it's just a single function). Sometimes it's not possible due to circular dependency between files. The last resort is renaming from .js to .ts, which comes with a price - you either have to fix all type errors immediately or set up separate, more forgiving, compilation rules of such files (more on it here and here).

It allowed us to migrate gradually and avoid full code refactoring. This way, types are defined for the functionality which is actually used/current - no need to spend effort on a dead or rarely used code.

Key takeaways:

  1. We develop features faster and ship them more often (deploying almost every day vs. once a week in the past)
  2. Engineers are happy to use new technologies and work with a more readable code
  3. Backend (NodeJS) and Frontend (Angular 11) codebases align well, allowing us to build common libraries
  4. Users and the support team are happy, too - much fewer silly bugs in production

Interested in working with us? Please ping me at andrewm@ridezum.com

Discussion (1)

pic
Editor guide