I started RonnyBank as a simple idea: a command-line Python program that simulated what a bank does — sign up, log in, deposit, withdraw, take out a loan, see your statement. The kind of project that's common as a beginner exercise, and a genuinely good one, because a bank touches so many fundamentals at once: data validation, password hashing, file storage, conditional logic, and a fair bit of arithmetic.

What I didn't fully appreciate going in was how different that command-line version would feel once I decided to turn it into something people could actually visit in a browser. This article is the honest version of that process — not a polished tutorial, but the real sequence of confusion, fixes, and small "oh, that's why" moments that got RonnyBank from a script running in a terminal to a live web app at ronnybank.onrender.com.

Starting point: a working CLI program isn't a web app

The original Mybank.py worked well as a terminal program. It had clean functions for signing up, logging in, handling deposits and withdrawals, applying interest to loans, and an admin panel — all built around a JSON file that persisted account data between runs. Genuinely solid logic.

The mistake I might have made, if I hadn't stopped to think about it, would have been assuming that logic could just be "wrapped" in a website. It can't — not directly. A terminal program waits for you to type something and prints a response. A website needs to respond to many different visitors, each clicking different buttons, at different times, often simultaneously. That's a fundamentally different shape of problem, and it's why the move to Flask wasn't just a change of file extension from .py to .html — it meant rebuilding the same business logic around a completely different model: routes, requests, and sessions.

The banking logic — interest rates, minimum balances, PIN verification — barely changed. Almost everything else did.

The first real wall: "Import flask could not be resolved"

Before any of the interesting backend logic mattered, the very first hurdle was just getting Flask recognized by VS Code at all. The fix turned out to be straightforward — installing Flask, and ideally inside a Python virtual environment (a .venv folder) rather than globally — but getting there involved a few false starts: a popup asking whether to create a virtual environment, a terminal that hadn't picked up the new environment yet, and one command copy-pasted from a suggestion that referenced a `.venv` path that didn't exist yet.

None of this was really a "bug." It was just the ordinary friction of environment setup that almost everyone hits the first time they run a Python web framework on a new machine. The lesson that stuck: when a virtual environment is involved, always check the terminal prompt itself — if it doesn't show (.venv) at the start, nothing you install will go where you expect it to.

The folder structure rule that breaks everything if you miss it

This was the single most instructive moment of the whole build. After Flask installed cleanly and the server actually started — Running on http://127.0.0.1:5000 — visiting the site produced:

jinja2.exceptions.TemplateNotFound: index.html

Flask has an unforgiving, non-negotiable rule: HTML templates must live inside a folder named exactly templates, and static assets (CSS, JS, images) inside a folder named exactly static. It doesn't matter how good the Python code is — if index.html is sitting in the project root instead of inside templates/, Flask will not find it, full stop.

Once every .html file moved into templates/ and the CSS/JS moved into static/, that error disappeared immediately. It's a small rule, but it's the kind of thing no amount of clever code can work around — you either follow Flask's expected structure, or nothing renders.

The same lesson, twice

Almost the identical problem showed up again moments later — the homepage rendered, but with zero styling, just plain black text on white. The HTML was correctly using Flask's url_for('static', filename='css/style.css') helper, but the actual file was sitting directly inside static/, not inside a static/css/ subfolder like the code expected. Same root cause as before: a mismatch between where Flask is told to look, and where the file actually is. Once the subfolders matched the code, the navy-and-gold design appeared instantly.

// Takeaway

Most of the early "bugs" in a Flask project aren't logic errors at all — they're file-location mismatches. Before debugging code, check that every path referenced in HTML (templates/, static/css/, static/js/) actually matches the real folder structure on disk.

Making it feel like a real product, not just a working demo

Once the core banking features were functioning — sign up, log in, deposit, withdraw, transfer between accounts, apply for a loan with automatic interest calculation, view a statement, and an admin panel to freeze or unfreeze accounts — the next layer was making it feel intentional rather than just "technically working."

That meant generating a proper favicon (a gold rounded square with a navy ₿ symbol, matching the site's own colour palette) instead of leaving the browser tab with a generic blank icon, and then going a step further by turning the site into an installable Progressive Web App. Adding a manifest.json file describing the app's name and icons, plus a small service-worker.js script that caches key files, meant the browser could offer a genuine "Install RonnyBank" option — the same mechanism that lets a web app behave like a native one, with its own icon and its own window, without ever touching an app store.

Going from "works on my laptop" to "works for anyone, anywhere"

This is the step that's easy to underestimate. A Flask app running via python app.py on a personal machine is only reachable at 127.0.0.1 — literally "this same computer." Nobody else, not even someone on the same WiFi network, can visit that address. Making RonnyBank genuinely public required:

  • Removing hardcoded secrets — the Flask secret_key (which encrypts session cookies) needed to come from an environment variable rather than being written directly into the code, since that code would soon live publicly on GitHub.
  • Turning off debug mode in production — Flask's debug mode is wonderful while developing locally (it shows detailed error pages and auto-reloads), but it would leak internal code details to any visitor of a live site if left on.
  • Swapping the development server for a production one — Flask's built-in server explicitly warns it isn't meant for real traffic. The fix was adding Gunicorn, a production-grade WSGI server, alongside a Procfile telling the host exactly how to start the app.
  • Version control and a clean .gitignore — making sure the virtual environment folder and the local JSON "database" file never got pushed to GitHub, since one is enormous and unnecessary, and the other contains test users' data that has no business being public.

With those four pieces in place, deploying to Render — connecting the GitHub repository, setting the build and start commands, adding the secret key as an environment variable — produced a real, public URL within minutes: ronnybank.onrender.com.

An honest limitation worth naming

RonnyBank currently stores data in a JSON file rather than a real database, and on a free hosting tier, that file resets whenever the server restarts or a new version is deployed. That's a deliberate trade-off for a learning project, not a hidden flaw — the right long-term fix is a persistent database like PostgreSQL, and that's a natural next iteration rather than something the project pretends not to need.

It's also worth being explicit about what RonnyBank is and isn't: it's a demonstration of full-stack patterns — authentication, sessions, business logic, deployment — not a licensed financial product, and it was never meant to be.

What actually mattered, in the end

Looking back, the technically "hardest" parts — interest calculations, transfer logic between two accounts at once, PIN verification — were the parts that gave the least trouble, because they were just careful, ordinary Python. The genuinely instructive parts were the unglamorous ones: understanding why Flask demands a specific folder structure, why a secret key shouldn't live in committed code, and why a development server explicitly tells you not to trust it with real traffic.

That's probably the real takeaway for anyone earlier in this same process: a working feature and a deployable, secure, properly structured application are two different milestones. Getting from one to the other is where most of the actual learning happens.

Curious to see it running?

RonnyBank is live and fully functional — sign up, explore the dashboard, try a transfer.

Visit RonnyBank → Get In Touch