Inspiration

Most people learning to code are already sitting in Discord all day. The tools that teach them are somewhere else entirely — a separate site, a separate login, a separate streak to forget about. And almost every "learn to code" product has you filling in blanks or dragging blocks, then drops you into a real editor where you've never actually typed a semicolon.

We wanted the practice to happen where people already are, and to be made of real code from the first second.

What it does

CodeQuest is a website and a Discord bot that share one account and one XP total.

  • CodeType — a typing test where every snippet is real code. Brackets, indentation, semicolons, all of it. Snippet, timed, and zen modes across Python, JavaScript, Java, C, C++ and more, with live WPM, accuracy, and a personal history of your runs.
  • Learn — courses that start from zero and build to real programs, with quick-fire quizzes to catch what you skipped.
  • Challenges — you write actual code, it gets compiled and executed in a sandbox, and the output is checked against the expected result. Not pattern-matched. Run.
  • In Discord — /quiz, /challenge, /daily, /rank, /leaderboard. Every server gets its own leaderboard, so you're competing with your friends, not with the internet.

Earn XP anywhere, level up everywhere. Answer a question in Discord on your phone and the XP is there when you open the site.

You can also try the whole thing without an account — "Continue as guest" opens the full app immediately, it just doesn't save anything.

How we built it

Python and Flask on the server, SQLite for storage, discord.py for the bot, and hand-written HTML, CSS and JavaScript on the front end with no build step and no framework. Code execution runs on Judge0. Hosted on Railway with a mounted volume for the database.

The bot and the website are two processes sharing one SQLite file, which is what makes "one account" real rather than a syncing feature.

A few decisions we'd make again:

  • The bot requests no privileged intents. It's slash-command only, so Intents.none(). Any admin can add it with zero portal configuration, and it can scale past 100 servers without applying to Discord for intent approval.
  • The server never trusts the client's score. Submitting a CodeType run sends the text you typed, not your WPM — accuracy and speed are recomputed server-side against the snippet you were given. Quiz answers are checked by index against the stored question. Claiming "150 wpm, 100% accuracy" gets you zeros.
  • Three identity states, decided on the server. Visitor, guest, and signed-in are resolved before the page is sent, so the wrong version never paints for a fraction of a second.
  • Guests genuinely aren't stored. A guest gets no database row at all — progress lives in their session and every API response is flagged saved: false so the interface can be honest that it will disappear, instead of implying progress that quietly vanishes.

Challenges we ran into

The typing test scrolled away from you. As you typed, the page jumped and the cursor left the screen — unusable. The cause was that we were reading offsetTop to find the caret, but the element's offsetParent was the document body, so we were getting a page coordinate and treating it as a coordinate inside the scrolling text window. Worse, our first "verification" watched the scroll position climb and called it working — the climbing was the bug. Fixed by measuring with getBoundingClientRect() deltas between the caret and the window, which gives a real distance regardless of what the offset parent is.

Gradient text kept rendering dim on first load. Twice, from two different causes. background-clip: text gradients wash out when the element sits inside a composited opacity layer, and CSS animation-fill-mode keeps applying values after an animation finishes, which held the layer promotion in place. A separate bug had a keyframe block with only a from — so the animation inherited the element's own opacity: 0 as its end value and animated from invisible to invisible.

All the player data was on one volume with no copy. Fixing it properly meant using SQLite's online backup API rather than copying the file, because copying a database that's being written to can capture it mid-transaction and restore as a corrupt file — exactly the failure a backup exists to prevent. Every snapshot is then decompressed, integrity-checked and read from before it counts, and a copy is delivered off the host. We proved it by deleting a live database and restoring it: row counts, leaderboard and XP totals all matched, and the app booted and served users from the restored file.

Changing the domain silently broke sign-in. The OAuth redirect_uri is derived from the request host, so a new URL means a redirect Discord has never seen, and login dies with "invalid redirect_uri" — with no error anywhere on our side. Caught it by testing the login path against the new host rather than assuming a URL change was cosmetic.

Accomplishments that we're proud of

The two halves genuinely are one product. There's no "link your account" step, no sync button — you play in Discord, you open the site, it's the same profile.

We also took the boring parts seriously: CSRF protection on every write, rate limiting, server-side scoring, a privacy policy that lists every field we store and why, and one-click data export and account deletion that actually work. The backup system has been through a real restore drill, not just a "the file exists" check.

And the guest mode is honest. It would have been easy to quietly store guest progress and dangle it as a reason to sign up. Instead nothing is written down, and the interface says so.

What we learned

Verifying a fix is a separate skill from writing one. The scroll bug taught us that watching a number change is not the same as watching the right number change, and the backups taught us that a backup nobody has ever restored is a guess, not a backup.

We also learned how much of "polish" is really about what happens before the first paint. Nearly every visual bug we hit — dim text, the wrong nav control flashing, layout shift — came down to something being decided too late.

What's next for CodeQuest

  • A /codetype command so typing runs work inside Discord, and a typing-speed leaderboard alongside the XP one
  • More courses and languages, and user-submitted challenges
  • A Discord Activity, so CodeType can run inside a voice channel and you can race friends live
  • A custom domain

Built With

Share this project:

Updates

Submission history