, 10 tweets, 3 min read Read on Twitter
I just got the first big TypeScript port payoff. All API communication is statically checked on both the server and the client. I tested it by renaming ~100 identifiers at once. 3,914 lines of total diff. Once I got it to typecheck, it worked the first time. It took ~30 minutes.
The idea of doing a huge identifier switch like this in Ruby fills me with fear. Without the huge effort of getting to 100% test coverage, it would cause so many bugs in obscure corners of the system. OTOH, the function I wrote to define checked server routes is 34 lines long.
(You can substitute JavaScript or any other dynamic language in place of Ruby in this tweet if you like; there's no difference in this case.)
I was worried that I was missing something huge and obvious – "why aren't people doing this?!" But I wasn't missing anything. If you're using TypeScript without static checking of API communication on both sides of the app then you're wasting much of its value.
Statically typed API communication is what made me finally do this port. The client was already in TypeScript; the server worked fine in Ruby. But keeping the two sides of the API in sync took so much effort. I had to write a lot of my own tools, but now I'm here and it's good.
Yes, I'm sharing type definitions between the backend and frontend. The server also does runtime validation of incoming API data. That validation and the static types come from a single type definition. And I have static checking that those runtime checks match the static types.
(How it works: the definition of the API is done via io-ts. Static types are extracted from io-ts validators with the usual `t.TypeOf<typeof validator>`. When you define a route, you mention the validator. The static API types are inferred for free from that validator.)
This adds minimal API definition overhead. Here's the smallest handler:

post("/api/feedback.json", requests.feedback,
requireUser(
async ({user, input}) => {
await mailer.sendFeedbackEmail(user.email, input.userComments)
return {}
}
)
)
requests.feedback is the io-ts validator. requireUser pulls the user out of the session and redirects if they're not logged in. It wraps the handler because it adds the user key to that `{user, input}` object that you see the handler taking. Zero types named explicitly.
All of this has zero cost in terms of runtime speed or bundle size. The client-side bundle is 121 bytes smaller than when I started (because many of those identifier renames that I did were switching underscore_case to camelCase). All of the types are erased at runtime.
Missing some Tweet in this thread?
You can try to force a refresh.

Like this thread? Get email updates or save it to PDF!

Subscribe to Gary Bernhardt
Profile picture

Get real-time email alerts when new unrolls are available from this author!

This content may be removed anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Follow Us on Twitter!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just three indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3.00/month or $30.00/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!