Initial commit

This commit is contained in:
Vadher
2026-07-19 14:09:43 +05:30
parent 8bf2cda36c
commit ca4d5b3db6
15 changed files with 168424 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
PORT=3000
TIME=60000
+45
View File
@@ -0,0 +1,45 @@
const express = require("express");
const fs = require("fs").promises;
const urlStoreController = require("./../models/url_store_controller");
const healthStatusController = require("./../models/health_status_controller");
const urlPath = "./database/urls.json";
const router = express.Router();
const app = express();
app.use(express.json());
router.post("/url/register", async (req, res) => {
try {
const { url } = req.body;
if (!url) {
return res.status(400).json({ error: "URL is required" });
}
await urlStoreController.addUrl(url);
res.status(201).json({
message: "URL added successfully",
});
} catch (err) {
console.error(err);
res.status(500).json({
error: "Failed to add URL",
});
}
});
router.get("/url", async (req, res) => {
try {
const data = await fs.readFile(urlPath, "utf8");
const urls = JSON.parse(data);
res.json(urls);
} catch (err) {
res.status(500).json({
error: err.message,
});
}
});
module.exports = router;
+26
View File
@@ -0,0 +1,26 @@
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const cors = require("cors");
const path = require("path");
const dotenv = require("dotenv");
const urlRoutes = require("./Routes/url_routes");
const WebSocket = require("ws");
const socket = require("./web_socket_server");
dotenv.config();
app.use(express.static(path.join(__dirname, "../frontend")));
app.use(express.json());
app.use("/api", urlRoutes);
app.get("/api/status", (req, res) => {
res.status(200).json({ status: "Server is running" });
});
const server = app.listen(process.env.PORT || 3000, () => {
console.log(`Server is running on port ${process.env.PORT || 3000}`);
});
const wss = new WebSocket.Server({ server });
socket.setupWebSocket(wss);
File diff suppressed because it is too large Load Diff
+52
View File
@@ -0,0 +1,52 @@
[
"cloudflare.com",
"Akshay",
"postgresql.org",
"http://localhost:3001",
"invalid-url.com",
"github.com",
"random-string",
"mozilla.org",
"todo",
"amazon.com",
"unknown-host-999.com",
"google.com",
"hello world",
"docker.com",
"abc123invalid.org",
"wikipedia.org",
"http://localhost:99999",
"duckduckgo.com",
"myfakeportal.dev",
"nodejs.org",
"example.com",
"ftp://example.com",
"bing.com",
"thissitedoesnotexist123456.com",
"mongodb.com",
"12345",
"reddit.com",
"openai.com",
"https://",
"netflix.com",
"expressjs.com",
"ubuntu.org",
"test-invalid-domain.local",
"microsoft.com",
"http://localhost:8080",
"www.",
"yahoo.com",
"vercel.com",
"brokenwebsite.fake",
"golang.org",
"apple.com",
"digitalocean.com",
"://invalid",
"python.org",
"notarealwebsite987654.io",
"fake-company-xyz.net",
"ubuntu.com",
"stackoverflow.com",
"idontexist.xyz",
"npmjs.com"
]
+20
View File
@@ -0,0 +1,20 @@
const fs = require("node:fs").promises;
const url_path = "./database/db.json";
const StoreRecord = async (urlRecord) => {
try {
const rawData = await fs.readFile(url_path, "utf8");
const data = JSON.parse(rawData);
data.push({
checkedAt: new Date().toISOString(),
status: urlRecord,
});
await fs.writeFile(url_path, JSON.stringify(data, null, 4));
} catch (err) {
console.error("Operation failed:", err);
}
};
module.exports = { StoreRecord };
@@ -0,0 +1,39 @@
const axios = require("axios");
const fs = require("fs").promises;
const path = require("path");
const normalizeUrl = require("../utils/utils").normalizeUrl;
async function checkUrl(url) {
const target = normalizeUrl(url);
try {
const start = Date.now();
const response = await axios.get(target, {
timeout: 10000,
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36",
},
});
const responseTime = Date.now() - start;
return {
url: url,
statusCode: response.status,
responseTime,
status: "UP",
error: null,
checkedAt: new Date(),
};
} catch (err) {
return {
url,
statusCode: err.response?.status || "No Response",
responseTime: null,
status: "DOWN",
error: err.message,
checkedAt: new Date(),
};
}
}
module.exports = { checkUrl };
+16
View File
@@ -0,0 +1,16 @@
const fs = require("node:fs").promises;
const url_path = "./database/urls.json";
const addUrl = async (url) => {
try {
const rawData = await fs.readFile(url_path, "utf8");
const data = JSON.parse(rawData);
data.push(url);
await fs.writeFile(url_path, JSON.stringify(data, null, 4));
} catch (err) {
console.error("Operation failed:", err);
}
};
module.exports = { addUrl };
+15
View File
@@ -0,0 +1,15 @@
{
"name": "uptime_monitor",
"version": "1.0.0",
"description": "simple url status monitor application",
"license": "ISC",
"author": "akshay vadher",
"type": "commonjs",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"dotenv": "^17.4.2"
}
}
+44
View File
@@ -0,0 +1,44 @@
const WebSocket = require("ws");
let wss;
let lastResults = [];
function normalizeUrl(url) {
if (url.startsWith("http://") || url.startsWith("https://")) {
return url;
}
return "http://" + url;
}
function init(webSocketServer) {
wss = webSocketServer;
wss.on("connection", (client) => {
lastResults.forEach((data) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data));
}
});
});
}
function broadcast(data) {
if (!wss) return;
if (data.url) {
const idx = lastResults.findIndex((r) => r.url === data.url);
if (idx >= 0) lastResults[idx] = data;
else lastResults.push(data);
}
wss.clients.forEach((client) => {
if (client.readyState !== WebSocket.OPEN) return;
try {
client.send(JSON.stringify(data));
} catch (err) {
console.error("WebSocket broadcast failed:", err.message);
}
});
}
module.exports = { normalizeUrl, broadcast, init };
+58
View File
@@ -0,0 +1,58 @@
const axios = require("axios");
const fs = require("fs").promises;
const dotenv = require("dotenv");
const { StoreRecord } = require("./models/db_store_controller");
const { checkUrl } = require("./models/health_status_controller");
const { init, broadcast } = require("./utils/utils");
const urlPath = "./database/urls.json";
dotenv.config();
async function setupWebSocket(wss) {
init(wss);
let Store = [];
try {
const urls = await fs.readFile(urlPath, "utf8");
const parsedUrls = JSON.parse(urls);
await Promise.allSettled(
parsedUrls.map(async (url) => {
const response = await checkUrl(url);
broadcast(response);
Store.push(response);
}),
);
StoreRecord(Store);
broadcast({ type: "checking" });
} catch (err) {
console.error("WebSocket polling error:", err.message);
broadcast({
type: "error",
message: "Unable to read urls.json or parse data",
});
}
setInterval(async () => {
try {
const urls = await fs.readFile(urlPath, "utf8");
const parsedUrls = JSON.parse(urls);
await Promise.allSettled(
parsedUrls.map(async (url) => {
const response = await checkUrl(url);
broadcast(response);
Store.push(response);
}),
);
StoreRecord(Store);
broadcast({ type: "checking" });
} catch (err) {
console.error("WebSocket polling error:", err.message);
broadcast({
type: "error",
message: "Unable to read urls.json or parse data",
});
}
}, process.env.TIME || 60000);
}
module.exports = { setupWebSocket };
+49
View File
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>URL HEALTH MONITOR</title>
<script src="scripts.js" defer></script>
</head>
<body>
<div style="display:flex; justify-content:space-between; align-items:flex-start;">
<div>
<h1>URL HEALTH MONITOR</h1>
<h4>WebSocket: <span id="websocket_status">Connecting...</span></h4>
<h4 id="status"></h4>
</div>
<div style="border:1px solid #ccc;padding:10px;border-radius:5px;">
<h3>Add URL</h3>
<input type="text" id="urlInput" placeholder="https://example.com">
<button id="addBtn">
Add
</button>
</div>
</div>
<table border="1">
<thead>
<tr>
<th>URL</th>
<th>Status</th>
<th>Status Code</th>
<th>Response Time</th>
<th>Error</th>
</tr>
</thead>
<tbody id="content-display"></tbody>
</table>
</body>
</html>
+102
View File
@@ -0,0 +1,102 @@
const tbody = document.getElementById("content-display");
async function init() {
try {
const response = await fetch("/api/url");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const urls = await response.json();
urls.forEach((url) => {
const id = url.replace(/[^a-zA-Z0-9]/g, "_");
const row = document.createElement("tr");
row.id = id;
row.innerHTML = `
<td>${url}</td>
<td class="status">Waiting...</td>
<td class="statusCode">-</td>
<td class="responseTime">-</td>
<td class="error">-</td>
`;
tbody.appendChild(row);
});
} catch (err) {
console.error(err);
}
}
document.addEventListener("DOMContentLoaded", init);
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
const socket = new WebSocket(`${protocol}://${window.location.host}`);
console.log(socket);
// const socket = new WebSocket("ws://localhost:3000");
const websocket_status = document.getElementById("websocket_status");
socket.onopen = () => {
websocket_status.textContent = "Connected";
};
let counter = 0
socket.onmessage = (event) => {
// console.log(counter)
const data = JSON.parse(event.data);
if (data.type === "checking") {
counter = counter + 1
console.log(counter)
document.getElementById("status").textContent = "Refreshed " + counter + " Times";
return;
}
const id = data.url.replace(/[^a-zA-Z0-9]/g, "_");
const row = document.getElementById(id);
if (!row) return;
row.querySelector(".status").textContent = data.status;
row.querySelector(".statusCode").textContent = data.statusCode;
row.querySelector(".responseTime").textContent = data.responseTime != null ? `${data.responseTime} ms` : "-";
row.querySelector(".error").textContent = data.error || "-";
};
socket.onclose = () => {
websocket_status.textContent = "Disconnected";
};
document.getElementById("addBtn").addEventListener("click", async () => {
const url = document.getElementById("urlInput").value.trim();
if (!url) return;
const response = await fetch("/api/url/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url })
});
if (response.ok) {
document.getElementById("urlInput").value = "";
const id = url.replace(/[^a-zA-Z0-9]/g, "_");
if (!document.getElementById(id)) {
const row = document.createElement("tr");
row.id = id;
row.innerHTML = `
<td>${url}</td>
<td class="status">Waiting...</td>
<td class="statusCode">-</td>
<td class="responseTime">-</td>
<td class="error">-</td>
`;
tbody.appendChild(row);
}
}
});
+21
View File
@@ -0,0 +1,21 @@
{
"name": "uptime_monitor",
"version": "1.0.0",
"description": "A lightweight **URL Health Monitoring** application built with **Node.js, Express, WebSocket, Axios, and Docker**. The application periodically checks the health of registered URLs and updates the frontend in real time using WebSockets.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Akshay Vadher",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"axios": "^1.18.1",
"body-parser": "^2.3.0",
"cors": "^2.8.6",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"ws": "^8.21.1"
}
}
+343
View File
@@ -0,0 +1,343 @@
# URL Health Monitor
A lightweight **URL Health Monitoring** application built with **Node.js, Express, WebSocket, Axios, and Docker**. The application periodically checks the health of registered URLs and updates the frontend in real time using WebSockets.
## Features
- Real-time URL health monitoring
- Live updates using WebSockets
- Register new URLs without refreshing the page
- Periodic health checks
- Displays:
- URL
- Status (UP/DOWN)
- HTTP Status Code
- Response Time
- Error Message
- Stores monitoring history in JSON format
- Simple frontend built with HTML and JavaScript
- REST API for URL management
- Docker and Docker Compose support
---
# Project Structure
```text
project/
├── backend/
│ ├── database/
│ │ ├── db.json
│ │ └── urls.json
│ │
│ ├── models/
│ │ ├── db_store_controller.js
│ │ ├── health_status_controller.js
│ │ └── url_store_controller.js
│ │
│ ├── routes/
│ │ └── url_routes.js
│ │
│ ├── utils/
│ │ └── utils.js
│ │
│ ├── .env
│ ├── app.js
│ └── web_socket_server.js
├── frontend/
│ ├── index.html
│ └── scripts.js
├── Dockerfile
├── docker-compose.yml
├── package.json
└── README.md
```
---
# Technologies Used
## Backend
- Node.js
- Express.js
- Axios
- WebSocket (ws)
## Frontend
- HTML
- JavaScript
## Storage
- JSON Files
- `urls.json`
- `db.json`
## DevOps
- Docker
- Docker Compose
---
# How It Works
1. The frontend loads all registered URLs from the backend.
2. A WebSocket connection is established.
3. The backend periodically reads all URLs from `urls.json`.
4. Each URL is checked using an HTTP request.
5. Response time and HTTP status are collected.
6. Results are broadcast to all connected clients using WebSockets.
7. The frontend updates the corresponding table row automatically.
8. Every monitoring cycle is stored in `db.json`.
---
# Monitoring Flow
```text
+------------------+
| urls.json |
+------------------+
|
v
Read Registered URLs
|
v
Perform Health Check
(Axios Request)
|
v
Collect Status & Response Time
|
+-------------+-------------+
| |
v v
Broadcast via WebSocket Save History
to Frontend to db.json
| |
+-------------+-------------+
|
v
Update UI in Real Time
```
---
# API Endpoints
## Get All Registered URLs
**Request**
```http
GET /api/url
```
**Response**
```json
["https://google.com", "https://github.com"]
```
---
## Register a New URL
**Request**
```http
POST /api/url/register
```
**Request Body**
```json
{
"url": "https://openai.com"
}
```
**Response**
```json
{
"message": "URL added successfully"
}
```
---
# WebSocket
A WebSocket connection is established when the frontend loads.
The server continuously broadcasts health updates to all connected clients.
Example message:
```json
{
"url": "https://google.com",
"status": "UP",
"statusCode": 200,
"responseTime": 84,
"error": null
}
```
The frontend updates the corresponding table row instantly without requiring a page refresh.
---
# Monitoring History
Each monitoring cycle is stored in `db.json`.
Example:
```json
[
{
"checkedAt": "2026-07-19T07:26:54.182Z",
"status": [
{
"url": "https://google.com",
"status": "UP",
"statusCode": 200,
"responseTime": 78,
"error": null
},
{
"url": "https://invalid-url.com",
"status": "DOWN",
"statusCode": "No Response",
"responseTime": null,
"error": "Request failed"
}
]
}
]
```
---
# Frontend Features
The frontend displays:
- URL
- Current Status
- HTTP Status Code
- Response Time
- Error Message
- WebSocket Connection Status
- Refresh Counter
- Add URL Form
New URLs appear automatically without refreshing the page.
---
# Error Handling
The application handles:
- Invalid URLs
- Connection refused
- Timeout errors
- HTTP error responses
- WebSocket disconnections
- File read/write errors
---
# Configuration
The monitoring interval can be modified in:
```
backend/web_socket_server.js
```
Example:
```javascript
setInterval(async () => {
// Perform health checks
}, 5000);
```
---
# Docker Support
The project can be run using Docker and Docker Compose.
## Build the Docker image
```bash
docker compose build
```
## Start the application
```bash
docker compose up
```
Run in detached mode:
```bash
docker compose up -d
```
## Stop the application
```bash
docker compose down
```
The application will be available at:
```
http://localhost:3000
```
The `backend/database` directory can be mounted as a Docker volume so that `urls.json` and `db.json` persist across container restarts.
---
# Future Improvements
- Delete registered URLs
- Edit existing URLs
- Pause/Resume monitoring
- Configurable polling interval
- Search and filter URLs
- Dashboard with charts
- Authentication
- MongoDB or PostgreSQL storage
- Email or Slack notifications
- Historical analytics
- Export monitoring reports (CSV/PDF)
---
# Author
**Akshay Vadher**
Backend Developer
---
# License
This project is licensed under the MIT License.