DomainViz – Universal Network Intelligence Powered by Cloudflare Workers
Project Overview
DomainViz is an open‑source platform that turns the fragmented world of WHOIS, RDAP, and DNS data into a single, easy‑to‑use service. Built on Cloudflare Workers, it lets you look up domains, IP addresses (IPv4 / IPv6), and Autonomous System Numbers (ASNs) from anywhere on the globe, store historical snapshots, and receive automated change alerts.
Why does it matter?
- Security analysts can trace ownership changes instantly.
- DevOps teams get reliable DNS records without juggling multiple APIs.
- Researchers obtain bulk data and historical context without paying for third‑party services.
All of this runs on a serverless edge network, giving millisecond‑scale response times and global availability.
Key Features
| Feature | What It Does |
|---|---|
| Universal Input Support | Accepts domains, IPv4, IPv6, and ASNs in a single endpoint; the system auto‑detects the type. |
| Bulk Lookups (≤ 50 items) | One request can process up to 50 queries, returning per‑item status flags such as Ok, Locked, Hold, Pending. |
| Historical WHOIS / RDAP | Authenticated users can view time‑stamped snapshots of WHOIS/RDAP data, useful for ownership investigations. |
| Comprehensive IP WHOIS | Returns full entity details – contacts, addresses, phone numbers, and e‑mail – for IP blocks. |
| ASN Enrichment | Provides CIDR ranges and organization contacts for any ASN. |
| DNS Record Queries | A, AAAA, MX, NS, TXT, SOA, CAA and more are fetched via multi‑provider DoH (Cloudflare, Google, AdGuard). |
| Automated Monitoring & Change Detection | Cron‑driven queues regularly re‑run lookups, diff snapshots, and fire alerts via e‑mail or webhooks. |
| HTTP Content Monitoring | Captures page HTML, runs a structural DOM diff, and (optionally) applies AI‑based semantic analysis. |
| Smart Notifications | Threshold‑based alerts (low / medium / high / critical) let you ignore noise and focus on real incidents. |
| Export & Share | Results can be downloaded as JSON or CSV, copied into Google Sheets, and shared via permalinks. |
| Password‑less Auth | Magic‑link login keeps the experience frictionless while remaining secure. |
Architecture Overview
A lightweight ASCII diagram that captures the core data flow (≈ 60 characters wide, ≤ 10 lines):
+----------------+ +----------------+ +-----------------+
| IANA Bootstrap |→→| RDAP Servers |→→| Cloudflare DoH |
| Service | | (1,192 TLDs) | | (HTTPS) |
+----------------+ +----------------+ +-----------------+
| | |
+--------+---------+---------------------+
|
v
+-------------------------------------------------------+
| Edge Workers (Hono API) |
| 4‑Tier RDAP fallback • DNS resolution • Queue Mgmt |
+-------------------------------------------------------+
| |
v v
+----------------+ +----------------+ +----------------+
| D1 DB |←→| KV Cache |←→| R2 Buckets |
| Authoritative | | Fast look‑ups | | Snapshots & |
| watches, users | +----------------+ | HTML files |
+----------------+ +----------------+
|
v
+-------------------+
| SvelteKit UI |
| (Web App) |
+-------------------+The edge worker layer (Hono) handles HTTP routing, performs the 4‑tier RDAP fallback, writes snapshots to R2, and queues monitoring jobs. D1 stores the authoritative watch definitions, KV provides low‑latency caching, and R2 holds historic data.
How It Works
1. Input Detection & Routing
When a request hits /api/lookup, the worker inspects the query string to decide which lookup path to follow:
// src/routes/lookup.ts
import { detectInput } from "./utils";
export async function onRequest(context) {
const url = new URL(context.request.url);
const query = url.searchParams.get("query");
const type = detectInput(query); // "domain" | "ipv4" | "ipv6" | "asn"
switch (type) {
case "domain":
return lookupDomain(query, context.env);
case "ipv4":
case "ipv6":
return lookupIP(query, context.env);
case "asn":
return lookupASN(query, context.env);
}
}detectInput uses simple regex checks and returns a normalized type, allowing a single endpoint to serve all universal inputs.
2. 4‑Tier RDAP Fallback
The core RDAP routine first checks the KV cache, then the IANA Bootstrap list, then the public rdap.org service, and finally falls back to a raw WHOIS socket for legacy TLDs.
// src/services/rdap.ts
export async function rdapLookup(query: string, env: Env) {
// 1️⃣ KV fast‑path cache
const cached = await env.KV.get(query);
if (cached) return JSON.parse(cached);
// 2️⃣ IANA Bootstrap – find the authoritative RDAP server
const bootstrap = await fetch(
`https://data.iana.org/rdap/${query.includes('.') ? "domains" : "ips"}.json`
).then(r => r.json());
const rdapUrl = bootstrap.services?.[0]?.[1]?.[0];
if (!rdapUrl) throw new Error("No RDAP service found");
// 3️⃣ Direct RDAP request
try {
const resp = await fetch(`${rdapUrl}/${encodeURIComponent(query)}`);
const data = await resp.json();
await env.KV.put(query, JSON.stringify(data), { expirationTtl: 86400 });
return data;
} catch (e) {
// 4️⃣ WHOIS socket fallback (only for legacy TLDs)
return whoisFallback(query, env);
}
}Key points
- KV entries expire after 24 h to keep data fresh.
- The IANA bootstrap file is fetched on‑demand; the list is small (< 10 KB).
whoisFallbackopens a TCP socket to the legacy WHOIS server, parses the plain‑text response, and normalizes it to the same JSON shape.
3. Monitoring Queue & Cron Scheduler
A daily (or user‑defined) Cron trigger runs ScheduleManager.getDueWatches(), pushes each due watch onto the WATCH_CHECK_QUEUE, and lets a separate consumer handle the heavy lifting.
// src/cron/schedule.ts
export async function getDueWatches(env: Env) {
const now = new Date().toISOString();
const { results } = await env.DB.prepare(
"SELECT id, query FROM watches WHERE next_check <= ?"
)
.bind(now)
.all();
return results;
}
// Enqueue
for (const w of await getDueWatches(env)) {
await env.WATCH_CHECK_QUEUE.send(JSON.stringify(w));
}The watch‑check consumer reads the queue, runs rdapLookup (or DNS lookup), stores the new snapshot in R2, and calls compareSnapshots (see next section).
4. Change Detection & Notification
// src/services/compare.ts
export function compareSnapshots(oldSnap: any, newSnap: any) {
const diffs = [];
// Simple deep‑diff for illustrative purposes
for (const key of Object.keys(newSnap)) {
if (JSON.stringify(oldSnap[key]) !== JSON.stringify(newSnap[key])) {
diffs.push({ field: key, old: oldSnap[key], new: newSnap[key] });
}
}
const severity = diffs.length > 10 ? "high" : diffs.length > 3 ? "medium" : "low";
return { diffs, severity };
}If severity meets the user’s alert threshold, the platform dispatches an e‑mail or webhook payload:
// src/services/notify.ts
export async function sendAlert(watch, result, env) {
const payload = {
watchId: watch.id,
query: watch.query,
severity: result.severity,
changes: result.diffs,
};
await fetch(env.WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
}5. HTTP Content Monitoring (Optional)
When a watch includes type: "http", the worker fetches the page, stores the raw HTML in R2, and runs a DOM‑diff library (e.g., diff-dom). The resulting diff is then fed into the same severity engine as above, allowing security teams to detect defacements or phishing page changes.
Getting Started (Quick Start)
Clone the repo
BASHgit clone https://github.com/xxdesmus/whoisdns.git cd whoisdnsInstall dependencies (workspace uses
pnpm)BASHpnpm installRun the API locally
BASHcd apps/api npm run dev # Starts a Workers dev server on http://127.0.0.1:8787Run the frontend
BASHcd ../web npm run dev # Vite dev server at http://localhost:5173Deploy (requires a Cloudflare account & API token)
BASHcd apps/api npm run deploy # Deploys the Hono worker cd ../web pnpm run build && pnpm run deploy # Deploys the SvelteKit site to Cloudflare Pages
The README also lists the full command matrix (npm run test, pnpm run ci, etc.) for CI pipelines.
Recent Developments
- Authoritative D1 migration – The lazy KV‑to‑D1 migration has been removed; D1 now stores the single source of truth for watches and user data, simplifying consistency guarantees.
- Bulk‑lookup race fix – Concurrency bugs in the 50‑item batch path were resolved, delivering stable per‑item status reporting.
- Mobile navigation polish – Overlap issues on small screens were fixed, improving the touch experience.
- Admin finalize endpoint – A new API route lets administrators clean up stuck bulk jobs, reducing manual intervention.
These updates were shipped in the latest releases and are reflected in the live service.
Conclusion
DomainViz turns the chaotic world of WHOIS, RDAP, and DNS into a cohesive, edge‑native platform. By unifying universal lookups, historical snapshots, and automated monitoring under a single, password‑less UI, it empowers security teams, developers, and researchers to act faster and with greater confidence. The serverless architecture—leveraging Cloudflare Workers, D1, KV, and R2—delivers global performance while keeping operational overhead low.
Explore the live web app at https://domainviz.org , experiment with the public API at https://api.domainviz.org , and dive into the source code on GitHub to contribute or customize the platform for your own workflows.