West Midlands | 26 March SDC | Iswat Bello | Sprint 2 | chat app#83
West Midlands | 26 March SDC | Iswat Bello | Sprint 2 | chat app#83Iswanna wants to merge 58 commits into
Conversation
- Exclude node_modules/ and package-lock.json from version control - Ignore environment variables (.env) and system files (.DS_Store) - Exclude build artifacts (dist/, build/) and log files (*.log) - Ignore VS Code workspace settings (.vscode/) - Reduce repository size and prevent committing sensitive data
- Import Express library - Create Express app instance - Initialize messages array to store chat data - Configure server port (default: 3000) - Start server with app.listen()
- Add JSON body parsing middleware - Implement POST /messages route with input validation - Validate text and sender fields are provided and non-empty - Create message object with id, sender, text, likes, and dislikes - Store message in messages array - Return created message with 201 status code - Return 400 error for invalid requests
- Implement GET /messages route - Return all messages from the messages array - Enable clients to fetch chat history
- Add index.html with semantic HTML structure - Include meta tags for charset and viewport - Link external stylesheet (styles.css) - Link deferred JavaScript file (script.js) - Create form with sender name and message inputs - Add submit button for sending messages - Create messages container div for displaying chat history
- Create getAllMessages async function to fetch messages from backend - Parse JSON response from /messages endpoint - Clear message container and render new messages - Display sender name and message text for each message - Add error handling for fetch failures - Call getAllMessages on page load to populate initial messages
- Import cors module for CORS support - Fix environment variable casing (process.env.port → process.env.PORT) - Add CORS middleware before other middleware - Move message creation logic inside POST route handler - Fix indentation for code inside route handlers - Properly close POST route handler with closing brace
- Configure Node.js project with ES modules ("type": "module")
- Add Express 5.2.1 for HTTP server framework
- Add CORS 2.8.6 for cross-origin request handling
- Enable npm package management for backend
- Add id="chat-form" to form for JavaScript event listener access - Change textarea to input element for message field - Maintain labels and input structure for sender name and message
- Get form, sender input, and message input elements from DOM - Add submit event listener to chat form - Prevent default form submission behavior - Extract sender name and message text from input values - Send POST request to /messages endpoint with JSON payload - Include Content-Type header for JSON data - Refresh message list on successful submission - Clear input fields after sending message
- Replace single getAllMessages() call with setInterval - Fetch messages from backend every 5000ms (5 seconds) - Enable live chat updates without manual refresh - Keep frontend synchronized with backend message list
- Change mismatched textarea closing tag to self-closing input tag
- Add scripts section with start command - Enable running backend with npm start instead of node server.js - Simplify server startup process - Remove trailing newline from file
- Change getAllMessages() fetch URL from localhost to Render production URL - Update form submission fetch URL to production backend - Enable frontend to communicate with deployed Render backend - Remove dependency on local development server
…king - Add lastIdSeen variable to track last viewed message ID - Update getAllMessages() to use ?since query parameter - Only fetch messages newer than lastIdSeen from backend - Remove messageContainer.textContent clearing to prevent flicker - Update lastIdSeen after each message is displayed - Reduce data transfer by only fetching new messages - Improve performance with incremental polling
- Parse since query parameter from request - Convert since value to number, default to -1 if undefined - Filter messages array to only return messages with id > sinceId - Handle edge case where since=0 evaluates as falsy - Enable pagination and incremental message loading - Reduce payload size by sending only new messages
- Wrap form submission logic in try-catch block - Handle network errors and fetch failures gracefully - Log errors to console for debugging - Prevent app from crashing on failed message submission - Improve user experience with better error handling - Reformat fetch call for better readability
- Replace setInterval with setTimeout for sequential polling - Add setTimeout in try block to schedule next poll after successful fetch - Add setTimeout in catch block to retry polling after error - Call getAllMessages() once on page load instead of starting interval - Ensure each poll waits for previous request to complete - Prevent overlapping requests and race conditions - Improve reliability with error recovery
- Add callBacksForNewMessages array to store pending response handlers - Modify GET /messages endpoint to implement long polling - When no new messages available (messagesSinceId.length === 0), store response callback - When new messages arrive, send them immediately via callback - Enable real-time message delivery without constant polling - Reduce server load by holding requests until new data is available
…lopment - Change getAllMessages() fetch URL from Render to localhost:3000 - Change form submission fetch URL from Render to localhost:3000 - Remove getAllMessages() call after successful message submission - Rely on long polling to fetch new messages automatically - Enable local development workflow - Simplify form submission logic
- Create route to handle message like requests - Extract message ID from URL parameter - Convert ID string to number for comparison - Find message by ID in messages array - Increment likes count for the message - Enable users to like messages in real-time
…like - Check if messageWithIdAsNumber exists after incrementing likes - Notify all waiting clients via callBacksForNewMessages callbacks - Send updated message with 200 status on success - Return 404 error if message not found - Enable real-time like updates via long polling - Improve error handling for invalid message IDs
- Extract current like count from likeSpan text content - Increment likes count immediately when user clicks Like button - Update UI before server response for instant feedback - Maintain consistency with backend data on next poll - Improve user experience with responsive interface
- Style message containers with border, padding, and rounded corners - Add light gray background color (#f9f9f9) to messages - Add spacing between message elements with margin-right - Style buttons with blue background (#007bff) and white text - Add cursor pointer and rounded corners to buttons - Improve visual presentation of chat interface
- Change getAllMessages() fetch URL from Render to CodeYourFuture hosting - Update like button fetch URL to CodeYourFuture backend - Update form submission fetch URL to CodeYourFuture backend - Enable frontend to communicate with CodeYourFuture deployed backend - Replace Render production URL with CodeYourFuture hosting domain
- Remove npm start script configuration - Simplify package.json to only include dependencies - Users will run server with direct node command instead
- Replace placeholder API_BASE_URL with deployment URL - Generate per-client ID with crypto.randomUUID() (myClientId) - Append clientId to GET /messages?since=... requests to support server long-poll tracking
- Replace callBacksForNewMessages array with object keyed by clientId - Store GET /messages callbacks under clientId and return [] if clientId missing - Broadcast new messages and likes to all waiting clients via Object.values(...) - Clear waiting-room after notifying clients to avoid double-sends - Add try/catch removal behavior when sending callback responses - Ensure 404 check before incrementing likes in POST /messages/:id/like - Add API_BASE_URL and per-client myClientId (with localhost fallback commented)
- Create dataToSendToClient containing only message id and likes - Notify long-polling clients with the minimized payload instead of the full message object - Respond to the liker with the same compact payload - Reduce payload size and clarify notification format
- Replace messages.find(...) with messages[idAsNumber] for direct lookup (assumes id == array index) - Construct compact dataToSendToClient object and fix trailing comma/terminator formatting
| callBacksForNewMessages[clientId] = (value) => { | ||
| try { | ||
| res.send(value); | ||
| } catch (e) { | ||
| delete callBacksForNewMessages[clientId]; | ||
| } | ||
| }; |
There was a problem hiding this comment.
Note: The new logic involving clientId might complicate things.
If the client becomes unreachable for any reason, res.send(value) may not throw an error. As a result, the server may retain references to disconnected clients and gradually leak memory.
Your previous approach (which you clear the waiting room after sending a response) is actually quite robust. If a client is unreachable when the server calls res.send(value), the response simply never reaches the client. As long as the client can detect lost connection, it can always issue a new request to re-establish communication.
Note: You can keep this mechanism as is. No change needed.
There was a problem hiding this comment.
I have decided to revert to the array-based approach to keep my code robust. Thank you for the feedback.
| const response = await fetch( | ||
| `${API_BASE_URL}/messages?since=${lastIdSeen}&clientId=${myClientId}`, | ||
| ); | ||
|
|
||
| const data = await response.json(); |
There was a problem hiding this comment.
A better and more robust pattern is:
try {
const res = await fetch(url, { signal });
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const data = await res.json();
} catch (err) {
console.error(err);
}
This way, the error caught in the catch block would be an error about the HTTP response and not a decoding error thrown by res.json().
There was a problem hiding this comment.
I have implemented the robust fetch pattern in getAllMessages as suggested. It now checks !response.ok and throws a specific HTTP error, which ensures that the catch block handles server errors correctly and avoids decoding errors in response.json().
Thanks for the detailed feedback
| const messageContainer = document.getElementById("all-messages"); | ||
|
|
||
| data.forEach((message) => { | ||
| const elementId = "msg-" + message.id; | ||
|
|
||
| const existingElement = document.getElementById(elementId); | ||
|
|
There was a problem hiding this comment.
If the presentation logic (lines 15 to 61) is kept in a separate function that take messages as its parameter, it would make the rendering logic easier to develop and test independently.
There was a problem hiding this comment.
I have refactored the code to separate the presentation logic into its own function, renderMessages(data), as suggested.
| setTimeout(getAllMessages, 0); | ||
| } catch (error) { | ||
| setTimeout(getAllMessages, 0); | ||
| console.error("Error fetching messages:", error); | ||
| } |
There was a problem hiding this comment.
Could consider using a finally block:
try {
...
} catch (error) {
}
finally {
setTimeout(getAllMessages, 0);
}
There was a problem hiding this comment.
I have refactored the getAllMessages function to use a finally block for the recursive setTimeout call.
Thank you for the feedback.
… clientId - Change callBacksForNewMessages from object back to array - Store callbacks in an array and notify clients by popping callbacks - Remove per-client clientId handling from frontend polling (drop myClientId and clientId query) - Simplify server notification flow for new messages and like events - Reduce complexity of long-poll implementation
- Add response.ok guard and throw on HTTP error - Prevent parsing JSON for non-OK responses to avoid runtime errors - Improves robustness of long-polling fetch and triggers existing error handling
- Move DOM creation and update logic into new renderMessages(data) function - getAllMessages now delegates rendering, schedules next poll, and logs fetch errors
- Move setTimeout(getAllMessages, 0) into a finally block - Remove duplicate scheduling in try/catch paths - Ensure polling continues after success or error
| } | ||
|
|
||
| const existingElement = document.getElementById(elementId); | ||
| function renderMessages(data) { |
There was a problem hiding this comment.
messages would probably a clearer name for this parameter.
There was a problem hiding this comment.
I have replaced the parameter data with messages in the renderMessages function.
…essages` - Improve clarity by using `messages` as the function parameter and loop input - Update internal forEach to iterate `messages` instead of `data`
Thank you so much for the detailed PR review. I really appreciate it. |
- Add backend/long-polling-server.js with Express + CORS setup - Implement POST /messages with input validation and message storage - Implement GET /messages supporting ?since= for incremental loading and long-poll callback registration - Implement POST /messages/:id/like to increment likes and notify waiting clients - Start server on process.env.PORT || 3000 - Enables real-time updates via long-polling
- Add http server and websocket (websocket) integration wrapping the Express app - Track active WebSocket connections and handle disconnects - Broadcast newly posted messages to all connected clients via sendUTF - Start the combined HTTP+WS server with server.listen instead of app.listen
…lients
- On WS connect, parse ?since and send missed messages as { command: "new-message", payload }
- Broadcast newly posted messages to all active WS connections
- Broadcast like updates as { command: "update-like", payload: { id, likes } }
- Preserve long-poll fallback by still invoking stored callbacks
- Fix connection cleanup variable name
…caffold - Delete backend/long-polling-server.js (legacy long-poll implementation) - Add frontend/index-websocket.html and frontend/script-websocket.js - script-websocket implements getAllMessages polling, renderMessages, like POSTs and API_BASE_URL - Keeps continuous polling with response.ok guard and DOM update handling - Prepare project for WebSocket integration / modern client scaffold
- Add `websocket` and `http` to backend/package.json - Replace frontend long-poll fetch with WebSocket client (script-websocket.js) - Handle `new-message` and `update-like` WS commands and render payloads - Prepare project for real-time WebSocket updates
- Add `dislikes` field to messages and implement POST /messages/:id/dislike
- Send compact counter payloads `{ id, likes, dislikes }` to clients
- Replace WS/long-poll update command with `"update-counter"`
- Update frontend (script.js & script-websocket.js) to render dislike counts and add Dislike button/handler
- Notify both WebSocket and long-poll clients and clear waiting callbacks after broadcasting
- Minor formatting and cleanup fixes
…dcast compact updates
- Add broadcastCounterUpdate(data) and findMessageOrError(req, res) helpers
- Consolidate POST /messages/:id/like to use helper and return compact { id, likes, dislikes }
- Implement POST /messages/:id/dislike and broadcast the same compact payload
- Notify long-poll and WS clients with the minimized update payload
- Minor: reformat API_BASE_URL in frontend
…ates
- Extract isValidMessage to validate/normalize POST /messages input and use requestBody when creating messages
- Add dislikes field to messages and implement POST /messages/:id/dislike
- Introduce broadcastCounterUpdate helper to send compact {id, likes, dislikes} payloads to WS and long-poll clients
- Add findMessageOrError helper and use early returns for missing messages
- Minor formatting and cleanup
Learners, PR Template
Self checklist
Changelist
This PR implements a fully functional real-time chat application with a Node.js/Express backend and vanilla JavaScript frontend. The application supports sending messages, viewing chat history, and liking messages with real-time updates using long polling.
Deployed sites
Additional Feature implemented
Interactive Reaction System: I implemented a "Like" button for each message. This required creating a custom API endpoint (/messages/:id/like) and logic to update the state of existing messages without re-rendering the entire chat.
Performance Optimisation (Long Polling): To make the chat feel "live" without overworking the server, I implemented Long Polling. This ensures messages appear instantly while reducing the number of empty requests.