Your Next Backend Should Be Written In...
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.
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?
How does Gleam’s Result-based style keep error handling explicit in the Pokémon endpoint?
What role does Dynamic play in decoding JSON from the Poke API?
How do tasks help with concurrency in the demo?
Why are actors used for caching instead of sharing a mutable structure across concurrent code?
Review Questions
- What kinds of backend requirements (beyond basic CRUD) tend to trigger over-engineering, and how does the transcript suggest avoiding that spiral?
- In the Pokémon API example, how do tasks and actors differ in their approach to concurrency and state?
- How does decoding JSON with Dynamic plus custom decoders change when validation happens compared with decoding directly into final types?
Key Points
- 1
The transcript criticizes backend over-engineering—especially microservices—when the product’s actual complexity doesn’t justify it.
- 2
Effect-style “wrap everything” error handling is portrayed as both ergonomically heavy and potentially costly for long-running services.
- 3
Gleam’s Result-based control flow is used to keep success and failure paths explicit and readable in API routes.
- 4
Custom JSON decoding in Gleam uses Dynamic plus decoders so validation and transformation happen at decode time.
- 5
Tasks provide parallelism by running work in isolated processes, with timeouts and result aggregation for robustness.
- 6
Actors provide race-condition-free shared state by processing messages sequentially while holding cache data internally.
- 7
A long-running background “manager” task can compute continuously, but the demo warns that combinatorial growth makes naive approaches unsuitable for production.