Skip to content

West Midlands | 26 March SDC | Iswat Bello | Sprint 2 | chat app#83

Open
Iswanna wants to merge 58 commits into
CodeYourFuture:mainfrom
Iswanna:feature/chat-app
Open

West Midlands | 26 March SDC | Iswat Bello | Sprint 2 | chat app#83
Iswanna wants to merge 58 commits into
CodeYourFuture:mainfrom
Iswanna:feature/chat-app

Conversation

@Iswanna
Copy link
Copy Markdown

@Iswanna Iswanna commented May 25, 2026

Learners, PR Template

Self checklist

  • I have titled my PR with Region | Cohort | FirstName LastName | Sprint | Assignment Title
  • My changes meet the requirements of the task
  • I have tested my changes
  • My changes follow the style guide

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.

Iswanna added 30 commits May 9, 2026 23:26
- 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
Iswanna added 5 commits May 28, 2026 11:10
- 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
@Iswanna Iswanna added Needs Review Trainee to add when requesting review. PRs without this label will not be reviewed. and removed Reviewed Volunteer to add when completing a review with trainee action still to take. labels May 29, 2026
@Iswanna Iswanna requested a review from cjyuan May 29, 2026 13:20
Copy link
Copy Markdown

@cjyuan cjyuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just have a few more suggestions.

Comment thread chat-app/backend/server.js Outdated
Comment on lines +77 to +83
callBacksForNewMessages[clientId] = (value) => {
try {
res.send(value);
} catch (e) {
delete callBacksForNewMessages[clientId];
}
};
Copy link
Copy Markdown

@cjyuan cjyuan May 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have decided to revert to the array-based approach to keep my code robust. Thank you for the feedback.

Comment on lines +9 to +13
const response = await fetch(
`${API_BASE_URL}/messages?since=${lastIdSeen}&clientId=${myClientId}`,
);

const data = await response.json();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread chat-app/frontend/script.js Outdated
Comment on lines +15 to +21
const messageContainer = document.getElementById("all-messages");

data.forEach((message) => {
const elementId = "msg-" + message.id;

const existingElement = document.getElementById(elementId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have refactored the code to separate the presentation logic into its own function, renderMessages(data), as suggested.

Comment thread chat-app/frontend/script.js Outdated
Comment on lines +62 to +66
setTimeout(getAllMessages, 0);
} catch (error) {
setTimeout(getAllMessages, 0);
console.error("Error fetching messages:", error);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could consider using a finally block:

try {
   ...
} catch (error) {
}
finally {
  setTimeout(getAllMessages, 0);
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have refactored the getAllMessages function to use a finally block for the recursive setTimeout call.

Thank you for the feedback.

@cjyuan cjyuan added Reviewed Volunteer to add when completing a review with trainee action still to take. and removed Needs Review Trainee to add when requesting review. PRs without this label will not be reviewed. labels May 29, 2026
Iswanna added 5 commits June 1, 2026 13:10
… 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
Copy link
Copy Markdown

@cjyuan cjyuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good. Well done.

Comment thread chat-app/frontend/script.js Outdated
}

const existingElement = document.getElementById(elementId);
function renderMessages(data) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

messages would probably a clearer name for this parameter.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have replaced the parameter data with messages in the renderMessages function.

@cjyuan cjyuan added Complete Volunteer to add when work is complete and all review comments have been addressed. and removed Reviewed Volunteer to add when completing a review with trainee action still to take. labels Jun 1, 2026
…essages`

- Improve clarity by using `messages` as the function parameter and loop input
- Update internal forEach to iterate `messages` instead of `data`
@Iswanna
Copy link
Copy Markdown
Author

Iswanna commented Jun 1, 2026

Changes look good. Well done.

Thank you so much for the detailed PR review. I really appreciate it.

Iswanna added 9 commits June 1, 2026 23:58
- 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Complete Volunteer to add when work is complete and all review comments have been addressed. Module-Decomposition The name of the module. 📅 Sprint 2 Assigned during Sprint 2 of this module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants