Tools
Tools: Beyond the Bracket: Building a High-Performance Tournament Engine with Go and WebAssembly
2026-02-18
0 views
admin
The "Simple" Problem of Tournaments ## What is Tourney? ## The Architecture: Go Core, WASM Everywhere ## 1. The Go Core (The Brain) ## 2. The WebAssembly Bridge (The Portability) ## 3. The TypeScript Wrapper (The DX) ## Killer Feature: Cross-Draw Progression ## The Built-in Developer UI ## Getting Started ## What's Next? Subtitle: How the need for complex chess tournaments led to creating Tourney—an open-source, cross-platform library for managing competition state. While building my upcoming chess platform, Chesstiny, I hit a deceptively complex engineering hurdle: managing tournaments. On the surface, a tournament seems simple. It’s just a bracket, right? Team A plays Team B, the winner moves on. But when you actually sit down to model this in software, the state management becomes a nightmare. You have to track: Trying to manage this state with ad-hoc database queries and spaghetti code is a recipe for disaster. I needed a robust, dedicated engine to handle the business logic of competition. I wanted something fast enough for a serverless backend, but portable enough to run entirely in the client's browser for a snappy user experience. Existing solutions didn't fit the bill. So, I engineered my own. Tourney is an open-source tournament management library designed to handle the entire lifecycle of a competition structure. It models the hierarchy of tournaments, draws (rounds), matches, games, players, and scores with a strict, clean type system. It is not a front-end bracket visualization library (though it pairs well with them). It is the logic engine underneath that decides who plays whom next based on the results you feed it. What makes Tourney unique is its hybrid architecture, designed for modern full-stack development. To get the performance and reliability I needed for Chesstiny, I made a specific architectural choice: Write once in Go, run anywhere via WebAssembly. The heavy lifting—the complex state transitions, the progression logic, leaderboard sorting—is all written in native Go. Go is perfect for this: it’s strongly typed, performant, and excellent at modeling complex data structures. If you are a Go developer, you can import the core package directly into your backend services. This is where it gets interesting. To make this logic available to frontend JavaScript/TypeScript developers (and Node.js serverless environments), the Go core is compiled into a WebAssembly (WASM) binary. Nobody wants to manually invoke exported WASM functions in their daily workflow. The tourney npm package provides an idiomatic TypeScript wrapper around the WASM binary. It handles the initialization of the WASM runtime and provides fully typed classes and methods. As a consumer of the library, you never feel like you are interacting with a binary; it just feels like a standard, robust TypeScript library. The hardest part of tournament logic is handling progression in multi-stage events (like knockouts). How do you define a match in the semi-finals before the quarter-finals have been played? You don't know who the players are yet. Tourney solves this with Position References. Instead of assigning a specific user ID to a match slot, you can assign a reference string like "1-match_abc123". This translates to: "The player who finished 1st (the winner) in match abc123." When you call the progress() method on the tournament, the engine—running deep inside the WASM binary—scans previous results, resolves these references to actual user IDs, and updates the tournament state automatically. Designing complex tournament structures via code can be mentally taxing. You often need to visualize the tree to understand if your progression logic is sound. To solve this developer experience hurdle, the npm package ships with a built-in visual tool. By running a simple command, you launch a local server that provides a drag-and-drop interface (built with React Flow) to design, test, and inspect your tournament structures visually. You can build out your bracket visually, verify the progression paths, and then export the resulting JSON structure to use in your actual application. Try it out in your JavaScript/TypeScript project: Here is the "Hello World" of creating a tournament and adding a match: Tourney was born out of necessity for Chesstiny, and it will continue to evolve as that platform grows. I’m excited to open-source it today so other developers building esports platforms, sports apps, or community gaming tools don't have to reinvent this particular wheel. I’d love for you to give it a spin and let me know what you think. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK:
// It feels like standard TS, but it's running Go under the hood.
import { TournamentWasm } from "@olaoluwanhs/tourney"; const tournament = TournamentWasm.create("Chess Championship 2026", "scheduled");
tournament.addDraw(4); // Add a round with 4 matches Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
// It feels like standard TS, but it's running Go under the hood.
import { TournamentWasm } from "@olaoluwanhs/tourney"; const tournament = TournamentWasm.create("Chess Championship 2026", "scheduled");
tournament.addDraw(4); // Add a round with 4 matches CODE_BLOCK:
// It feels like standard TS, but it's running Go under the hood.
import { TournamentWasm } from "@olaoluwanhs/tourney"; const tournament = TournamentWasm.create("Chess Championship 2026", "scheduled");
tournament.addDraw(4); // Add a round with 4 matches CODE_BLOCK:
npx @olaoluwanhs/tourney launch-ui Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
npx @olaoluwanhs/tourney launch-ui CODE_BLOCK:
npx @olaoluwanhs/tourney launch-ui COMMAND_BLOCK:
npm install @olaoluwanhs/tourney Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
npm install @olaoluwanhs/tourney COMMAND_BLOCK:
npm install @olaoluwanhs/tourney CODE_BLOCK:
import { TournamentWasm } from "@olaoluwanhs/tourney"; async function run() { // 1. Initialize WASM (only needed once if using progression features) // Ensure tournament.wasm and wasm_exec.js are in your public assets folder await TournamentWasm.initWasm({ wasmUrl: "/tournament.wasm", wasmExecUrl: "/wasm_exec.js" }); // 2. Create the tournament container const t = TournamentWasm.create("My First Tourney", "scheduled"); // 3. Add a draw (round) expecting 2 matches t.addDraw(2); const drawId = t.draws[0].id; // 4. Add a specific match to that draw expecting 2 players t.addMatchToDraw(drawId, 2); const gameId = t.draws[0].matches[0].game.id; // 5. Add real players t.addPlayerToGame(gameId, { kind: "user", id: "p1", name: "Alice" }); t.addPlayerToGame(gameId, { kind: "user", id: "p2", name: "Bob" }); console.log(t.tournamentObject);
} run(); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { TournamentWasm } from "@olaoluwanhs/tourney"; async function run() { // 1. Initialize WASM (only needed once if using progression features) // Ensure tournament.wasm and wasm_exec.js are in your public assets folder await TournamentWasm.initWasm({ wasmUrl: "/tournament.wasm", wasmExecUrl: "/wasm_exec.js" }); // 2. Create the tournament container const t = TournamentWasm.create("My First Tourney", "scheduled"); // 3. Add a draw (round) expecting 2 matches t.addDraw(2); const drawId = t.draws[0].id; // 4. Add a specific match to that draw expecting 2 players t.addMatchToDraw(drawId, 2); const gameId = t.draws[0].matches[0].game.id; // 5. Add real players t.addPlayerToGame(gameId, { kind: "user", id: "p1", name: "Alice" }); t.addPlayerToGame(gameId, { kind: "user", id: "p2", name: "Bob" }); console.log(t.tournamentObject);
} run(); CODE_BLOCK:
import { TournamentWasm } from "@olaoluwanhs/tourney"; async function run() { // 1. Initialize WASM (only needed once if using progression features) // Ensure tournament.wasm and wasm_exec.js are in your public assets folder await TournamentWasm.initWasm({ wasmUrl: "/tournament.wasm", wasmExecUrl: "/wasm_exec.js" }); // 2. Create the tournament container const t = TournamentWasm.create("My First Tourney", "scheduled"); // 3. Add a draw (round) expecting 2 matches t.addDraw(2); const drawId = t.draws[0].id; // 4. Add a specific match to that draw expecting 2 players t.addMatchToDraw(drawId, 2); const gameId = t.draws[0].matches[0].game.id; // 5. Add real players t.addPlayerToGame(gameId, { kind: "user", id: "p1", name: "Alice" }); t.addPlayerToGame(gameId, { kind: "user", id: "p2", name: "Bob" }); console.log(t.tournamentObject);
} run(); - Different stages (groups vs. knockouts).
- Dynamic progression (the player in "Round 2, Match 3, Slot 1" isn't known until "Round 1, Match 5" finishes).
- Scoring rules, tie-breakers, and leaderboards.
- The status of every individual game (scheduled, ongoing, settled). - 📦 NPM: @olaoluwanhs/tourney
- 💻 GitHub: olaoluwanhs/Tourney (Star s are greatly appreciated!)
- ♟️ Chesstiny: Coming soon!
how-totutorialguidedev.toaiservernodejavascriptdatabasegitgithub