Only show guides for your app
A live uptime page in three files — it fetches your other servers and sites on a timer, marks each up or down with a hard timeout, and shows the result. Deploy it, point it at your services, extend it.
If you run more than one thing — a bot's API, a website, a game server's query port — a status page is the single place that tells you, at a glance, what's up and what's down. This one checks each service on a timer with fetch, uses a hard timeout so a dead server can't hang the check, and shows a green or red dot per service. Three files on the Node.js application.
| At a glance | |
|---|---|
| You're building | A live status/uptime dashboard for your own services |
| You need | A Falix server running the Node.js application |
| Plan | Any — free runs while your session timer has time, premium runs 24/7 |
| Time | About thirty minutes |
New to Node here? Start with Node.js on Falix.
| Feature | How |
|---|---|
| Check services | fetch each URL from sites.json and record up/down |
| Hard timeout | An AbortController fails a hung request after 5 seconds |
| Live re-check | Checks once at boot, then every minute |
| Status API | GET /api/status returns the latest results as JSON |
| Dashboard | public/index.html polls the API and draws the dots |
package.json — only Express is needed; fetch is built into Node:
{
"name": "status-page",
"version": "1.0.0",
"private": true,
"main": "index.js",
"dependencies": {
"express": "^4.21.2"
}
}
sites.json — the things to watch. Edit this to list your own:
[
{ "name": "My website", "url": "https://example.com" },
{ "name": "My API", "url": "https://api.github.com" },
{ "name": "My bot's API", "url": "http://123.45.67.89:25570/health" }
]
index.js:
const express = require('express');
const path = require('node:path');
const fs = require('node:fs');
// The list of things to watch lives in sites.json.
const sites = JSON.parse(fs.readFileSync(path.join(__dirname, 'sites.json'), 'utf8'));
// The latest result for each site, updated in place.
const state = sites.map((s) => ({
name: s.name, url: s.url, status: 'checking', code: null, ms: null, checkedAt: null,
}));
async function checkOne(url) {
const started = Date.now();
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000); // 5s timeout
try {
const res = await fetch(url, { signal: controller.signal, redirect: 'manual' });
const ok = res.status >= 200 && res.status < 400;
return { status: ok ? 'up' : 'down', code: res.status, ms: Date.now() - started };
} catch {
// Timed out, refused, DNS failure — anything that isn't a real response.
return { status: 'down', code: null, ms: Date.now() - started };
} finally {
clearTimeout(timer);
}
}
async function checkAll() {
await Promise.all(state.map(async (row) => {
Object.assign(row, await checkOne(row.url), { checkedAt: new Date().toISOString() });
}));
}
checkAll(); // check once at boot
setInterval(checkAll, 60_000).unref(); // then every minute
const app = express();
app.get('/api/status', (req, res) => res.json(state));
app.use(express.static(path.join(__dirname, 'public')));
const PORT = process.env.SERVER_PORT || 8080;
app.listen(PORT, '0.0.0.0', () => console.log(`Listening on port ${PORT}`));
public/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Status</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 40rem; margin: 3rem auto; }
.site { display: flex; align-items: center; gap: 0.8rem; padding: 0.8rem 0; border-bottom: 1px solid #eee; }
.dot { width: 0.9rem; height: 0.9rem; border-radius: 50%; background: #999; }
.up .dot { background: #22c55e; }
.down .dot { background: #ef4444; }
.name { flex: 1; font-weight: 600; }
.meta { color: #666; font-size: 0.85rem; }
</style>
</head>
<body>
<h1>Service status</h1>
<div id="list">Loading...</div>
<script>
async function refresh() {
const sites = await (await fetch('/api/status')).json();
document.getElementById('list').innerHTML = sites.map((s) =>
'<div class="site ' + s.status + '"><span class="dot"></span>' +
'<span class="name">' + s.name + '</span>' +
'<span class="meta">' + s.status.toUpperCase() +
(s.ms != null ? ' · ' + s.ms + 'ms' : '') + '</span></div>'
).join('');
}
refresh();
setInterval(refresh, 15000);
</script>
</body>
</html>
fetch to a dead server can hang for a long time — long enough to stall every other check. AbortController plus a setTimeout cancels the request after 5 seconds, and the catch turns that into a clean down. Verified against a genuinely unroutable address, the check gives up at exactly 5000 ms.2xx or 3xx counts as up; anything else — a 5xx, a refused connection, a DNS failure, a timeout — is down. redirect: 'manual' means a redirect is treated as a valid response (the site answered) rather than being followed.Promise.all fires every check at once, so ten services take about as long as the slowest one, not the sum.state array holds the latest result and is updated in place; /api/status just serves it. No database needed — a status page only cares about now.🎯 Good to know: Checking your other Falix servers is exactly what this is for. Use each server's public address and port from its Network page. If a service has a
/healthroute (a great habit — see the healthchecks guide), point at that instead of the front page so you're testing the app, not just the port.
public for index.html), or deploy from Git. Put your real services in sites.json.npm install, the console prints Listening on port … and the server goes online.The server reads SERVER_PORT and binds 0.0.0.0 already, so nothing to configure for the port. Change sites.json and restart to pick up a new list.
curl http://YOUR_ADDRESS:PORT/api/status
# -> [{"name":"My website","status":"up","code":200,"ms":21, ...}, ...]
fetch; for now, point at a service's web/health port.The check itself is standard fetch and AbortController — the MDN fetch documentation covers options like headers and methods.
/api/status (the app didn't start) or sites.json is invalid JSON. Read the console; a bad sites.json throws on boot.sites.json is an empty array, or a JSON syntax error stopped the parse. Validate the file.