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 43 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
|
| const messagesSinceId = messages.filter((message) => message.id > sinceId); | ||
|
|
||
| if (messagesSinceId.length === 0) { | ||
| callBacksForNewMessages.push((value) => res.send(value)); |
There was a problem hiding this comment.
What do you expect to happen if no new message arrives within 30 minutes?
There was a problem hiding this comment.
I expect the connection to be closed by the hosting environment or the browser.
To handle this, I added Unique Client IDs:
Frontend: If the connection times out, my code catches the error and retries using the same ID.
Backend: I replaced the array with an Object. The server uses the Client ID to overwrite the old connection with the new one.
This ensures the server's memory doesn't fill up with 'ghost' connections, even if no messages arrive for 30 minutes.
I have updated the PR description based on your feedback. Thank you. |
- Add check for req.body existence before destructuring - Add type validation to ensure text and sender are strings - Replace simple falsy check with .trim() for whitespace validation - Return specific error messages for each validation failure - Improve error handling clarity with separate validation steps - Prevent type coercion errors and invalid data submission
- Trim whitespace from sender and message inputs - Prevent empty submissions and show alert if fields are blank - Return early from submit handler when validation fails - Improve UX and prevent sending invalid messages
- Add API_BASE_URL placeholder constant
- Replace hardcoded production URLs with `${API_BASE_URL}` for:
- GET /messages
- POST /messages/:id/like
- POST /messages (form submission)
- 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.
| 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().
| 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.
| 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);
}
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.