Brett Owers
← All Projects

Tenlach TypeScript Backend

Archived

April 15, 2024

The original Tenlach backend in TypeScript — built before learning Go, because TypeScript was the language I knew. There were aspects I did not like, and the real problem was that I did not understand Nakama well enough to leverage it properly.

Purpose

First backend for Tenlach, written in TypeScript because Go was unknown territory at the time. Used Nakama's TypeScript runtime for game logic, but the experience was frustrating — not because Nakama was bad, but because I did not understand its architecture deeply enough to use it effectively.

Stack

TypeScriptNode.jsNakamaGame ServerWebSocket

What I Learned

  • Nakama supports runtime code in Go, TypeScript, and Lua. The TypeScript runtime runs in a JavaScript VM embedded in the Go-based Nakama server. This means your TypeScript game logic runs inside the server process, with access to Nakama's APIs for matchmaking, storage, leaderboards, and real-time messaging. The mistake: treating Nakama as a dumb WebSocket relay instead of using its built-in match handler system, which manages game sessions as first-class objects with lifecycle hooks (matchInit, matchJoinAttempt, matchLoop, matchTerminate).
  • The aspects I did not like were symptoms of misunderstanding, not real limitations. Match handlers in Nakama expect you to define a tick rate and a matchLoop function that runs every tick. Game state is maintained in the match object. I was trying to manage state outside of Nakama's match system — fighting the framework instead of using it. The equivalent of using React and managing DOM directly instead of using state.
  • Switching from TypeScript to Go for the Tenlach backend was driven by the belief that Go would be faster and more suitable for game servers. In retrospect, the language was not the problem. Understanding the framework was. A deeper investment in Nakama's TypeScript runtime would have produced the same result with less context-switching cost.
  • This is the "new language + new domain = two unknowns" lesson from the Go boilerplate entry, experienced from the other side. TypeScript + Nakama was one unknown (Nakama). Go + Nakama was two unknowns (Go AND Nakama). The TypeScript backend was actually the simpler path — I just did not realize it at the time.

Key Insights

  • When a framework frustrates you, the first diagnosis should be "do I understand it?" not "is it bad?" Most framework frustration is misalignment between your mental model and the framework's mental model. Nakama expects match-handler-based game sessions. I expected a raw WebSocket server. The frustration was the gap between those two models, not a deficiency in Nakama.
  • The Tenlach backend has now been attempted in three languages: TypeScript, Go, and conceptually in Dart/Flutter. Each attempt taught something, but the recurring failure was not the language — it was the game design and networking complexity. The backend stack is a solved problem. The game on top of it is the unsolved one.
#TypeScript#Nakama#game-server#Tenlach#WebSocket#real-time#multiplayer#framework-misunderstanding

This post was composed through a conversation between Brett Owers and Claude Code (Anthropic). The content reflects Brett's recollection of each project and the lessons drawn from it. Some details may be approximate or omitted — the purpose is to paint an honest picture of a software engineer's development over time, not to serve as a precise historical record.