173
社区成员
发帖
与我相关
我的任务
分享Course: 2601_FZU-MU_SE
Assignment: First Assignment — Front-End and Back-End Separation Calculator System
Student: Shiyu Qian (钱施宇)
MU ID: 24217213 / FZU ID: 832402115
Completion date: October 1, 2026
| Item | Description |
|---|---|
| Course | 2601_FZU-MU_SE / Software Engineering |
| Assignment | First Assignment: Front-End and Back-End Separation Calculator System |
| Objectives | Understand front-end/back-end separation, implement a safe calculator API, persist calculation history, and deploy the system for public access |
| Student | Shiyu Qian (钱施宇) |
| MU ID | 24217213 |
| FZU ID | 832402115 |
| Front end | Web application built with HTML, CSS, and native JavaScript |
| Back end | Node.js 22+, Express, node:sqlite, decimal.js |
| Database | SQLite |
| Deployment | Alibaba Cloud ECS, Ubuntu 24.04, Nginx, PM2 |
| Public frontend | http://120.27.206.217/ |
| Public API | http://120.27.206.217:8080/ |
The goal of this assignment was to build a calculator system with a separated front end and back end. The front end is responsible for user interaction and displaying results. The back end is responsible for validating and parsing expressions, performing calculations, handling exceptions, storing history, and returning JSON responses.
The most important architectural rule is that the final result must be calculated by the back end. The front end only sends an expression and displays the result returned by the API.
| Item | Link |
|---|---|
| Frontend repository | https://github.com/q1527700693-afk/832402115_calculator_frontend |
| Backend repository | https://github.com/q1527700693-afk/832402115_calculator_backend |
| Frontend code standard | https://github.com/q1527700693-afk/832402115_calculator_frontend/blob/main/codestyle.md |
| Backend code standard | https://github.com/q1527700693-afk/832402115_calculator_backend/blob/main/codestyle.md |
The frontend and backend are stored in two independent GitHub repositories, as required by the assignment.
The assignment requires a calculator system with the following functions:
eval, exec, or equivalent arbitrary-code executioncodestyle.mdThe planned time was recorded before implementation. The actual time was updated after development, testing, and deployment.
| Phase | Task | Planned Time | Actual Time |
|---|---|---|---|
| Requirement analysis | Read the assignment and define the scope | 1 h | 1 h |
| System design | Architecture, API, and database design | 2 h | 2.5 h |
| Frontend development | Layout, button interaction, history display | 4 h | 8 h |
| Backend development | Node.js + Express project and APIs | 4 h | 6 h |
| Expression calculation | Tokenizer, infix-to-RPN conversion, RPN evaluation | 5 h | 7 h |
| Database | SQLite setup, table creation, node:sqlite operations | 3 h | 3 h |
| Frontend/backend integration | Fetch API, CORS, joint debugging | 3 h | 4 h |
| Testing | Normal, invalid, and boundary cases | 3 h | 5 h |
| Deployment | Cloud deployment and public access | 3 h | 4 h |
| Blog writing | Screenshots, documentation, PSP | 3 h | 3 h |
| Total | 31 h | 43.5 h |
The actual time was higher than planned because additional features such as scientific calculation and base conversion were added, and several parser bugs were fixed during testing.

The main interface contains three modes: standard calculation, base conversion, and scientific calculation. The calculation history is displayed on the right side.

The expression 5÷2 is calculated on the backend and the result 2.5 is returned to the frontend.

The compound expression 3+3×(5÷9)-5 is evaluated as -0.333333333333, which verifies operator precedence and negative results.

The scientific expression 9×ln(2) is evaluated as 6.23832462504.

The base conversion page supports conversions among binary, octal, decimal, and hexadecimal numbers.

The /api/history endpoint returns calculation history in JSON format.

When the user clicks “Clear,” a confirmation dialog appears to prevent accidental deletion.

If the backend is stopped, the frontend displays the message “Unable to connect to the backend service.”

History records include scientific expressions such as log(8) and 9×ln(2), as well as invalid-expression messages.

When the backend is unavailable during base conversion, the frontend shows a connection error.

The invalid expression 0.888888888889tan(9)× was once treated as multiplication. This bug was fixed by adding syntax validation.

The frontend filters unsupported characters, and the backend returns “Missing operator” for invalid input.

When the backend is stopped, historical records remain visible, but new calculations cannot be completed.
Web Frontend
|
| HTTP / JSON
v
Node.js + Express Backend
|
v
SQLite Database
The frontend does not calculate the final result. It sends the expression to the backend, and the backend performs validation, parsing, calculation, and history storage.
Calculator System
├── Standard Calculation
│ ├── Addition, subtraction, multiplication, division
│ ├── Parentheses and operator precedence
│ ├── Decimal numbers
│ └── Unary positive/negative numbers
├── Scientific Calculation
│ ├── sin, cos, tan
│ ├── sqrt, log, ln, abs
│ ├── exponentiation (^)
│ └── constants pi and e
├── Base Conversion
│ └── Binary, octal, decimal, hexadecimal
├── Calculation History
│ ├── Save history
│ ├── Query history
│ ├── Delete one record
│ ├── Clear all history
│ └── Click a record to reuse its result
└── Error Handling
├── Invalid expression
├── Missing operator
├── Division by zero
└── Backend connection failure
POST /api/calculate
Content-Type: application/json
Request:
{
"expression": "(1+2)*3"
}
Success response:
{
"success": true,
"expression": "(1+2)*3",
"result": 9
}
Error response:
{
"success": false,
"message": "Division by zero is not allowed"
}
POST /api/convert/base
Request:
{
"value": "1010",
"fromBase": 2,
"toBase": 10
}
Success response:
{
"success": true,
"result": "10"
}
GET /api/history
Response:
{
"success": true,
"data": [
{
"id": 1,
"expression": "6×6",
"result": "36",
"createdAt": "2026-10-01 13:15:54"
}
]
}
DELETE /api/history/{id}
DELETE /api/history
The backend uses SQLite to persist calculation history.
Table name: calculation_history
| Field | Type | Description |
|---|---|---|
| id | INTEGER | Primary key, auto-increment |
| expression | TEXT | Calculation expression |
| result | TEXT | Calculation result |
| created_at | TEXT | Creation time |
SQL:
CREATE TABLE IF NOT EXISTS calculation_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
expression TEXT NOT NULL,
result TEXT NOT NULL,
created_at TEXT NOT NULL
);
The frontend uses the Fetch API to send expressions to the backend.
function calculate(expression) {
return request(API_BASE + "/api/calculate", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
expression: expression
})
});
}
The frontend selects the API base address dynamically:
var isLocal = window.location.protocol === "file:"
|| window.location.hostname === "localhost"
|| window.location.hostname === "127.0.0.1";
var API_BASE = isLocal
? "http://localhost:8080"
: "http://120.27.206.217:8080";
router.post("/calculate", (request, response) => {
try {
const expression = request.body.expression;
if (typeof expression !== "string" || expression.trim() === "") {
throw new Error("Expression cannot be empty");
}
const result = evaluate(expression);
historyService.save(expression, result);
response.json({
success: true,
expression,
result: toJsonResult(result)
});
} catch (error) {
response.status(400).json({
success: false,
message: error.message
});
}
});
The parser uses a safe mathematical expression pipeline:
The parser supports +, -, *, /, ^, parentheses, unary signs, sin, cos, tan, sqrt, log, ln, abs, pi, and e.
Insert:
const insertStatement = db.prepare(
"INSERT INTO calculation_history (expression, result, created_at) VALUES (?, ?, ?)"
);
insertStatement.run(expression, result.toString(), createdAt);
Query:
const listStatement = db.prepare(
"SELECT id, expression, result, created_at AS createdAt " +
"FROM calculation_history ORDER BY id DESC"
);
listStatement.all();
Delete:
const deleteStatement = db.prepare(
"DELETE FROM calculation_history WHERE id = ?"
);
deleteStatement.run(id);
Open frontend/index.html directly in a browser, or use a local static server:
cd frontend
python -m http.server 5500
Then visit:
http://localhost:5500
When running locally, the frontend uses http://localhost:8080 as the API base address.
| Test | Expression | Expected Result |
|---|---|---|
| Addition | 12+8 | 20 |
| Subtraction | 8-3*2 | 2 |
| Multiplication | 7*6 | 42 |
| Division | 10/2+7 | 12 |
| Decimal | 0.1+0.2 | 0.3 |
| Parentheses | (1+2)*3 | 9 |
| Unary negative | -5+8 | 3 |
| Division by zero | 1/0 | Error message |
| Invalid expression | (1+2 | Error message |
| Scientific | sin(30) | 0.5 |
| Base conversion | 1010 from binary to decimal | 10 |
The backend is deployed on an Alibaba Cloud ECS instance.
Server information:
Deployment steps:
npm install in the backend directory.The following tests were performed:
All required functions worked correctly after the fixes described in Section 15.
Input:
0.888888888889tan(9)×
The parser treated the expression as 0.888888888889 × tan(9) instead of reporting an error.
Cause: the parser did not check whether two operands were separated by a valid operator.
Solution: I added a validateTokens step. The validator checks:
).After the fix, the expression above returns “Missing operator.”
The input box allowed users to enter long sequences of unsupported characters.
Solution: I added frontend input sanitization and operator replacement:
The backend uses the built-in node:sqlite module, which requires Node.js 22 or later.
Solution: I installed Node.js 22+ on the server and used PM2 to keep the process running.
When the backend was not running, the frontend showed a connection error. This was expected, but the message was initially misleading because it mentioned Spring Boot.
Solution: I changed the message to “Unable to connect to the backend service. Please make sure the Node backend is running.”
Through this assignment, I learned how to design and implement a front-end/back-end separated system. I practised:
evalnode:sqlitedecimal.js for accurate decimal calculationsI also learned that testing invalid and boundary inputs is as important as testing normal cases.
Possible future improvements include:
| Library | Purpose | Reason |
|---|---|---|
| Express | Web framework | Provides REST API routing and request handling |
| decimal.js | High-precision decimal arithmetic | Avoids floating-point issues such as 0.1 + 0.2 |
| node:sqlite | SQLite driver | Built-in Node.js module for persistent history |
| cors | Cross-origin support | Allows the frontend to call the backend API during development |
The backend does not use eval, exec, or script engines to execute user input. It uses a custom parser with syntax validation, shunting-yard conversion, and stack-based evaluation.
Supports binary, octal, decimal, and hexadecimal conversions through:
POST /api/convert/base
Supports:
sin, cos, tan, sqrt, log, ln, abs, ^, pi, e
Trigonometric functions use degrees by default.
This assignment helped me understand the complete workflow of a front-end/back-end separated application. The frontend provides a usable calculator interface, while the backend performs all calculations, handles errors, stores history, and returns JSON responses. The project has been deployed to a public server and can be accessed through the URLs above. The bugs found during testing were analysed and fixed, which improved both the reliability of the parser and the user experience.