832402203陈嘉源Chen Jiayuan-Front-End and Back-End Separation Calculator-Web

832402203陈嘉源 2026-09-26 21:04:03

1. Assignment Information

This project was developed for the Software Engineering course assignment. It implements a browser-based calculator using a separated front-end and back-end architecture.

  • Student: Chen Jiayuan
  • FZUID: 832402203
  • MUID: 24125415

3. PSP Table

PSP StageEstimated Time (min)Actual Time (min)
Planning4050
Development710970
├─ Requirement analysis6080
├─ Design specification5065
├─ Design review3040
├─ Coding standard3040
├─ Detailed design7090
├─ Coding300410
├─ Code review6080
└─ Testing110165
Reporting150180
├─ Test report7085
├─ Size measurement2020
└─ Postmortem and process improvement6075
Total9001200

The project was initially estimated to require fifteen hours but took approximately twenty hours of active work. The additional time was mainly spent debugging API communication, implementing the expression parser, migrating the database from SQLite to PostgreSQL, resolving database connection problems, and deploying the front end and back end separately. Waiting time between development sessions is not included.

4. Requirement Analysis

The calculator is required to use a separated front-end and back-end architecture. The browser is responsible for collecting user input and displaying the response, while expression validation and calculation are performed by the server. If the back-end service is unavailable, the front end cannot produce a new calculation result.

4.1 Functional requirements

The calculator must support the following functions:

  • Addition, subtraction, multiplication, and division
  • Decimal numbers and compound expressions
  • Operator precedence and parentheses
  • Unary positive and negative signs
  • Clear and backspace operations
  • Keyboard input
  • Detection of invalid expressions and division by zero
  • Storage of successful calculations
  • Retrieval of calculation history after a page refresh
  • Deletion of an individual history record
  • Removal of all history records

The interface also provides two small convenience functions: a previous expression can be placed back into the input area, and the current result can be copied to the clipboard.

4.2 Data requirements

Each successful calculation is stored with an identifier, the original expression, the result, and the time at which it was created. Failed calculations are not added to the history. The stored records must remain available after the browser is refreshed or the back-end service is restarted.

4.3 Technical constraints

Communication between the two parts uses HTTP requests and JSON data. The front end sends an expression to the calculation API and displays the returned result or error message. The back end must parse the expression safely and must not use eval, exec, or another method that directly executes user input as program code.

The front-end and back-end source code are maintained in separate GitHub repositories and deployed independently. The completed application must also be accessible through a public URL.

4.4 Basic interaction flow

  1. The user enters an expression through the buttons or keyboard.
  2. The browser sends the expression to the back-end calculation API.
  3. The server validates and evaluates the expression.
  4. A successful result is stored in PostgreSQL.
  5. The server returns a JSON response.
  6. The front end displays the result and reloads the history list.

5. System Design

5.1 Overall architecture

The system consists of three independently managed parts: a static front end, a Node.js back end, and a PostgreSQL database.

User
  │
  ▼
Front end on GitHub Pages
  │  HTTPS requests and JSON responses
  ▼
Express service on Render
  │  SQL queries
  ▼
PostgreSQL database on Neon

The browser never connects directly to the database. All calculations and database operations pass through the back-end API. This arrangement keeps the user interface separate from the calculation and storage logic.

5.2 Component responsibilities

ComponentResponsibility
HTMLDefines the calculator, display area, buttons, and history panel
CSSControls layout, colours, responsive behaviour, and button states
Browser-side JavaScriptHandles input, sends API requests, and updates the page
Express serverValidates requests, evaluates expressions, and manages history
PostgreSQLStores successful calculations and their creation times
GitHub PagesHosts the static front-end files
RenderRuns the Node.js back-end service
NeonProvides the hosted PostgreSQL database

5.3 API design

The front end communicates with the server through the following endpoints:

MethodEndpointPurpose
GET/api/statusChecks whether the server and database are available
POST/api/calculateValidates and evaluates an expression
GET/api/historyReturns the stored calculation history
DELETE/api/history/:idDeletes one history record
DELETE/api/historyDeletes all history records

A calculation request contains only the expression:

{
  "expression": "(2+3)*4"
}

For a successful calculation, the server returns the result and the stored record. Invalid input produces an error response and is not inserted into the database.

5.4 Expression-processing design

The server processes expressions in several stages. It first converts the input string into numbers, operators, and parentheses. A recursive-descent parser then evaluates the tokens according to the following order:

  1. Parenthesised values and numbers
  2. Unary positive and negative signs
  3. Multiplication and division
  4. Addition and subtraction

This structure gives multiplication and division higher priority than addition and subtraction. Parentheses are evaluated recursively. The parser also checks malformed numbers, unsupported characters, incomplete expressions, unmatched parentheses, and division by zero.

5.5 Request flow

When the user presses the equals button, the browser sends a POST request to /api/calculate. The server validates and evaluates the expression. If the calculation succeeds, the server stores the expression, result, and creation time in PostgreSQL before returning the response. The browser then displays the result and requests the latest history.

Deletion follows the same separation. The browser sends the record identifier to the appropriate API endpoint, while the server performs the database operation and returns the outcome.

6. Front-End Implementation

The front end is implemented with plain HTML, CSS, and JavaScript. No external user-interface framework is used. The three files have separate responsibilities: index.html defines the page structure, style.css controls the presentation, and script.js manages interaction and communication with the server.

6.1 Page structure

The main page contains two sections. The calculator section includes the expression display, result display, input buttons, and message area. The history section contains the record count, clear button, and a list generated from data returned by the back end.

Buttons use data-value and data-action attributes. This allows JavaScript to identify input buttons and control buttons without assigning a separate event function to every element.

6.2 Input handling

The current expression is stored as a string in the browser. Both mouse clicks and keyboard events pass values to the same input-handling function. The interface supports numbers, decimal points, operators, parentheses, Enter, Backspace, and Escape.

Several small checks are performed before a character is added. For example, the interface prevents repeated decimal points within the same number and limits an expression to 100 characters. It can also insert multiplication automatically between a number and an opening parenthesis. These checks improve input convenience, but the server still performs the final validation.

6.3 Sending a calculation request

When the equals button is pressed, the browser sends the expression to the deployed back end:

const response = await fetch(
  `${API_BASE_URL}/api/calculate`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      expression: submittedExpression
    })
  }
);

The front end does not calculate the answer. It waits for the JSON response and then displays either the returned result or the error message. The equals button is temporarily disabled while the request is running, which prevents repeated submissions.

If the server cannot be reached, the page reports a connection problem instead of generating a local result. This behaviour preserves the required separation between the two parts of the system.

6.4 History interaction

The history list is loaded through GET /api/history. JavaScript creates the list elements from the returned records rather than placing fixed records in the HTML file. Each item displays the expression, result, and creation time.

A history item provides two actions. The Use button places the saved expression back into the calculator, while the Delete button sends its record identifier to the deletion endpoint. The clear button removes all records after confirmation. After a successful calculation or deletion, the list is requested again so that the displayed state matches the database.

6.5 Additional interface features

The current result can be copied through the Clipboard API. The button text briefly changes after a successful copy operation to give the user visible feedback.

The page uses CSS Grid for the main layout and calculator buttons. On wider screens, the calculator and history panel appear side by side. Media queries switch the page to a single-column layout on smaller screens, allowing the same interface to remain usable on phones and narrow browser windows.

7. Back-End Implementation

The back end is implemented with Node.js and Express. It receives JSON requests, validates expressions, performs the calculations, and accesses PostgreSQL through the pg library. The database connection string is loaded from the DATABASE_URL environment variable rather than being written in the source code.

The server reads the port from process.env.PORT, with port 3000 used during local development. This allows the same code to run locally and on Render.

7.1 Expression tokenizer

The first stage of calculation is tokenisation. The tokenizer reads the expression from left to right and produces tokens for numbers, operators, and parentheses. Spaces are ignored.

A number may contain one decimal point. Inputs such as 2..5 or a single decimal point are rejected. Any character outside the supported set also produces an error. This step prevents unsupported input from reaching the parser.

7.2 Recursive-descent parser

The calculator uses a recursive-descent parser rather than eval. Its structure can be represented by the following simplified grammar:

expression → term (("+" | "-") term)*
term       → unary (("*" | "/") unary)*
unary      → ("+" | "-") unary | primary
primary    → number | "(" expression ")"

Each parser function handles one precedence level. readExpression processes addition and subtraction, while readTerm processes multiplication and division. readUnary supports positive and negative signs, and readPrimary reads numbers or recursively evaluates a parenthesised expression.

Division checks the right-hand value before performing the operation. If it is zero, the parser returns a division-by-zero error. The parser also verifies that all tokens have been consumed, which prevents incomplete or incorrectly ordered expressions from being accepted.

JavaScript floating-point arithmetic can produce values such as 0.30000000000000004. Before returning a valid result, the server limits unnecessary floating-point noise with toPrecision(12) and converts the value back to a string. For example, 0.1+0.2 is returned as 0.3.

7.3 Calculation endpoint

POST /api/calculate first checks that the request contains a non-empty string no longer than 100 characters. Display symbols such as ×, ÷, and − are normalised to standard operators before parsing.

If parsing succeeds, the server stores the expression and result in PostgreSQL. The created record is then returned with HTTP status 201. If validation fails, the server returns status 400 with a specific message, and no history record is created.

7.4 History endpoints

GET /api/history returns the latest records in descending identifier order, with a limit of 100 entries. DELETE /api/history/:id validates the identifier and deletes the matching row. If no row is found, the server returns status 404. The optional clear operation uses DELETE /api/history and reports how many records were removed.

SQL values are passed through parameterised queries such as $1 and $2. User input is therefore kept separate from the SQL statement itself.

7.5 Error handling and cross-origin access

Validation errors and database errors are handled separately. Validation problems return a message that can be shown directly by the interface. Database failures return status 500 without exposing the connection string or internal credentials.

The front end and back end use different public domains, so the server includes cross-origin response headers for the required GET, POST, DELETE, and OPTIONS requests. Unknown API paths return a JSON 404 response.

During startup, the server creates the history table if it does not already exist. It begins listening only after this database step succeeds. A failed database connection therefore prevents the service from starting in an invalid state.

8. Database Design

8.1 Choice of database

The first local version stored calculation history in SQLite. This was suitable for early development because it required little configuration and kept the data in a local file. However, the selected free hosting environment does not provide a persistent local disk for the web service. A SQLite file could therefore be lost when the service is restarted or recreated.

The final version uses PostgreSQL hosted by Neon. The Render service connects to the database through the DATABASE_URL environment variable. This keeps the calculation history separate from the application instance and allows records to remain available after a browser refresh or back-end restart.

8.2 Table structure

The project requires only one table:

CREATE TABLE IF NOT EXISTS history (
  id SERIAL PRIMARY KEY,
  expression TEXT NOT NULL,
  result TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
FieldTypeDescription
idSERIALUnique identifier for each history record
expressionTEXTNormalised expression submitted for calculation
resultTEXTResult returned by the expression parser
created_atTIMESTAMPTZTime at which the record was inserted

The result is stored as text because the parser already produces the final display value as a string. The timestamp is generated by PostgreSQL, so the browser does not decide when a record was created.

8.3 Database operations

A successful calculation inserts one row into the history table. Invalid expressions are rejected before the insert query is executed. History retrieval returns the newest records first and limits the response to 100 entries.

Individual deletion uses the record identifier:

DELETE FROM history
WHERE id = $1
RETURNING id, expression, result, created_at;

The placeholder $1 is supplied separately from the SQL text. The insert operation follows the same parameterised-query approach for the expression and result. Clearing the history deletes all rows but keeps the table available for later calculations.

8.4 Connection and credential management

The database address and password are stored in the deployment environment rather than in GitHub. During local development, the same value is loaded from a .env file, which is excluded through .gitignore. The repository contains only .env.example as a description of the required variable.

The application checks for DATABASE_URL when it starts. It then creates the table if necessary and verifies the connection before opening the HTTP server. This prevents the back end from appearing available when the database setup has failed.

8.5 Persistence verification

Persistence was tested by creating several calculations, refreshing the front-end page, and restarting the back-end service. The same history records remained available because they were stored in Neon rather than application memory or a temporary local file. Single-record deletion and clear-all operations were also checked against the updated history returned by the API.

9. Testing and Screenshots

Testing was carried out through the deployed GitHub Pages application rather than only through the local development files. This checked the complete path from the browser to Render and then to the Neon database.

9.1 Test summary

No.Input or actionExpected resultActual resultStatus
1Open the applicationCalculator and empty history are displayedInterface loaded correctlyPassed
212+82020Passed
32+3*41414Passed
4(2+3)*42020Passed
55*-2-10-10Passed
60.1+0.20.30.3Passed
78/0Division-by-zero errorError message displayedPassed
8(2+3Incomplete-parenthesis errorError message displayedPassed
9Refresh the pageExisting history remains availableRecords were loaded againPassed
10Delete one recordRecord count decreases by oneCount changed from 5 to 4Passed
11Clear all recordsHistory becomes emptyCount changed to 0Passed

Failed expressions were not added to the history table. This was checked by comparing the history count before and after the division-by-zero and incomplete-parenthesis tests.

9.2 Initial interface

img

Figure 1. The deployed calculator before any calculation was stored.

9.3 Basic calculation

img

Figure 2. The expression 12+8 returned 20 and created the first history record.

9.4 Operator precedence

img

Figure 3. Multiplication was evaluated before addition in 2+3*4, producing 14.

9.5 Parentheses

img

Figure 4. Parentheses changed the evaluation order of (2+3)*4, producing 20.

9.6 Unary negative sign

img

Figure 5. The parser treated the minus sign after multiplication as a unary sign and returned -10.

9.7 Decimal calculation

img

Figure 6. The decimal expression 0.1+0.2 was returned as 0.3.

9.8 Division by zero

img

Figure 7. The back end rejected 8/0 and returned a specific division-by-zero message. The failed expression was not stored.

9.9 Invalid expression

img

Figure 8. The incomplete expression (2+3 was rejected because its closing parenthesis was missing.

9.10 Persistent history

img

Figure 9. The history records remained available after the browser page was refreshed.

9.11 Single-record deletion

img

Figure 10. After one record was deleted, the displayed history count decreased from five to four.

9.12 Clear-all operation

img

Figure 11. The clear operation removed all remaining records and returned the history panel to its empty state.

10. Deployment

The three parts of the project are deployed separately. The front end is hosted by GitHub Pages, the Node.js API runs on Render, and the PostgreSQL database is provided by Neon.

PartPlatformPublic address
Front endGitHub Pageshttps://rederchen.github.io/832402203-calculator-frontend/
Back endRenderhttps://eight32402203-calculator-backend.onrender.com/
DatabaseNeonNot exposed to the browser

10.1 Front-end deployment

The front-end repository is public on GitHub. GitHub Pages is configured to deploy the main branch from the repository root. Since the project uses plain HTML, CSS, and JavaScript, no build command is required.

The deployed script.js contains the public Render base URL. All calculation and history requests are therefore sent to the online back end rather than to localhost.

10.2 Back-end deployment

The back-end repository is connected to a Render Web Service. The service uses the following configuration:

Language: Node
Branch: main
Region: Singapore
Build command: npm install
Start command: npm start
Health check path: /api/status
Instance type: Free

Render supplies the production port through the PORT environment variable. The server also keeps port 3000 as a fallback for local development.

The free Render instance may stop after a period of inactivity. Its first request after sleeping can therefore take longer than later requests. This affects startup time but does not remove the history records, because the database is hosted separately.

10.3 Database deployment

A Neon PostgreSQL project was created in the Singapore region. The back end connects to it using a pooled PostgreSQL connection string stored in Render as the DATABASE_URL environment variable.

The connection value is not written in server.js, README.md, screenshots, or GitHub. The local .env file is also excluded from version control. This keeps the database password outside the public repositories.

10.4 Deployment verification

After deployment, /api/status was opened through the Render address to confirm that both the HTTP service and PostgreSQL connection were available. /api/history was then checked to confirm that stored records could be retrieved through the public API.

The GitHub Pages application was tested separately using its public address. Calculations, error responses, history retrieval, deletion, and page-refresh persistence all worked through the deployed services.

11. Development Experience and Summary

11.1 Development process

I began with a small static page and learned the project in stages. The first version contained only a title and basic HTML content. I then added the calculator layout with CSS, connected the buttons with JavaScript, and implemented keyboard input. Building the interface first made it easier to understand how HTML structure, CSS layout, and browser events work together.

The next step was to move the calculation logic to a Node.js server. This part changed the project from a static webpage into a front-end/back-end application. The browser no longer generated the final answer itself; it sent an expression to the API and waited for the server response. This separation was initially less convenient than keeping all logic in one JavaScript file, but it made the responsibilities of each part much clearer.

11.2 Problems encountered

Several problems occurred during development. Early tests were opened directly through a file:// address, which produced browser security warnings and made debugging requests more difficult. Running the project through an HTTP service and later deploying both parts gave the application a clearer execution environment.

I also encountered Cannot GET /api/history and Cannot POST /api/history responses. These errors showed that a running server is not enough by itself; the request method and route must match the registered Express endpoint. Checking the route definitions and restarting the current server process helped distinguish code problems from an outdated running process.

The first persistent-history implementation used SQLite. It worked locally, but it was not suitable for the selected free Render deployment because the local database file would not provide reliable persistence. I therefore changed the back end to use Neon PostgreSQL. This required a new database driver, asynchronous SQL queries, environment-variable configuration, and additional connection testing.

The Neon connection initially failed with ECONNRESET. Retrying the connection and checking the network helped confirm that the parser and API code were not the cause. A later front-end connection problem was caused by an extra slash at the end of the API base URL, which produced request paths containing //api/.... This was a small mistake, but it showed the value of checking the final request address rather than only reading the surrounding code.

11.3 What I learned

The main lesson from this project was that a complete web application involves more than producing the correct arithmetic result. The request path, HTTP method, response status, JSON structure, database operation, and browser update must all agree.

Implementing the parser also helped me understand why submitted expressions should not be passed directly to eval. Dividing the parser into expression, term, unary, and primary levels provided a safer method and made operator precedence explicit.

Git and GitHub were another important part of the work. The front end and back end were placed in separate repositories, and later changes were committed and pushed independently. Deployment also introduced practical issues that are not visible in local development, including environment variables, cross-origin requests, database persistence, and the delayed first response of a sleeping free service.

11.4 Limitations and possible improvements

The current application meets the assignment requirements, but several parts could be improved. The free Render service may take time to respond after a period of inactivity. The project also relies mainly on manual functional testing. Automated tests for the parser and API routes would make later changes safer.

For a larger public application, cross-origin access should be limited to the actual front-end domain instead of allowing every origin. History pagination, rate limiting, and user-specific records could also be added if the system were extended beyond a single shared calculator.

11.5 Summary

The completed project supports basic and compound arithmetic, server-side validation, persistent history, single-record deletion, clear-all operation, keyboard input, and a responsive interface. The front end, back end, and database are deployed separately and communicate through public HTTPS endpoints.

More importantly, the development process connected several topics that had previously been learned separately: page layout, JavaScript events, HTTP APIs, expression parsing, SQL, Git, and cloud deployment. The final system is small, but it provides a complete example of how these parts work together.

...全文
69 回复 打赏 收藏 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
内容概要:本文研究了基于深度强化学习的多无人机辅助边缘计算网络路径规划方法,旨在通过深度强化学习算法优化多无人机在执行任务时的飞行路径,以提高边缘计算网络的服务质量和效率。文中系统阐述了深度强化学习的基本原理及其在无人机路径规划中的应用,重点解决了动态环境适应性和多目标优化等关键技术难题。研究提出了一种高效的算法框架,能够有效应对复杂环境中的障碍物规避、能耗最小化和任务完成时间最短化等多重挑战。该框架通过Matlab代码实现,并经过大量仿真实验验证,结果表明,相较于传统路径规划方法,所提方法在确保飞行安全的前提下,显著提升了无人机的任务执行效率和网络服务质量,展现了优异的性能表现和广阔的应用前景。; 适合人群:具备一定编程基础,特别是熟悉Matlab编程语言,对无人机技术、边缘计算或深度强化学习感兴趣的科研人员和技术开发者。; 使用场景及目标:①为研究人员提供一种新的无人机路径规划解决方案,特别是在复杂动态环境下;②促进深度强化学习技术在无人机辅助边缘计算网络中的应用和发展;③为相关领域的工程技术人员提供技术支持和参考案例。; 阅读建议:读者在阅读本文时,应重点关注深度强化学习算法的设计思路及其在无人机路径规划中的具体应用,同时结合提供的Matlab代码进行实践操作,以便更好地理解和掌握该技术的核心要点。

91

社区成员

发帖
与我相关
我的任务
社区描述
梅努斯-软件工程
软件工程 高校 福建省·福州市
社区管理员
  • FZU_SE_LQF
  • zjcTander
  • 木村修
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧