Frontend/Backend Separated Calculator System — Assignment Blog
Table of Contents
- Frontend/Backend Separated Calculator System — Assignment Blog
- 1. Course Information
- 2. Git Repository Link and Code Standards Link
- 3. PSP Table
- 4. Presentation of the Finished Product
- 4.1 Main Interface
- 4.2 Addition
- 4.3 Subtraction
- 4.4 Multiplication
- 4.5 Division
- 4.6 Decimal Arithmetic
- 4.7 Operator Precedence
- 4.8 Parentheses
- 4.9 Unary Minus
- 4.10 Division by Zero
- 4.11 Invalid Expression
- 4.12 History Records
- 4.13 Persistence
- 4.14 Delete a Single Record
- 4.15 Clear All
- 4.16 Keyboard Input (Extended Feature)
- 5. Design and Implementation Process
- 5.1 Requirements Analysis
- 5.2 Overall System Architecture
- 5.3 Front-End Design
- 5.4 Back-End Design
- 5.5 API Design
- 5.6 Database Design
- 5.7 Expression Evaluation Algorithm Design
- 5.8 Exception Handling
- 5.9 Frontend/Backend Interaction Process
- 5.10 Deployment Process
- 6. Function Structure Diagram
- 7. Code Explanation (Key Code + Design Rationale)
- 7.1 Backend: Expression Parsing (calc.py)
- 7.2 Backend: REST Interface (app.py)
- 7.3 Backend: History CRUD (app.py)
- 7.4 Frontend: Unified Input for Keyboard and Buttons (app.js)
- 7.5 Frontend: History Records (Proof of Persistence)
- 8. Personal Journey and Learnings
- 9. Extended Features (Extra Credit)
- 10. Deployment / Access Information
- Backend (public address)
- Frontend (public address)
- How to test
1. Course Information
| Course for This Assignment | Web Development Technology(Web 开发技术) |
|---|---|
| Assignment Requirements | Implement a calculator system with a frontend/backend separated architecture; the backend must do all calculation and persist history in a database; deploy the project and submit the blog, the two GitHub repositories and a publicly accessible address. |
| Objectives of This Assignment | Understand the frontend/backend separation architecture, REST API design, database persistence, expression parsing, deployment, and write a complete assignment blog. |
| Other References | Assignment notice: https://bbs.csdn.net/topics/620530837 |
| Item | Content |
|---|---|
| Student | 王少杰 |
| Student ID | 832401219 |
| Platform | Windows + Python 3.14 + Flask + SQLite + HTML/CSS/JS |
| Date | 2026-09-24 |
2. Git Repository Link and Code Standards Link
| Item | Link |
|---|---|
| Frontend repository | https://github.com/060725/calculator_frontend |
| Frontend code standard | https://github.com/060725/calculator_frontend/blob/main/codestyle.md |
| Backend repository | https://github.com/060725/calculator_backend |
| Backend code standard | https://github.com/060725/calculator_backend/blob/main/codestyle.md |
3. PSP Table
| Phase | Estimated (hours) | Actual (hours) |
|---|---|---|
| Requirements analysis | 0.5 | 0.5 |
| System design (architecture / API / database) | 0.5 | 0.5 |
| Backend: expression parsing & calculation module | 1.5 | 2.0 |
| Backend: calculation history & database module | 0.5 | 0.5 |
| Frontend: UI, interaction, keyboard shortcuts | 2.0 | 2.0 |
| Frontend/backend integration & testing | 1.0 | 1.0 |
| Deployment (PythonAnywhere + GitHub Pages) | 0.5 | 0.5 |
| Blog writing | 1.0 | 1.5 |
| Total | 7.5 | 8.5 |
4. Presentation of the Finished Product
4.1 Main Interface
The page shows a dark-themed calculator: the button area is on the left and the history panel is on the right.
4.2 Addition
Click
12+8=in sequence; the result shows20.
4.3 Subtraction
After clearing, enter
15-7=; the result shows8.
4.4 Multiplication
After clearing, enter
6×7=; the result shows42.
4.5 Division
After clearing, enter
20÷4=; the result shows5.
4.6 Decimal Arithmetic
After clearing, enter
3.14+2.86=; the result shows6.
4.7 Operator Precedence
After clearing, enter
1+2×3=; the result shows7instead of9, proving that multiplication/division have higher precedence than addition/subtraction.
4.8 Parentheses
After clearing, enter
(1+2)×3=; the result shows9, and parentheses have the correct precedence.
4.9 Unary Minus
After clearing, enter
3×±2=; the result shows-6. You can also type3*-2with the keyboard.
4.10 Division by Zero
After clearing, enter
5÷0=; the UI shows a red message: “除数不能为零” (Division by zero is not allowed).
4.11 Invalid Expression
After clearing, type only
+and press=; the UI shows “表达式无效” (Invalid expression).
4.12 History Records
After several calculations, the right panel lists the expressions, results and timestamps in reverse chronological order.
4.13 Persistence
Press
F5to refresh the page; the history records still exist, which proves the data is persisted in the backend database.
4.14 Delete a Single Record
Click the
×at the top-right of a record; that record is removed from both the list and the database.
4.15 Clear All
Click “清空全部” (Clear All) at the top of the panel and confirm; all records are removed.
4.16 Keyboard Input (Extended Feature)
Type
1+2*3directly with the keyboard and pressEnter; it computes7correctly.
Full keyboard support (digits,+ - * /,( ),Enter,Backspace,Escape) is an
extended featurebeyond the basic requirements (see Section 9).
5. Design and Implementation Process
5.1 Requirements Analysis
The assignment requires afrontend/backend separatedcalculator system:
- The frontend provides a graphical interface (dark theme) and supports both mouse clicks and keyboard input;
- The backend provides an expression-evaluation API and persists history records in a database (SQLite);
- History records support deleting a single record and clearing all; records must survive page refresh;
- It must support the four basic operations, decimals, parentheses with precedence, and unary plus/minus;
- Invalid expressions (e.g., division by zero, a bare operator) must produce friendly error messages;
- The project must be deployed to a publicly accessible address so that the teaching assistant can verify it.
5.2 Overall System Architecture
┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │ Browser (Frontend) │ │ Backend Service │ │ calculator_frontend │ HTTP │ calculator_backend │ │ │ ──────► │ │ │ index.html / style.css │ JSON │ app.py REST API │ │ app.js (Fetch calls the API) │ ◄────── │ calc.py Expression parser │ │ · Calculator buttons / keys │ │ SQLite History persistence │ │ · History panel / error msg │ │ (calculator.db) │ └─────────────────────────────────┘ └─────────────────────────────────┘The frontend and backend communicate through a REST API with JSON. The frontend never
touches the database directly and never computes the result itself — the calculation is
always done on the backend. This is the essence of “frontend/backend separation”.
A simple way to verify this: if the backend service is stopped, the frontend can still
accept input but can no longer obtain any new valid calculation result.
5.3 Front-End Design
- Single-page UI: left panel is the calculator button grid, right panel is the history list.
- Every button carries a
data-keyattribute; clicks and keyboard events share one input channel. - The display area has three lines: the input expression, the result, and a red error message.
- After a successful calculation the frontend re-queries the history API to refresh the panel.
5.4 Back-End Design
- Flask app exposing a small REST API (calculation + history CRUD).
- A hand-writtenrecursive descent parser(
calc.py) — noeval/execis used,
which satisfies the assignment’s security requirement. - CORS is enabled so an independently hosted frontend can call the API cross-origin.
- SQLite for persistence; a
wsgi.pyentry is provided for production deployment.
5.5 API Design
| Method | Path | Request Body / Params | Response |
|---|---|---|---|
| POST | /api/calculate | {"expression":"1+2×3"} | 201:{id, expression, result, created_at} |
| GET | /api/history | — | {items:[{id, expression, result, created_at}]} |
| DELETE | /api/history/<id> | path param | {ok:true} |
| DELETE | /api/history | — | {ok:true, deleted:n}(optional “clear all”) |
- On success:
201with the result, and the record is written to history; - On failure (division by zero, invalid expression, etc.):
400with a Chinese message in theerrorfield.
5.6 Database Design
SQLite database filecalculator.dbwith a single tablehistory:
| Field | Type | Description |
|---|---|---|
| id | INTEGER PRIMARY KEY AUTOINCREMENT | Primary key |
| expression | TEXT NOT NULL | The expression evaluated |
| result | TEXT NOT NULL | The evaluation result |
| created_at | TEXT NOT NULL | Record timeYYYY-MM-DD HH:MM:SS |
The table is created automatically on first startup (init_db()), so no manual
database initialization is required. History is queried withORDER BY id DESC LIMIT 100,
so the records remain visible after a page refresh.
5.7 Expression Evaluation Algorithm Design
The backend uses arecursive descent parser(calc.py):
expr := term (('+' | '-') term)* term := factor (('*' | '/' | '×' | '÷') factor)* factor := ('+' | '-') factor | '(' expr ')' | number- The grammar naturally handles “multiplication/division before addition/subtraction”, parentheses, and unary plus/minus;
- Division by zero raises
ValueError('除数不能为零')and a parse failure raisesValueError('表达式无效'); - Results are formatted uniformly:
6.0 → 6,0.30000000000000004 → 0.3. - Input is tokenised with a whitelist regex, so arbitrary code can never be executed.
5.8 Exception Handling
- Backend: business errors raise
ValueErrorwith Chinese messages; the API layer maps
them to400+errorfield. Unexpected internal errors are caught and returned as500with a generic message. - Frontend: on a non-2xx response, the red message area shows the backend’s
error
text; if the backend is unreachable the UI shows “无法连接到后端服务”
(Cannot connect to the backend service) instead of a wrong result.
5.9 Frontend/Backend Interaction Process
User clicks a button / presses a key ↓ Frontend builds the expression string ↓ POST /api/calculate { "expression": "1+2×3" } ↓ Backend validates → parses → calculates → saves to SQLite ↓ 201 { id, expression, result, created_at } ↓ Frontend shows the result and refreshes the history panel (GET /api/history)5.10 Deployment Process
- Backend: deployed withPythonAnywhere(free tier) using the
wsgi.pyentry
point; the Flask app runs behind PythonAnywhere’s web server. - Frontend: deployed withGitHub Pages(free static hosting);
chooses the production backend address when opened from the deployed domain. - Online addresses and test instructions are listed inSection 10.
6. Function Structure Diagram
Frontend/Backend Separated Calculator System ├── Frontend calculator_frontend │ ├── Calculator UI (dark theme) │ │ ├── Digit / decimal point input │ │ ├── Four basic operator input │ │ ├── Parenthesis input │ │ ├── Sign toggle (±) │ │ ├── Clear (AC) / backspace │ │ └── Evaluate (=) │ ├── Keyboard shortcuts (extended) │ ├── Error messages (red, division by zero / invalid expression) │ └── History panel │ ├── Shows expression / result / timestamp │ ├── Delete a single record (×) │ └── Clear all (with confirmation) └── Backend calculator_backend ├── POST /api/calculate evaluate expression + write history ├── GET /api/history read history ├── DELETE /api/history/<id> delete one history record ├── DELETE /api/history clear all history └── SQLite persistence7. Code Explanation (Key Code + Design Rationale)
7.1 Backend: Expression Parsing (calc.py)
defparse_term(self):value=self.parse_factor()whileself.peek()in('*','/','×','÷'):op=self.take()rhs=self.parse_factor()ifopin('/','÷'):ifrhs==0:raiseValueError('除数不能为零')# business error -> HTTP 400value/=rhselse:value*=rhsreturnvalueDesign rationale: The grammar is a three-level recursionexpr → term → factor.
Thetermlevel parses afactorfirst and then handles multiplication/division,
so the multiplication/division “binds” tighter and naturally has higher precedence
than the addition/subtraction handled by theexprlevel. Thefactorlevel also
handles parentheses and unary minus, covering cases like(1+2)×3and3×-2.
Noeval/execis used anywhere — the input is parsed with a whitelist token regex.
7.2 Backend: REST Interface (app.py)
@app.route('/api/calculate',methods=['POST'])defcalculate():expression=(request.get_json(silent=True)or{}).get('expression','').strip()try:result=evaluate(expression)exceptValueErrorasexc:returnjsonify({'error':str(exc)}),400cur=conn.execute('INSERT INTO history (expression, result, created_at) VALUES (?, ?, ?)',(expression,result,created_at))conn.commit()returnjsonify({'id':cur.lastrowid,'expression':expression,'result':result,'created_at':created_at}),201Design rationale: The endpoint only does “receive expression → validate → evaluate →
save to DB → return”. Business errors are uniformly mapped to400 + error message, which the
frontend renders as a red message. Parameterized SQL (?placeholders) prevents injection attacks.
7.3 Backend: History CRUD (app.py)
@app.route('/api/history/<int:rid>',methods=['DELETE'])defdelete_record(rid):conn=get_conn()try:conn.execute('DELETE FROM history WHERE id = ?',(rid,))conn.commit()finally:conn.close()returnjsonify({'ok':True})Design rationale: Deletion goes through the backend API and removes the row from the
database for real; the frontend then re-queriesGET /api/historyto refresh the list,
so the displayed data always reflects the latest state of the backend database.
7.4 Frontend: Unified Input for Keyboard and Buttons (app.js)
document.addEventListener('keydown',(event)=>{constkey=event.key;if(/[0-9]/.test(key))insert(key);elseif(key==='*')insert('×');elseif(key==='/'){event.preventDefault();insert('÷');}elseif(key==='Enter'){event.preventDefault();evaluate();}elseif(key==='Backspace')backspace();elseif(key==='Escape')clearAllInput();});Design rationale: Every button carries adata-keyattribute, and both button
clicks and keyboard events go through the sameinsert()input channel.*//are
automatically converted to×/÷before being sent to the backend, so clicking with
the mouse and typing with the keyboard behave identically (see screenshot 4.16).
This keyboard support is one of the extended features.
7.5 Frontend: History Records (Proof of Persistence)
asyncfunctionloadHistory(){constres=awaitfetch(`${API_BASE_URL}/history`);constdata=awaitres.json();renderHistory(data.items||[]);}Design rationale: History is not stored inlocalStorage; it is read from the
backend SQLite database every time. Therefore the records survive a page refresh
(F5), which demonstrates the frontend/backend separation idea that “data is
persisted by the backend” (see screenshot 4.13).
8. Personal Journey and Learnings
- I truly understood frontend/backend separation: the frontend is only
responsible for display and interaction, while all calculation and data storage
are delegated to the API. The two sides communicate via JSON with clear
responsibilities, so they can be developed and deployed independently. - The recursive descent parsergave me a concrete understanding of the
relationship between grammar and precedence. Before, I only knew the precedence
rules; this time I implemented them with a grammar myself and realized that the
layering ofterm/factoris exactly where precedence comes from. - Error handling must reach the frontend: a
ValueErrorraised in the backend
has to be converted into an HTTP status code plus a user-friendly message, and the
frontend renders it as a red error message — the full chain must be complete. - SQLite made persistence painless: no separate database service needed, a
single file does the job. It fits course assignments perfectly and is more than
enough for the CRUD operations on history records. - Deployment taught me the difference between local and online environments:
the frontend and the backend live on different domains, so CORS must be enabled
and the frontend must switch its API address automatically. - Through the unified input channel of keyboard shortcuts and
data-key, I merged
“clicking” and “typing” into one logic flow and learned the value of abstraction.
9. Extended Features (Extra Credit)
The following features go beyond the basic requirements and have been implemented and
demonstrated:
| Feature | Description | Demonstration |
|---|---|---|
| Keyboard shortcuts | Full keyboard input: digits,+ - * /,( ),Enter,Backspace,Escape | Screenshot 4.16 |
| Delete a single history record | Each record has a×button; deletion is executed through the backend API | Screenshot 4.14 |
| Clear all history | “清空全部” button with a confirmation dialog | Screenshot 4.15 |
| Red error messages | Division by zero / invalid expression shown in red, coming from the backend | Screenshots 4.10, 4.11 |
10. Deployment / Access Information
Backend (public address)
https://060725.pythonanywhere.comFrontend (public address)
https://060725.github.io/calculator_frontend/How to test
- Open the frontend address in a browser (desktop or mobile).
- Click or type any expression, e.g.
12+8, then press=— the result20is returned by the backend. - Check the history panel: records (expression / result / time) are stored in the backend database and survive
F5. - Try
5÷0— the red message “除数不能为零” appears; try+then=— “表达式无效”. - To verify separation: stop the backend and press
=— no new valid result can be computed.