The future of Typescript in Production-Grade Applications (2024)

Martin Persson

Posted on

The future of Typescript in Production-Grade Applications (3) The future of Typescript in Production-Grade Applications (4) The future of Typescript in Production-Grade Applications (5) The future of Typescript in Production-Grade Applications (6) The future of Typescript in Production-Grade Applications (7)

#typescript #webdev #productivity #javascript

Javascript is everywhere and will not be going away anytime soon. I love Javascript, it's the first language I learned, and I use it every day, having done so for the past 7 years. But I think we can all agree that the language has many flaws, especially with error handling, callback hell, type coercion and many more. Alot of improvments have been made over time tho.

Typecript was introduced as a superset of Javascript, aimed at addressing many of Javascript's flaws through the introduction of static typing. These days, pure Javascript applications are becoming increasingly rare. The vast majority of new projects opt for Typescript for its robust typing system that improves code reliability and maintainability.

Popular frameworks like Next.js strongly advocate for Typescript, further solidifying its position as the preferred choice for modern web development. This shift towards Typescript is a positive move towards a future where code is more understandable, easier to refactor, and less prone to runtime errors.

We often like to think that Typescript solves all the problems inherent to Javascript, but unfortunately, that's not entirely true.

How many times do we not write an API call like this? I've done it thousands of times, and it seems to work, right?

async function getTodo(id: number): Promise<{ todo: Todo}> { const response = await fetch(`/todos/${id}`) const todo = await response.json() return { todo }}

So, what's the problem here? At first glance, it looks straightforward and effective. However, several things could go wrong and as developers we need to take care of that. For error handling we can use try-catch.

async function getTodo( id: number): Promise< | { ok: true; todo: any } | { ok: false; error: "InvalidJson" | "RequestFailed" }> { try { const response = await fetch(`/todos/${id}`) if (!response.ok) throw new Error("Not OK!") try { const todo = await response.json() return { ok: true, todo } } catch (jsonError) { return { ok: false, error: "InvalidJson" } } } catch (error) { return { ok: false, error: "RequestFailed" } }} 

This is better but we had to add more then 10 lines of code for it and the nested try-catches can be hard to read.

But for a production-grade app, we might still need more features, like retries in case something fails. That would look something like this:

function getTodo( id: number { retries = 3, retryBaseDelay = 1000, }: { retries?: number; retryBaseDelay?: number },): Promise< | { ok: true; todo: any } | { ok: false; error: "InvalidJson" | "RequestFailed" }> { async function execute( attempt: number ): Promise< | { ok: true; todo: any } | { ok: false; error: "InvalidJson" | "RequestFailed" } > { try { const response = await fetch(`/todos/${id}`) if (!response.ok) throw new Error("Not OK!") try { const todo = await response.json() return { ok: true, todo } } catch (jsonError) { if (attempt < retries) { throw jsonError // jump to retry } return { ok: false, error: "InvalidJson" } } } catch (error) { if (attempt < retries) { const delayMs = retryBaseDelay * 2 ** attempt return new Promise((resolve) => setTimeout( () => resolve(execute(attempt + 1)), delayMs, ), ) } return { ok: false, error: "RequestFailed" } } } return execute(0)} 

The code is getting out of hand but we are still not done. Often we must introduce a mechanism for interruption to manage and clean up resources effectively. We can use the AbortSignal API for that

function getTodo( id: number { retries = 3, retryBaseDelay = 1000, signal, }: { retries?: number retryBaseDelay?: number signal?: AbortSignal },): Promise< | { ok: true; todo: any } | { ok: false error: "InvalidJson" | "RequestFailed" | "Timeout" }> { async function execute(attempt: number): Promise< | { ok: true; todo: any } | { ok: false error: "InvalidJson" | "RequestFailed" | "Timeout" } > { try { const controller = new AbortController() setTimeout(() => controller.abort(), 1000) signal?.addEventListener("abort", () => controller.abort(), ) const response = await fetch(`/todos/${id}`, { signal: controller.signal, }) if (!response.ok) throw new Error("Not OK!") try { const todo = await response.json() return { ok: true, todo } } catch (jsonError) { if (attempt < retries) { throw jsonError // jump to retry } return { ok: false, error: "InvalidJson" } } } catch (error) { if ((error as Error).name === "AbortError") { return { ok: false, error: "Timeout" } } else if (attempt < retries) { const delayMs = retryBaseDelay * 2 ** attempt return new Promise((resolve) => setTimeout( () => resolve(execute(attempt + 1)), delayMs, ), ) } return { ok: false, error: "RequestFailed" } } } return execute(0)}

Now our API is robustly designed, having addressed nearly all the typical issues. However, we find ourselves dealing with over 60 lines of code and the solution lacks modularity. This means we'd need to replicate this for each of our API routes, which is hardly efficient or scalable.

Additionally, another aspect worth considering is the incorporation of a tracer.

const tracer = Otel.trace.getTracer("todos")function getTodo( id: number { retries = 3, retryBaseDelay = 1000, signal, }: { retries?: number retryBaseDelay?: number signal?: AbortSignal },): Promise< | { ok: true; todo: any } | { ok: false error: "InvalidJson" | "RequestFailed" | "Timeout" }> { return tracer.startActiveSpan( "getTodo", { attributes: { id } }, async (span) => { try { const result = await execute(0) if (result.ok) { span.setStatus({ code: Otel.SpanStatusCode.OK }) } else { span.setStatus({ code: Otel.SpanStatusCode.ERROR, message: result.error, }) } return result } finally { span.end() } }, ) async function execute(attempt: number): Promise< | { ok: true; todo: any } | { ok: false error: "InvalidJson" | "RequestFailed" | "Timeout" } > { try { const controller = new AbortController() setTimeout(() => controller.abort(), 1000) signal?.addEventListener("abort", () => controller.abort(), ) const response = await fetch(`/todos/${id}`, { signal: controller.signal, }) if (!response.ok) throw new Error("Not OK!") try { const todo = await response.json() return { ok: true, todo } } catch (jsonError) { if (attempt < retries) { throw jsonError // jump to retry } return { ok: false, error: "InvalidJson" } } } catch (error) { if ((error as Error).name === "AbortError") { return { ok: false, error: "Timeout" } } else if (attempt < retries) { const delayMs = retryBaseDelay * 2 ** attempt return new Promise((resolve) => setTimeout( () => resolve(execute(attempt + 1)), delayMs, ), ) } return { ok: false, error: "RequestFailed" } } }} 

Now all of a sudden we have gone from from 6 lines of code to about 80 lines...

To be honest, I rarely go to these lengths when creating an API endpoint and that's largely due to how cumbersome it is.

Producing production-grade TypeScript is hard.

Challenges include:

  • No type-safety beyond the happy path
  • Error handling
  • Retrying on error
  • Interruptions
  • Observability
  • Schema validation

The last point, schema validation, isn't even covered in our code example, but we'd often turn to tools like Zod for this purpose.

What TypeScript lacks, that other languages offer, is a comprehensive standard library. Instead, we have NPM, which is great because we can find almost everything, but that comes with its own set of problems as well.

So, what should we do then?

Effect is designed to address the issues inherent in Typescript and to solve the challenges developers face when using it.

It does so using:

  • Powerful building blocks: Each component of the Effect ecosystem is crafted to be composable.
  • No more one-off deps:: With Effect, we have all we need, eliminating the necessity to depend on numerous npm packages. It provides tools for data validation and HTTP frameworks, among others.
  • No more try-catch:: Effect takes a comprehensive approach to error handling, ensuring all errors are type-safe and not just the happy path.

The 2022 State of JS survey highlighted a strong desire among developers for a standard library.

Effect can be utilized wherever Typescript is applicable, positioning itself as the missing standard library for Typescript. However, there's a catch:

  • Learning curve: Effect heavily relies on functional programming concepts, which can be intimidating for many developers.
  • Different programming style: The programming style of Effect is quite distinct from traditional Typescript, potentially making it challenging to adapt to.
  • Extensivve API: Aiming to be a standard library for Typescript, Effect boasts an API for nearly every situation, which can be overwhelming. Yet, mastering just a few of the most common ones can go a long way.

So, how would our production-grade code example look like in Effect?

const getTodo = (id: number): Effect.Effect< unknown, HttpClientError | TimeoutException> => Http.request.get(`/todos/${id}`).pipe( Http.client.fetchOk(), Http.response.json, Effect.timeout("1 seconds"), Effect.retry( Schedule.exponential(1000).pipe( Schedule.compose(Schedule.recurs(3)), ), ), Effect.withSpan("getTodo", { attributes: { id } }), ) 

This code is significantly shorter and more modular. Admittedly, it might be challenging to grasp what's happening if you're not familiar with the syntax, but it illustrates the power and potential of adopting Effect for Typescript development.

In this post we did not go into how to use or what effect really is. We talked about the problem it solves.

What does the future of Typescript look like, in your opinion?

This post is heavily inspired by the insightful talk from Johannes Schickling the founder of Primsa. You can watch his talk here

The effect website. Be sure to read the docs

Join the Discord: https://discord.gg/effect-ts

Learning resources:

The future of Typescript in Production-Grade Applications (2024)
Top Articles
5 Most Secure Cloud Storage Picks in 2023: Best Secure File Storage
What to do if your online order never arrives
Craigslist Houses For Rent In Denver Colorado
Lamb Funeral Home Obituaries Columbus Ga
Maria Dolores Franziska Kolowrat Krakowská
Words From Cactusi
Here's how eating according to your blood type could help you keep healthy
New Day Usa Blonde Spokeswoman 2022
Craigslist Phoenix Cars By Owner Only
Pvschools Infinite Campus
Nonne's Italian Restaurant And Sports Bar Port Orange Photos
Missing 2023 Showtimes Near Landmark Cinemas Peoria
Magic Mike's Last Dance Showtimes Near Marcus Cedar Creek Cinema
7543460065
Bcbs Prefix List Phone Numbers
Bitlife Tyrone's
New Stores Coming To Canton Ohio 2022
Images of CGC-graded Comic Books Now Available Using the CGC Certification Verification Tool
Red Devil 9664D Snowblower Manual
Vrachtwagens in Nederland kopen - gebruikt en nieuw - TrucksNL
Welcome to GradeBook
Cvs El Salido
Dallas Mavericks 110-120 Golden State Warriors: Thompson leads Warriors to Finals, summary score, stats, highlights | Game 5 Western Conference Finals
Hood County Buy Sell And Trade
Gina Wilson Angle Addition Postulate
Chamberlain College of Nursing | Tuition & Acceptance Rates 2024
Manuela Qm Only
Criterion Dryer Review
The Eight of Cups Tarot Card Meaning - The Ultimate Guide
This Is How We Roll (Remix) - Florida Georgia Line, Jason Derulo, Luke Bryan - NhacCuaTui
Everything You Need to Know About Ñ in Spanish | FluentU Spanish Blog
About | Swan Medical Group
Cars And Trucks Facebook
Unlock The Secrets Of "Skip The Game" Greensboro North Carolina
School Tool / School Tool Parent Portal
Craigslist Lakeside Az
Greater Keene Men's Softball
Jail View Sumter
10 Rarest and Most Valuable Milk Glass Pieces: Value Guide
Tripadvisor Vancouver Restaurants
Subdomain Finder
My Gsu Portal
Tropical Smoothie Address
Christie Ileto Wedding
6463896344
R Detroit Lions
Diamond Desires Nyc
Wieting Funeral Home '' Obituaries
Kobe Express Bayside Lakes Photos
Dumb Money Showtimes Near Regal Stonecrest At Piper Glen
Latest Posts
Article information

Author: Fredrick Kertzmann

Last Updated:

Views: 6068

Rating: 4.6 / 5 (66 voted)

Reviews: 81% of readers found this page helpful

Author information

Name: Fredrick Kertzmann

Birthday: 2000-04-29

Address: Apt. 203 613 Huels Gateway, Ralphtown, LA 40204

Phone: +2135150832870

Job: Regional Design Producer

Hobby: Nordic skating, Lacemaking, Mountain biking, Rowing, Gardening, Water sports, role-playing games

Introduction: My name is Fredrick Kertzmann, I am a gleaming, encouraging, inexpensive, thankful, tender, quaint, precious person who loves writing and wants to share my knowledge and understanding with you.