A full-stack chat application built to explore client-server communication, HTTP polling, RESTful API design, and real-time communication with WebSockets and Socket.IO.
The project started as a simple polling-based chat application and was progressively improved by introducing RESTful API operations, request validation, error handling, environment variables, CORS configuration, message editing and deletion, and improved reaction handling.
The project also forms part of a broader exploration of different approaches to real-time communication, including HTTP polling, native WebSockets, and Socket.IO.
- Frontend: https://mc-chatapp.netlify.app/
- Backend API: https://chat-app-backend-gr6s.onrender.com/
Note: The backend is hosted on Render's free tier. After a period of inactivity, the first request may take approximately 30–60 seconds while the service starts.
This project is a full-stack chat application designed to explore how different technologies can be used to enable communication between clients and a server.
The primary implementation uses HTTP polling. The frontend periodically sends requests to the Express backend to check for updated messages.
The backend exposes a RESTful API that allows clients to create, retrieve, update, delete, and react to messages.
Users can:
- Join the chat with a username
- Send messages
- View messages from other users
- Edit messages
- Delete messages
- Like and dislike messages
- Receive updated messages through HTTP polling
The frontend communicates with the backend using the JavaScript Fetch API.
One of the main learning goals of this project was understanding different approaches to client-server communication.
The main application uses HTTP polling.
The client periodically requests the latest messages from the server:
Client
│
│ GET /messages
▼
Server
│
│ Return messages
▼
Client
│
│ Wait
▼
GET /messages
│
▼
Repeat
This approach is simple and works with standard HTTP requests, but it can generate unnecessary requests when there are no new messages.
I also explored native WebSockets to understand how persistent, two-way communication works between a client and server.
Unlike polling, WebSockets allow the server to send information to connected clients without the client having to repeatedly request updates.
WebSocket implementation: https://github.com/Ify-O/Chat-App-WebSocket
I also explored Socket.IO as another approach to real-time communication.
Socket.IO provides an abstraction around real-time connections and includes additional features that can simplify communication between clients and servers.
Socket.IO implementation: Add your Socket.IO file/repository link here.
| Approach | Communication | Server Push | Main Learning |
|---|---|---|---|
| HTTP Polling | Repeated HTTP requests | No | REST APIs and polling |
| WebSockets | Persistent connection | Yes | Native real-time communication |
| Socket.IO | Persistent connection | Yes | Higher-level real-time communication |
Exploring all three approaches helped me understand why WebSockets are generally more suitable for applications that require frequent real-time updates, while polling can be useful for simpler applications and learning how HTTP-based communication works.
- Join the chat using a username
- Send messages
- View messages from multiple users
- Automatic message updates using HTTP polling
- Display message timestamps
- Retrieve all messages
- Retrieve an individual message
- Create messages
- Edit existing messages
- Delete messages
- Validate message input before processing requests
- Like messages
- Dislike messages
- Update reaction counts through API requests
- Handle invalid message IDs
- RESTful API architecture
- Appropriate HTTP methods
- HTTP status codes
- JSON request and response handling
- API error handling
- Request validation
- CORS configuration
- Environment variable configuration
- Custom request logging middleware
- Node.js
- Express.js
- CORS
- REST API
- dotenv
- JavaScript
- HTML5
- CSS3
- JavaScript (ES6)
- Fetch API
- HTTP Polling
- Native WebSockets
- Socket.IO
- Git
- GitHub
- Netlify
- Render
The backend follows REST principles by using HTTP methods according to the operation being performed.
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
API health check |
| GET | /messages |
Retrieve all messages |
| GET | /messages/:id |
Retrieve one message |
| POST | /messages |
Create a new message |
| PATCH | /messages/:id |
Edit a message |
| DELETE | /messages/:id |
Delete a message |
| POST | /messages/:id/like |
Like a message |
| POST | /messages/:id/dislike |
Dislike a message |
Returns the current status of the API.
{
"status": "Polling Chat API is running"
}Status: 200 OK
Retrieves all messages.
[
{
"id": 123456789,
"username": "Ifeoma",
"text": "Hello!",
"senderId": "abc123",
"likes": 2,
"dislikes": 0,
"timestamp": 123456789
}
]Status: 200 OK
Retrieves a single message using its ID.
GET /messages/123456789
If the message exists, the API returns the message object.
If the message does not exist:
{
"error": "Message not found."
}Status: 404 Not Found
Creates a new message.
{
"username": "Ifeoma",
"text": "Hello everyone!",
"senderId": "abc123"
}The API returns the newly created message.
Status: 201 Created
If the message is empty:
{
"error": "Message cannot be empty."
}Status: 400 Bad Request
Updates an existing message.
PATCH /messages/123456789
{
"text": "Updated message"
}The API validates the message ID and request body before updating the message.
Successful status: 200 OK
If the message cannot be found:
Status: 404 Not Found
Deletes an existing message.
DELETE /messages/123456789
If the message exists, it is removed from the application's message store.
Successful status: 200 OK
If the message cannot be found:
Status: 404 Not Found
Adds a like to a message.
POST /messages/123456789/like
Successful status: 200 OK
If the message does not exist:
Status: 404 Not Found
Adds a dislike to a message.
POST /messages/123456789/dislike
Successful status: 200 OK
If the message does not exist:
Status: 404 Not Found
The API uses HTTP status codes to communicate the result of requests.
| Status | Meaning | Example |
|---|---|---|
200 |
OK | Successful retrieval, update, deletion, or reaction |
201 |
Created | Message successfully created |
400 |
Bad Request | Invalid or missing request data |
404 |
Not Found | Requested message does not exist |
500 |
Internal Server Error | Unexpected server-side error |
HTTP is a request-response protocol. Unlike WebSockets, the server cannot automatically push new messages to the client.
The frontend therefore periodically requests the latest messages.
The application uses a recursive setTimeout() approach:
async function startPolling() {
await fetchMessages();
setTimeout(() => {
startPolling();
}, 1000);
}The client:
- Sends
GET /messages - Receives the current messages
- Updates the interface when necessary
- Waits approximately one second
- Sends another request
- Repeats the process
Using setTimeout() after the request completes also helps prevent multiple polling requests from running simultaneously if a request takes longer than expected.
The Express backend demonstrates several types of middleware.
app.use(express.json());Parses JSON request bodies so the server can access data through:
req.bodyCORS allows the frontend and backend to communicate when they are hosted on different origins.
The allowed frontend URL is configured through an environment variable.
CLIENT_URL=https://mc-chatapp.netlify.appThe application includes custom middleware for monitoring incoming requests.
app.use((req, res, next) => {
console.log(
`${new Date().toISOString()} | ${req.method} ${req.url}`
);
next();
});This helps monitor API activity during development and debugging.
The API validates incoming data before processing requests.
For example, messages cannot be empty.
if (!text || !text.trim()) {
return res.status(400).json({
error: "Message cannot be empty."
});
}Validation helps prevent invalid data from entering the application.
The frontend checks API responses before processing them.
For example:
if (!response.ok) {
throw new Error("Unable to fetch messages.");
}The backend also returns meaningful error responses when requests fail.
Example:
{
"error": "Message not found."
}This allows the frontend to distinguish between successful and unsuccessful requests.
Environment variables are used to keep configuration separate from application code.
The backend uses a .env file for values such as:
PORT=3002
CLIENT_URL=http://localhost:5500The .env file is excluded from Git using .gitignore.
A .env.example file is included so that other developers know which environment variables are required without exposing the actual values.
Example:
PORT=3002
CLIENT_URL=http://localhost:5500Never commit the actual
.envfile or sensitive credentials to GitHub.
Chat-App/
│
├── Polling/
│ │
│ ├── Backend/
│ │ ├── node_modules/
│ │ ├── .env
│ │ ├── .env.example
│ │ ├── .gitignore
│ │ ├── package.json
│ │ ├── package-lock.json
│ │ └── server.js
│ │
│ └── Frontend/
│ ├── index.html
│ ├── script.js
│ ├── style.css
│ └── README.md
│
├── Screenshots/
│ ├── join-screen.png
│ └── polling-chat.png
│
└── README.md
node_modulesand.envshould not be committed to GitHub. They are shown above only to illustrate the local project structure.
The project separates the frontend and backend.
The static frontend is deployed using Netlify.
The Express REST API is deployed using Render.
Browser
│
│ HTTPS requests
▼
Netlify
Frontend
│
│ REST API requests
▼
Render
Express Backend
│
▼
In-memory message storage
This architecture demonstrates how a separately hosted frontend can communicate with a backend API.
- Simple to implement
- Uses standard HTTP requests
- Easy to understand and debug
- Works with standard browser APIs
- Does not require a persistent connection
- Useful for learning client-server communication and REST APIs
- It is not true real-time communication
- Requests are sent even when there are no new messages
- Frequent requests can increase network traffic
- It can increase server workload
- Updates can be delayed depending on the polling interval
- Less efficient than persistent connections for frequent real-time updates
The application currently uses in-memory storage on the backend.
Messages are stored in a JavaScript array while the server is running.
This was intentionally used to keep the project focused on understanding:
- REST APIs
- HTTP methods
- Request/response cycles
- Polling
- CRUD operations
Because the data is stored in memory, messages are lost whenever the backend server restarts.
Building and progressively improving this project helped me understand:
- RESTful API design
- CRUD operations
- HTTP methods:
GET,POST,PATCH, andDELETE - HTTP status codes
- Request and response handling
- Express routing
- Express middleware
- CORS
- Request validation
- API error handling
- Environment variables
- Fetch API
- Client-server communication
- HTTP polling
- Native WebSockets
- Socket.IO
- The differences between polling and persistent connections
- Frontend and backend deployment
The project also helped me understand that different communication technologies solve different problems. Building the same type of application using multiple approaches provided practical experience beyond simply following a single implementation.
Potential future improvements include:
- Persistent message storage using a database
- User authentication and authorisation
- User-specific permissions for editing and deleting messages
- Improved reaction logic to prevent repeated reactions
- Typing indicators
- Read receipts
- Private conversations
- Pagination for large numbers of messages
- Automated API testing
- API documentation using OpenAPI/Swagger
- Improved frontend error messages
- Migration of the primary application from polling to WebSockets for true real-time communication
This project is part of my continued development in software development, backend APIs, cloud technologies, and real-time communication.
The project began as an introduction to HTTP polling and gradually evolved into a broader exploration of backend development.
By implementing and comparing HTTP polling, RESTful APIs, native WebSockets, and Socket.IO, I was able to develop a stronger understanding of how frontend applications communicate with backend services and how different architectures affect real-time applications

