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!





Top comments (1)
penguinchess.vercel.app/