White Prompt
EngineeringOct 18, 2024 · 4 min read

Unlocking TypeScript’s Potential with Effect: A Modern Approach to Building Robust Applications

By Marcelo Bairros

Hey, everyone! Today, I want to share some insights into a library that’s been gaining traction in the TypeScript ecosystem — Effect. If you’re like me, always on the lookout for ways to write more resilient and scalable code without jumping to entirely different tech stacks, this is a game-changer.

Effect is designed specifically for TypeScript developers who want to build production-ready applications with maximum type safety and minimal boilerplate. Think of it as a toolkit that lets you manage complex workflows — error handling, concurrency, retries — without the usual mess of try-catch blocks or nested callbacks.

Why Effect Caught My Attention

For the last couple of years I’ve worked with different technologies and learned their strengths and weaknesses. The ones that connect in some way with Effect are:

  • RxJS in the Angular world
  • Goroutines in Golang for backend processes
  • Zod, Prisma and all the nice typed libraries in Typescript

So when I started learning about Effect the concepts and characteristics of these technologies were present in the best way possible.

  • Effects are descriptions of computations, much like RxJS pipelines are descriptions of computations over time.
  • Fibers in Effect represent the execution of an effect, and like goroutines, you can spawn thousands of those concurrently.
  • Effect fully leverages Typescript types, so when handling errors, dependency requirements, exhaustive matching, and data schemas, everything is strongly typed. Same “magical” experience you get when you start using Prisma and your data access layer is fully typed.

A Quick Dive into What Effect Does

When you work with TypeScript, handling errors and managing asynchronous code is an everyday struggle. Traditional approaches using async/await or Promise can get messy, especially when things get more complex. That’s where Effect shines — it offers a functional approach, allowing you to build applications in a declarative manner. Instead of focusing on how things are done step by step, you define what you want to happen, and Effect handles the orchestration.

For example, let’s talk about retries. In a standard TypeScript setup, if you need to retry an API call, you might end up writing several lines to manage state: tracking attempts, handling failures, and retrying with a delay. With Effect, you write something like:

const getTodo = (id: number): Effect.Effect<unknown, HttpClientError> =>httpClient.get(/todos/${id}).pipe(Effect.andThen((response) => response.json),Effect.scoped,Effect.retry({schedule: Schedule.exponential(1000),times: 3}))

💡Example from the Effect website

And that’s it. It’s concise, clean, and most importantly, readable.

Going Beyond Basic Error Handling

I’ve previously worked with various error handling strategies, from simple try-catch blocks to more elaborate solutions like using custom Result types. While they work, they can be verbose and hard to scale when your codebase grows. Effect takes error handling up a notch by making it a core part of the framework. You get type-safe error handling that’s baked into every function, ensuring you don’t miss any edge cases.

Make sure to check the official documentation to understand important concepts and utilities:

  • The Effect type

type Effect<Success, Error, Requirements> = ( context: Context) => Error | Success

Documentation

Observability and Tracing Out of the Box

One thing that gets me excited is Effect’s built-in support for observability. If you’ve ever had to set up distributed tracing or custom logging in TypeScript, you know it’s not a walk in the park. Effect integrates with open telemetry tools, giving you a straightforward way to track what’s happening inside your application.

For instance, adding tracing with an OLTP http exporter can be as simple as:

import { Effect } from "effect"import { NodeSdk } from "@effect/opentelemetry"import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"

const NodeSdkLive = NodeSdk.layer(() => ({resource: { serviceName: "example" },spanProcessor: new BatchSpanProcessor(new OTLPTraceExporter())}))

// Will trace Effects with// Effect.withSpan("Example")

Documentation

And if you are using AWS Lambda and want to avoid having Lambda layers that add time to your lambda’s cold start, you can use export traces to XRay with this utility:

https://github.com/sellooh/effect-xray

It’s clear that Effect isn’t just about writing code; it’s about writing robust code that’s ready for production.

Effect and TypeScript: A Match Made for Developers

What I love about Effect is how it fits naturally into the TypeScript workflow. You don’t need to refactor your entire application to use it — Effect can coexist with your current setup. You can wrap specific parts of your app in Effect functions and still return regular promises. This gradual adoption approach is perfect for teams that want to experiment without fully committing to a new paradigm.

Let’s say you have an Express-based API written in TypeScript, and you want to implement a complex business logic in a specific endpoint. You don’t need to rebuild everything from scratch. Instead, you can introduce Effect where it makes sense and gradually enhance other parts of the application.

The Future of Effect: What’s Next?

The library is still evolving, especially when it comes to its ecosystem and documentation. The core package is stable, but new features like clustering are in alpha. I’m looking forward to what the Effect Cluster will look like. It has the potential to unlock a new level of reliability for Typescript backend applications.

It’s important to point out: Effect is not here to compete with Go or Rust for performance. If you need high-performance for CPU bound problems, you’ll still want to reach for those technologies. Effect’s goal is to enhance TypeScript development, potentially becoming the best tool-set for commercial applications with complex IO bound problems.

Final Thoughts

Effect is a powerful library that’s making TypeScript development more efficient and scalable. If you’re already in the TypeScript world and looking for ways to level up your code’s resilience without jumping to a completely different tech stack, give Effect a try. It’s exciting to see how it evolves, and I’m looking forward to its growth and adoption.

Let’s keep pushing the boundaries of what TypeScript can do — who knows, it might just be the tool we’ve been missing.

kncoa v}aelmfwjirbf valentine roldan ismontella

Share

Ready to Build Something That Lasts?

Let's talk about your project. We'll bring the engineering judgment and the speed to ship.