I built a free chess training web app with React + FastAPI + Stockfish — here's how
A few months ago I started converting my personal chess training desktop app (Python + tkinter) into a full web application. The result is PenguinChess, a free chess training platform with no ads and no account required.
Here's what I built and the technical choices behind it.
What the app does
The site follows the natural logic of a chess game :
- Openings : line-by-line interactive training with the ability to create custom openings
- Tactics : 50k+ puzzles (from Lichess CC0 database) filterable by theme and Elo level, with a competitive mode using a live Elo rating system
- Endgames : theory lessons and practical training against Stockfish at variable Elo
- Analysis : full position analyzer powered by Stockfish 16
Everything is free, no signup required, no paywall.
Tech stack
Frontend : React 19 + Vite, chess.js for move logic, custom SVG pieces rendered inline, i18next for FR/EN internationalization, Recharts for the Elo progression graph.
Backend : Python FastAPI, python-chess for move validation, Stockfish 16.1 binary downloaded at startup on the server.
Database : PostgreSQL via Supabase + SQLAlchemy ORM. I started with SQLite locally and migrated to Supabase for persistent production data.
Auth : JWT tokens with python-jose, bcrypt password hashing, password reset flow via Resend API (SMTP is blocked on Render's free tier — learned that the hard way).
Hosting : Vercel for the frontend, Render for the backend.
Interesting technical challenges
Stockfish on a free server : Render's free tier doesn't persist files between deploys, so Stockfish is downloaded at every cold start (~50MB). It works fine but adds a few seconds of latency on the first request after inactivity.
SMTP blocked on Render : when I implemented the password reset email flow, I discovered that Render's free tier blocks outbound SMTP connections entirely. The fix was switching to Resend's HTTP API instead of smtplib.
Elo rating system for tactics : I implemented a soft Elo formula (K=10) with a placement multiplier (x2 for the first 10 puzzles, x1.5 up to the 20th) to speed up initial calibration. Each puzzle only affects the score once — first mistake counts as a loss, clean solve counts as a win.
Opening mastery with EMA : instead of tracking a fixed window of the last 10 attempts, I use an exponential moving average (alpha=0.2) so that recent performance is weighted more heavily. Mastery threshold is 75%.
What I learned
- React was completely new to me — I came from Python desktop development. The learning curve was steep but the ecosystem is great.
- Free hosting tiers have real constraints (no SMTP, ephemeral filesystem, cold starts) that you only discover in production.
- SQLite is great for local dev but migrating to PostgreSQL mid-project requires careful attention to dialect differences.
Try it / feedback welcome
I'm looking for honest feedback, especially from chess players and developers. What's missing? What would you improve?
Happy to answer any technical questions in the comments!

















