832401304陈颀桢 First Assignment: A Front-End/Back-End Separated Calculator System
This article documents CalcFlow, a calculator built for the first Software Engineering Practice assignment. It covers the source repositories, live demo, feature screenshots, design decisions, implementation, and deployment checks.
Table of Contents
- Course Information and Assignment Requirements
- GitHub Repositories and Code Standards
- Live Demo and Testing Instructions
- PSP Table
- Finished Product
- Design and Implementation
- Key Code and Explanations
- Deployment and Verification
- Additional Features and Limitations
- Personal Reflection
Course Information and Assignment Requirements
| Item | Description |
|---|---|
| Course | Software Engineering Practice |
| Assignment | Build a calculator with separate front and back ends. The back end calculates results and stores history in a database. |
| Objectives | Understand HTTP APIs, expression processing, database persistence, and cooperation between the front end and back end. |
| Deadline | October 7, 2026, 23:59 (China Standard Time) |
| Other references | First assignment requirements published by teaching assistant Zhuo Genghua |
GitHub Repositories and Code Standards
- Front-end repository: grrrced/calculator_frontend
- Back-end repository: grrrced/calculator_backend
- Front-end code standard: codestyle.md
- Back-end code standard: codestyle.md
Live Demo and Testing Instructions
- Public calculator: Open CalcFlow
- Back-end health endpoint: Railway API health
Open the calculator, enter an expression, and click “=” or press Enter. The following cases can be checked:12+8→ 20,18-7→ 11,6*7→ 42,14/2→ 7,0.1+0.2→ 0.3,1+2*3→ 7,(1+2)*3→ 9, and3*-2 + +4→ -2. Entering1++*2or1/0displays an error. After a successful calculation, the result appears in history. Refresh the page to check that the history is loaded again; use the search field to filter loaded records and the “×” button beside a record to delete that specific entry. The upper-right button changes the theme. When the expression field has focus, Enter calculates and Esc clears the input.
The expressions, error messages, history after a browser refresh, search, deletion followed by another refresh, theme switching, and keyboard shortcuts were checked on the public page on October 5, 2026. Persistence after a back-end restart has not yet been tested. The back-end service and its database volume must remain available during grading.
PSP Table
The figures below are an illustrative, retrospective workload estimate for a third-year student with basic programming experience who is building a separated front-end/back-end application for the first time. Neither column is an original pre-development plan or a measured record of time actually spent. The last column describes work covered by the estimate; it does not prove that the work took the stated time. Without original time logs, an actual plan-versus-result deviation cannot be calculated.
| Phase | Reference plan (minutes; drafted afterward) | Reference time with debugging (minutes; not measured) | Work covered by the estimate |
|---|---|---|---|
| Requirements analysis | 40 | 50 | Read the assignment and identify the four required feature groups and acceptance checks. |
| System design | 60 | 70 | Define front-end/back-end responsibilities, API responses, and project structure. |
| Front-end development | 150 | 180 | Build input, buttons, result display, history, and error feedback. |
| Back-end API development | 120 | 140 | Implement routes, input checks, JSON responses, and CORS. |
| Expression parser | 120 | 170 | Handle precedence, parentheses, unary signs, and invalid input. |
| Database design | 40 | 50 | Create the SQLite table and initialize the database connection. |
| History features | 60 | 80 | Save successful calculations, query records, and delete by ID. |
| Integration | 60 | 100 | Check API URLs, request bodies, and response fields. |
| Testing and fixes | 90 | 120 | Check arithmetic, compound expressions, errors, and history behavior. |
| Deployment | 60 | 140 | Publish GitHub Pages, deploy Railway, and configure the database path. |
| Blog writing | 90 | 120 | Prepare screenshots, API and code explanations, and project review. |
| Total | 890 | 1220 | Approximately 14 hours 50 minutes and 20 hours 20 minutes, respectively. |
Finished Product
The following 16 screenshots were captured from the deployed application. They cover the main calculator functions, compound expressions, error handling, history, and additional features. Some screenshots include records left by earlier test cases.
Figure 1: Main Interface
The page has a calculator area and a history area. It shows the back-end connection status, expression input, buttons, and saved records.
Figure 2: Addition
Entering12+8displays 20. The same expression and result appear in history.
Figure 3: Subtraction
Entering18-7returns 11, checking both the calculation endpoint and the page display.
Figure 4: Multiplication
Entering6*7returns 42. The interface may show a multiplication sign, while the request uses “*”.
Figure 5: Division
Entering14/2returns 7, matching the result returned by the back end.
Figure 6: Decimal Numbers
Entering0.1+0.2displays 0.3, demonstrating decimal expression handling.
Figure 7: Operator Precedence
Entering1+2*3returns 7 because multiplication is evaluated before addition.
Figure 8: Parentheses
Entering(1+2)*3returns 9. Compared with Figure 7, this demonstrates how parentheses change the calculation order.
Figure 9: Unary Plus and Minus
Entering3*-2 + +4returns -2. This checks a negative multiplication operand and a unary plus sign.
Figure 10: Invalid Expression
Entering1++*2displays an expression error from the back end. It is not stored as a successful calculation.
Figure 11: Division by Zero
Entering1/0displays a division-by-zero error and produces no valid result.
Figure 12: History After a Browser Refresh
After refreshing the browser, the input returns to its initial state while history is loaded again from the back end. The earlier record12345+55 = 12400is still present. This checks a browser refresh, not a back-end restart.
Figure 13: History Search
Typing12345+55into the history search box filters the loaded list to the record with result 12400. Search covers loaded records, not the entire database.
Figure 14: Delete a Specific Record
With the same search filter active, clicking the record’s delete button causes the page to display “No matching records.” The front end sends a deletion request to the back end.
Figure 15: History After Deletion and Another Refresh
After the deletion, a second browser refresh reloads history from the back end. The12345+55record no longer appears, while other records remain. The item was deleted from the database rather than hidden only on the page.
Figure 16: Dark Theme
Clicking the theme button in the upper-right corner switches the page to a dark appearance. The selection lasts only for the current page visit.
Design and Implementation
Requirements and Separation of Responsibilities
The assignment requires four arithmetic operations, compound expressions, decimals, parentheses, unary signs, error handling, database-backed history, and deletion of a specific record. Two central constraints are that the final result must be calculated by the back end and that history must be stored in a back-end database.
The front end uses HTML, CSS, and vanilla JavaScript. It collects expressions, sends HTTP requests, and displays results and history. The back end uses the Python standard library to provide a JSON API, while SQLite stores calculation records. The two parts are kept in separate GitHub repositories, each with its own README and code standard.
Functional structure:
CalcFlow ├── Front end: expression input -> HTTP request -> result/error/history display ├── HTTP API: validation, routing, JSON responses, CORS ├── Calculator: tokens, precedence, parentheses, unary signs, division-by-zero checks └── Data layer: SQLite insert, query, delete one record, clear all recordsA successful request follows this sequence: the front end sends an expression; the back end validates, parses, and calculates it; the back end saves the result; the back end returns the result; and the front end updates the display and reloads history. The browser has no implementation that independently calculates the final answer.
Front-End and Back-End Design
The interface separates calculation from history. Users can type in the expression field or use calculator buttons. Multiplication and division buttons may display “×” and “÷”, but add “*” and “/” to the expression sent to the API. While a request is pending, the interface shows a waiting state. On success it displays the response’s result; on failure it displays the error message. History expressions are escaped before being inserted into HTML so that input is not interpreted as page markup.
The back end separates HTTP handling in server.py, expression processing in calculator.py, and SQLite operations in database.py. This keeps UI changes independent of parsing rules and separates API behavior from database access.
API Design and Interaction
| Method and path | Purpose | Main response |
|---|---|---|
| GET /api/health | Service health check | 200 with a service name and success flag |
| POST /api/calculate | Submit an expression | 201 on success; 400 for an invalid expression |
| GET /api/history?limit=100 | Retrieve recent history | 200 with an items array |
| DELETE /api/history/{id} | Delete one record | 200 on success; 404 if no such record exists |
| DELETE /api/history | Clear all records | 200 with the deleted-record count |
For example, a request body of {“expression”:“(1+2)*3”} returns success, id, expression, result, and createdAt after a successful calculation; result is 9, while the ID and timestamp are generated by the back end. Division by zero returns HTTP 400 with {“success”:false,“message”:“Division by zero is not allowed”}. The front end checks both the HTTP status and the success field.
The two services have different origins, so the back end returns CORS headers and handles OPTIONS preflight requests. The current allowed origin is “*”, suitable for this account-free course demo. The application does not provide user accounts or separate histories for different visitors.
Database and History
The calculation_history table contains:
| Field | Type | Meaning |
|---|---|---|
| id | INTEGER primary key, autoincrement | Unique ID used to delete a record |
| expression | TEXT, not null | Submitted expression with surrounding whitespace removed |
| result | REAL, not null | Result calculated by the back end |
| created_at | TEXT, not null | Creation time in UTC ISO format |
On first startup, the back end creates the data directory, database, and table. Only successful calculations are saved. History is returned in descending ID order; the default is the 100 most recent entries, and the limit parameter is restricted to 1–500. Front-end search filters only the records already loaded.
Deletion by ID uses a parameterized SQL statement. After deletion, the front end requests the latest history again. Clearing history deletes all records in the back-end database. A browser refresh triggers a new history API request rather than relying on browser storage.
Expression Parsing and Error Handling
The parser first turns the input into numeric, operator, and parenthesis tokens. It then processes expression -> term -> unary -> primary recursively. The expression level handles addition and subtraction, term handles multiplication and division, unary handles positive and negative signs, and primary handles numbers and parentheses. Therefore1+2*3evaluates multiplication first,(1+2)*3evaluates the parentheses first, and3*-2treats “-2” as a negative operand. Operators at the same level are processed from left to right.
The implementation does not call eval or exec. Inputs are limited to 200 characters and 100 tokens. Unknown characters, missing operands, mismatched parentheses, and division by zero raise ExpressionError. The back end rejects non-finite results. It uses floating-point arithmetic and rounds results to 12 decimal places, so it is a standard calculator rather than an arbitrary-precision system.
Empty input, API configuration, and network-error messages in the front end improve usability but do not replace server-side validation. Database and infrastructure errors are not all mapped to one standardized business error; this remains an area for improvement.
Key Code and Explanations
The following short excerpts come from the project; some line breaks have been adjusted for readability.
1. Send the Expression and Use the Back-End Result
constdata=awaitrequest('/calculate',{method:'POST',body:JSON.stringify({expression})});resultOutput.textContent=String(data.result);Only the expression is sent. The displayed result comes directly from the response. The request() helper uses fetch and parses JSON; after calculation, loadHistory() retrieves the saved record instead of constructing a fake history item in the browser.
2. Calculate, Save, and Respond in That Order
expression=expression.strip()result=calculate(expression)record=repository.add(expression,result)self._send_json({"success":True,**record},HTTPStatus.CREATED)This order ensures that the history result comes from the same back-end calculation. If calculation raises an expression error, repository.add() is not called.
3. Handle Unary Signs Separately
defparse_unary(self)->float:ifself.current()isnotNoneandself.current().kindin("+","-"):operator=self.consume(self.current().kind).kind value=self.parse_unary()returnvalueifoperator=="+"else-valuereturnself.parse_primary()The multiplication and division layer calls the unary layer to read its operands. Thus the minus sign in3*-2is understood as part of the operand, rather than a binary subtraction with a missing left operand.
4. Insert History with SQL Parameters
cursor=connection.execute("INSERT INTO calculation_history(expression, result, created_at) VALUES (?, ?, ?)",(expression,result,timestamp),)The SQL statement and expression data remain separate, preventing user input from being concatenated into SQL. Queries and deletions also use parameters, and the connection context commits a successful transaction.
Deployment and Verification
The front end is published with GitHub Pages, and the back end runs on Railway. A Dockerfile builds the back-end Python 3.12 container. The service listens on 0.0.0.0 and reads Railway’s PORT environment variable. A persistent volume is mounted at /data, the SQLite path is /data/calculator.sqlite3, and the health endpoint is /api/health.
The front-end config.js chooses the API URL according to the environment: local development uses http://127.0.0.1:8000/api, while the public site uses https://calculatorbackend-production-1bcb.up.railway.app/api. The address 127.0.0.1 refers to the visitor’s own device and cannot serve as a public back-end URL for the teaching assistant.
On October 5, 2026, 14 public API checks passed, covering the health endpoint, CORS preflight, eight types of expressions, invalid input, division by zero, history retrieval, and deletion by ID. The page, scripts, and stylesheet also returned HTTP 200, and the online configuration contained the correct back-end URL. Browser checks covered arithmetic, compound expressions, error feedback, history after refresh, search, deletion followed by refresh, theme switching, and Enter/Esc. History persistence after a back-end restart has not yet been tested; a configured volume is not presented as proof of a successful restart test.
Deployment required three separate pieces: static page hosting, a running back-end process, and database storage. A GitHub repository is a source-code address, while GitHub Pages serves only the static front end. The back end must run separately and respect the platform-assigned port. SQLite stored only in a temporary container directory could be lost, so the deployment uses a persistent volume. These configuration decisions do not prove every long-running failure scenario has been tested.
At the time of deployment, the Railway account showed a 30-day or USD 5 trial credit and no paid plan. Service status and remaining credit should be checked during the grading period.
Additional Features and Limitations
- History search filters the most recent 100 records loaded by default. It does not search the entire database, and there is no pagination interface.
- The theme button switches between light and dark styles for the current page visit. The choice is not saved across refreshes.
- When the expression field has focus, Enter calculates and Esc clears the input. The calculation still takes place on the back end.
- “Clear history” deletes all records from the current database. All visitors currently share one history table because the application has no accounts.
The history-search and theme-switching screenshots demonstrate the corresponding features. Enter and Esc were checked in the browser. The clear-all operation is implemented in code but was not covered by this set of screenshots or the online interaction checks. Possible improvements include paginated history, per-user records, saved theme preferences, and more consistent handling of service failures.
Personal Reflection
The most useful lesson from this project is that the front-end repository, public page, and back-end API have different roles and addresses. During deployment, the public page once returned 404. After it became accessible, the back-end health endpoint and the public API URL still needed separate checks. A page loading successfully does not prove that calculation, error handling, and history retrieval work, so testing must use specific input cases.
Compound expressions are easier to mishandle than a single arithmetic operation. The implementation separates addition and subtraction, multiplication and division, unary signs, and parentheses in a recursive-descent parser. Comparing1+2*3with(1+2)*3makes precedence visible, while3*-2, decimals, invalid expressions, and division by zero exercise important edge cases. A useful next learning step is to trace these examples through the parser functions and explain each call.
History requires checking both the visible page and the database state. The back end saves successful calculations to SQLite, and the page queries history again after a refresh. Deleting a specific record and refreshing once more verifies that the record was removed from the database rather than merely hidden in the UI. Persistence after a back-end restart still needs a separate test. Shared history across visitors and unsaved theme preferences are also limitations to address later.
There are no original time logs for this project, so the PSP figures above are a workload example, not evidence of actual efficiency. For the next assignment, the tasks should be broken down and estimated before implementation, with time recorded after each phase. Another goal is to reproduce the local startup and essential tests independently, so that the project can be explained, debugged, and modified rather than merely run.