Chess

Play Stockfish in your browser, over a REST API

Live on this site Play in the browser, on the board or in the terminal client, or over the API.

A Go server that exposes chess as a REST API, with Stockfish behind it for move validation and computer play. The server owns the rules; clients own nothing but the display, which is why three very different ones talk to the same endpoints.

Games are anonymous by default. An account is optional: it records the game against you, keeps it past the 24 hours an anonymous game is kept, and stops anyone else from moving, taking back moves, or resigning for your side.

Modes
Human vs computer on the board; human vs human and computer vs computer through the terminal client or the API.
Endings
Checkmate, stalemate, resignation, draw by agreement, and automatic draws: dead position, threefold repetition, fifty-move rule.
Engine
Stockfish, skill level 0–20, per-move search budget 100–10000 ms.
Positions
Any legal FEN — puzzles, endgames, mid-game starts.
Export
Move list and current FEN to the clipboard from the board; full PGN with SAN, headers and how the game ended from the terminal client or the API.
Clients
Web board, terminal CLI, and the same CLI compiled to WebAssembly.

You are on the onion mirror, which serves static pages only. This needs the live API, so it only works at https://lixen.com.

Click a piece, then its square. A piece picker handles promotion; undo, draw offers and resignation sit beside the board, with live server and storage indicators and a move list that copies to the clipboard. Against the computer you can undo past a resignation and play on; the resignation stays on record.

You are on the onion mirror, which serves static pages only. This needs the live API, so it only works at https://lixen.com.

The terminal client is the Go CLI from the repository, compiled to WebAssembly and drawn by xterm.js. It opens in its own tab rather than in a frame here: the binary is around 10 MB, and there is no reason to spend that on a visitor who only came for the board.

Start
help for the command list, then new and answer the prompts: player type for each side, engine level and search time for a computer side, an optional starting FEN.
Two players
Sign in, share the game ID, join it from a second client, and poll for the opponent's moves, draw offers and resignation.
Shared state
Same API as the board — a game started in one shows up in the other.
Native build
The same client runs as a normal terminal program against any instance; the WebAssembly build only swaps the I/O layer.
Requires
A modern browser with JavaScript and WebAssembly. WebGL2 draws the terminal when available; otherwise xterm.js falls back to its slower DOM renderer.

Base URL on this site: https://lixen.com/chess/api. JSON in and out, with Content-Type: application/json on every POST and PUT. Where auth is optional, send Authorization: Bearer <token> to act as your account.

EndpointMethodAuthNotes
/auth/registerPOST—Username, password, optional email; returns a token. 5/min per IP.
/auth/loginPOST—Username or email, and password. 10/min per IP.
/auth/meGETrequiredThe signed-in user.
/auth/logoutPOSTrequiredRevokes the session on the server.
/auth/meDELETErequiredDeletes your account; send the password again.
/gamesPOSToptionalPlayer type, engine level and search time per colour; optional starting FEN.
/games/{id}GET—Game state. ?wait=true&moveCount=N long-polls up to 30 s.
/games/{id}/movesPOSToptionalA UCI move (e2e4, e7e8q), or cccc for the engine’s move.
/games/{id}/undoPOSToptionalTakes back count plies, default 1.
/games/{id}/resignPOSToptionalResigns; the side is inferred when only one is yours.
/games/{id}/drawPOSToptionaloffer, accept or decline; the computer answers from its evaluation.
/games/{id}/playersPUToptionalReconfigures the players mid-game.
/games/{id}/boardGET—The position as an ASCII board.
/games/{id}/historyGET—Durable replay: every ply with UCI, SAN and the resulting FEN.
/games/{id}/pgnGET—PGN of the game, or up to ?ply=N.
/games/{id}DELETEoptionalUnloads the live game; its stored history remains.
/users/me/gamesGETrequiredYour stored games, newest first; cursor-paged, filter by status and colour.

Health is outside the API prefix: GET https://lixen.com/chess/health reports server and storage status and the running build.

General rate limit is 10 requests a second per IP. A side claimed by an account acts only for that account, so two players cannot take back each other’s moves, and a resignation or agreed draw between them is final. The full reference is doc/api.md.

Transport

Fiber over fasthttp. Routing, rate limits, content-type checks and JWT middleware live here; handlers only translate HTTP to a command and back.

Processing

One Execute(Command) entry point holds the game logic, so the transport is swappable. Engine moves go to an async queue and the request returns at once.

Long-polling

A wait registry holds each client’s request for up to 30 seconds and releases it on the next move, so clients learn of a move when it happens, not on a timer.

Engine pool

Two Stockfish workers fed from a queue, plus a separate instance for legality checks, so a long search never blocks move validation.

Persistence

PostgreSQL 18 through pgx. Game writes drain in order through one writer and never stall a move; account writes commit before they report success.

Accounts

Argon2id hashes, seven-day HS256 tokens bound to a server-side session, so logout revokes. Case-insensitive names; separate login and signup rate limits.

Runtime

  • Server: Go, Fiber, PostgreSQL 18 (pgx; SQLite until v0.12), Stockfish over UCI
  • Rules core: dependency-free Go move generator for SAN, PGN, draw rules and replay verification
  • Web client: vanilla JavaScript and CSS, no framework, no build step
  • Terminal client: Go → WebAssembly, xterm.js with the WebGL renderer
  • Platforms: FreeBSD and Linux; the clients in any modern browser
  • Toolchain: Go 1.27, static CGO-free binaries; one instance enforced by a PID lock file
  • License: BSD-3-Clause

Build

Clone
git clone https://github.com/lixenwraith/chess --depth 1
Build
cd chess && make server
Run
bin/chess-server -dsn 'dbname=chess' (Stockfish on PATH; without -dsn games live in memory only)
Deploy
Scripts for a FreeBSD jail (how it runs here) and for Linux with systemd are in deploy/.