Get AI summaries of any video or article — Sign up free
Your Next Backend Should Be Written In... thumbnail

Your Next Backend Should Be Written In...

The PrimeTime·
5 min read

Based on The PrimeTime's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

The transcript criticizes backend over-engineering—especially microservices—when the product’s actual complexity doesn’t justify it.

Briefing

Backend work often gets overbuilt: once an API needs caching, background jobs, and external calls, teams start stacking libraries and splitting into microservices—sometimes without matching the actual complexity of the product. The core pitch here is that Gleam (running on the Erlang VM) can deliver a simpler, more reliable path for “real” backend requirements—especially long-running services—without turning error handling and concurrency into a tangle.

The discussion starts with language preferences and tradeoffs. Go, Rust, Zig, Elixir, Python, and TypeScript all come up, but the focus narrows to what matters when building an API that must cache external data and run background processes on a schedule. Microservices are treated skeptically: having more services than users is framed as a common Twitter trope, and the practical advice is to build the application first and choose architecture proportional to the problem.

A major thread is error-handling philosophy. One side favors “errors as values” and effect systems (with TypeScript’s “wrap everything” approach criticized as all-or-nothing). The counterpoint is that “cannot crash” is unrealistic and that crashing can be an intentional signal when core invariants are violated. Performance concerns also appear: effect-style wrapping can add overhead and garbage-collection pressure, which matters for long-running services.

Gleam is then presented as the alternative that balances static typing with pragmatic concurrency. A demo API simulates Pokémon battles using the Wisp web framework with two endpoints: one fetches Pokémon data (including base stats and moves) and another simulates a battle between two Pokémon. The implementation emphasizes Gleam’s static type system, the built-in Result type for explicit control flow, and decoding JSON via Dynamic plus custom decoders—so validation happens at decode time rather than later.

Concurrency is where the pitch becomes concrete. On the Erlang VM, Gleam tasks run in isolated processes, enabling parallel API calls—for example, fetching additional move details concurrently and then combining results. The demo uses task timeouts and error aggregation to keep the system responsive. For shared state like caches, it avoids race conditions by using actors: long-running processes that hold state and process messages sequentially. Instead of reaching for a full caching “universe” or external systems like Redis, the example builds an in-memory cache using an actor-based dictionary keyed by strings, then wires those caches into request handling.

Finally, the demo includes a separate long-running “battle manager” task that continuously computes battle outcomes for combinations of cached Pokémon. That part is explicitly framed as a toy approach because the number of combinations grows quickly, but it illustrates how Gleam can run background work alongside the API. Overall, the takeaway is that Gleam’s type safety, Result-based error handling, and Erlang-style concurrency (tasks + actors) can make backend APIs more maintainable without the usual complexity spiral.

Cornell Notes

The transcript argues that backend complexity often balloons when caching, external API calls, and background jobs get added—leading to over-engineering like microservices and heavy effect systems. Gleam is presented as a practical alternative because it combines static typing with explicit error handling via the Result type and supports safe concurrency on the Erlang VM. A Pokémon API demo shows how to fetch and cache data, decode JSON using Dynamic plus custom decoders, and parallelize move lookups using tasks with timeouts. Shared state (caches) is handled with actors, which process messages sequentially to avoid race conditions. The approach aims to keep control flow visible and reduce architectural bloat while still supporting long-running background work.

Why does adding caching and background processing make backend stacks feel more complex than CRUD alone?

Once an API must cache external data, it needs a strategy for reads/writes and invalidation. If it also runs background jobs—like periodic refreshes or continuous computation—it introduces additional moving parts: a background processing library, concurrency concerns, and shared state management. The transcript frames this as the moment teams start stacking tools (and sometimes microservices) even when the product doesn’t justify it.

How does Gleam’s Result-based style keep error handling explicit in the Pokémon endpoint?

The demo’s route logic checks the cache first; if the Pokémon isn’t present, it calls the external Poke API. Using Gleam’s Result pattern, errors are handled immediately by only executing the success path when the call succeeds. It repeats the same pattern for fetching moves, then updates the cache and returns a success response; otherwise it returns an error response with a message. The key claim is that control flow stays visible rather than hidden behind implicit exceptions.

What role does Dynamic play in decoding JSON from the Poke API?

Because Gleam is statically typed and doesn’t rely on macros/reflection, the demo decodes JSON into a Dynamic type when the exact shape isn’t known upfront. Custom decoders then validate and transform that Dynamic data into strongly typed Gleam structures. The transcript highlights an example where Pokémon stats arrive in an adjacent array format, so decoders reshape it into a single object with fields for the six stats.

How do tasks help with concurrency in the demo?

Tasks are used to parallelize move enrichment. After retrieving a Pokémon’s move list, the code maps over moves and spawns a task per move using the Gleam OTP library. Each task performs an API call to fetch additional move details. The demo then awaits each task with a Timeout, bubbles up results as needed, and aggregates successes and failures (using a partition-style approach) so the handler can return either all moves or the first error.

Why are actors used for caching instead of sharing a mutable structure across concurrent code?

Actors hold state inside a long-running process and receive messages that are processed one at a time in FIFO order. That design prevents race conditions because the underlying memory isn’t shared across processes. In the demo, the cache actor stores a dictionary keyed by strings and supports message types like set, get, and get keys; get operations return a Result containing either the cached value or nil when missing. This keeps caching safe without external state stores for the example.

Review Questions

  1. What kinds of backend requirements (beyond basic CRUD) tend to trigger over-engineering, and how does the transcript suggest avoiding that spiral?
  2. In the Pokémon API example, how do tasks and actors differ in their approach to concurrency and state?
  3. How does decoding JSON with Dynamic plus custom decoders change when validation happens compared with decoding directly into final types?

Key Points

  1. 1

    The transcript criticizes backend over-engineering—especially microservices—when the product’s actual complexity doesn’t justify it.

  2. 2

    Effect-style “wrap everything” error handling is portrayed as both ergonomically heavy and potentially costly for long-running services.

  3. 3

    Gleam’s Result-based control flow is used to keep success and failure paths explicit and readable in API routes.

  4. 4

    Custom JSON decoding in Gleam uses Dynamic plus decoders so validation and transformation happen at decode time.

  5. 5

    Tasks provide parallelism by running work in isolated processes, with timeouts and result aggregation for robustness.

  6. 6

    Actors provide race-condition-free shared state by processing messages sequentially while holding cache data internally.

  7. 7

    A long-running background “manager” task can compute continuously, but the demo warns that combinatorial growth makes naive approaches unsuitable for production.

Highlights

The caching solution avoids Redis for the demo by using actors on the Erlang VM: a single-process cache that handles get/set messages sequentially.
Parallel move lookups are implemented with tasks per move, then combined with timeout-aware awaiting and error aggregation.
JSON decoding is handled via Dynamic plus custom decoders, letting the code validate and reshape Poke API responses into strongly typed Gleam structures.
The transcript frames “cannot crash” as unrealistic, arguing that crashing can be an intentional signal when invariants are violated.

Topics

  • Backend Architecture
  • Gleam
  • Erlang VM Concurrency
  • Error Handling
  • JSON Decoding
  • Caching Actors
  • Tasks and Timeouts