Basic Banking System
A small PHP and MySQL banking app: a money-transfer form with balance checks and a paginated transaction history.
- ROLE
- Developer
- PERIOD
- 2021
- DOMAIN
- Student work
- CODE
- GitHub ↗
OVERVIEW
A small banking app in procedural PHP and MySQL. A form moves money between two accounts after checking the amount, both account numbers, and a 1,000 minimum balance, and a history page lists every transfer, newest first. There is no login: the project is about forms, server-side validation, and SQL writes.
ARRIVED AS
“A minimal banking flow for learning: move money between accounts and keep a record of every transfer.”
One of my first complete web applications, built in February 2021 as a task for The Sparks Foundation, which the app's footer credits. The scope is deliberately small: a home page, a form that transfers money between two accounts, and a history of every transfer. It was a way to learn the request-response cycle and how a page reads and writes a MySQL database.
WHAT I BUILT
- 01A transfer form (debit account, beneficiary account, amount) that validates the input, checks the balance, and moves the money.
- 02A transaction history page that lists every transfer, newest first, 100 per page.
WHAT CHANGED
- A first pass at forms, server-side validation, and SQL writes against a real schema.
ARCHITECTURE
Four procedural PHP files over one MySQL database, with no framework:
- index.php: the home page, linking to the transfer form and the history.
- transfermoney.php: renders the transfer form and, on POST, runs the transfer.
- transactionhistory.php: lists transfers, newest first, 100 per page.
- database.php: opens the mysqli connection the other pages share.
The schema has an ACCOUNTS table (account number and balance) and a TRANSACTIONS table (debit account, beneficiary account, amount, time added).
Data flow
- The user fills in the transfer form and submits it back to transfermoney.php
- The amount must not be zero, the debit account must exist, and the sender must keep at least 1,000 after the transfer
- A row goes into TRANSACTIONS; if the beneficiary account does not exist, the insert fails and the page says so
- Two UPDATE queries debit the sender and credit the beneficiary
- The page shows success, or the error next to the field that failed
COMPONENTS
Transfer formTakes a debit account number, a beneficiary account number, and an amount, and marks the field that was wrong when a transfer fails.
Transfer logicRejects a zero amount or an unknown account, refuses a transfer that would leave the sender below a 1,000 minimum balance, then records the transfer and updates both balances.
Transaction historyReads TRANSACTIONS newest first, 100 rows per page, and shows the account number on each side of every transfer.
Decisions, with the cost of each.
A decision without its trade-off is marketing. Each row says what was chosen, why, and what it gave up.
Procedural PHP over a framework
Wanted to understand the fundamentals before using abstractions like Laravel. This helped me appreciate what frameworks do under the hood.
AT THE COST OF
Each page mixes HTML, form handling, and SQL; nothing is shared between pages except the database connection.
Hand-written SQL instead of an ORM
Needed to learn SQL itself: an insert that looks up account ids with sub-selects, the balance updates, and a paginated history query.
AT THE COST OF
The queries are built by concatenating form input into SQL rather than using prepared statements, and the insert and the two balance updates run without a database transaction.
Minimal frontend styling
Focus was on the server side. Bootstrap's stylesheet and a few inline styles make it usable, not polished.
AT THE COST OF
No client-side validation: every check happens on the server, after the form is submitted.
LEARNED
Validation belongs on the server
Every check (zero amount, unknown account, minimum balance) runs in PHP after the form posts, so a request built by hand cannot skip it.
Security fundamentals
Concatenating form input into SQL, as this code does, leaves it open to SQL injection. Prepared statements are the fix.
Why atomicity matters for money
The transfer is three separate queries: an insert and two updates. If one fails partway, the balances and the history disagree. Wrapping them in one database transaction is what keeps them in step.