Theseus also went back from (callback) hell
At Hunteed we recently rewrote our entire api codebase, migrating from a standard node-express api to a brand-new typescript app. This was quite a challenge, and it took quite some time so the choice of doing so was not obvious to make at the time.
Now that we’re done with it, we think the benefits of doing so are tremendous. Here’s why.
We started with a node-express javascript app, originally bootstraped with a popular yeoman build.
The code was es6-written, and some perplexing babel config was somehow making things work, though I wasn’t so sure why and how. We also ignored the big red flag saying that babel should not be used in production (but was it really?).
The app code coverage was good. However, testing new features required a lot of boilerplate and intricated callbacks before you could actually write a test. This was a real trouble, as the temptation to write untested code only would only grow.
I’ve come to think that using a lot of mongoose hooks made the code a bit difficult to understand. First it looked nice and efficient, but at some point it was not obvious to see what happened when you called a method. Things could be more explicit.
A lot of callbacks. Callbacks everywhere. It did not felt like such an issue at the time, as we got quite accustomed to use them. You’ll see how we changed our minds about that :)
Typing your code adds a lot of robustness to your project. This has been discussed a lot elsewhere, but some main advantages could still be mentioned here:
f(foo, bar)
instead of f(bar, foo
is now impossible. Typescript checks the input/outputs so you won’t commit such a mistake.
Note that [1, 2, 3, 4].map(x => add(x, 1))
will produce an array of numbers [2, 3, 4, 5]
. The compiler is able to figure it out by itself.
One of the typescript features I like the most is the ES7-like async/await
. Although this is currently an ES7 proposal, typescript already supports it, and it knows how to transpile await
to ES6 code (by taking advantage of the ES6 iterators, but you don’t have to worry about that as a dev).
[edit]: since typescript 2.1, you can transpile [_await_](http://react-etc.net/entry/async-await-support-for-es5-browsers-pushed-to-typescript-2-1)
to es2015 \o/
This post covers the details about async/await
. Long story short, as long as a function is declared as async,
it will return a Promise. Moreover, it will allow to use await,
wich tells the compiler to wait for a promise to resolve, without blocking the thread. For instance,
This is non blocking, although it looks like regular synchronous code we all dream of
This allowed us to change some code that looked like
our old, tedious, cb syntax
to the now fancy
new await syntax
ConclusionGetting rid of the tedious callback/callback error handling process allowed us a reduction of 25% of the codebase !
Want to “promisify” a function of yours ? easy as pie:
We used to have a lot of callbacks/promises in our tests. As a result, it became more and more difficult to even properly initialize a test.
Having a testing environment that is tedious to work with is a catastrophic thing. At some point, someone will skip testing, because it is too much of a hassle.
With the awesome supertest-as-promised lib and async
functions, testing a route has now become quite elegant:
Look ma, no callbacks !
Same as usual, except that you should transpile your code before uploading it. If you choose to use await
, be aware that you need to transpile to at least ES6. So you will need to run a recent enough version of nodejs. Most of the modern tools do (like heroku, modulus…), and it should not be an issue for backend-api deployment.
At Hunteed we open-sourced a build (checkout this story for more details) that you can use. The workflow can be summarized as:
/dist
dist
directory is transpiled and tested, it is simply a good old ES6 node-express app, that you can safely send to prod. Obviously this is a CI-server work. You could, but you won’t do that from your computer.In my opinion, getting to code in typescript takes less than a day for an experienced javascript developer. Changing your function (user) {...}
to function (user: User) {...}
is already enough to benefit from the ts compiler awesomeness. If you feel brave enough, then you can turn on the noImplicitAny
compiler options, that will ensure that no variable is left undeclared
It was not so obvious, however, to understand how to import typings from external libraries. With typescript 2 it’s more easy, since most of the time you simply have to install @types/something
from npm registery in order to make things work.
All things considered, though, it was very fast and it took less than a week before we noticed how fast and secure it feels to code with typescript.
Benefits we observed about working in a typescript environment:
await/async
made me change my mind about javascript. We now have something that looks that a good programming language, with concurrency support.Many thanks to everyone who are responsible for this typescript awesomeness. You guys rule.
Feel free to share some feedback about your ts transition if you have some !
Related links: