یہ مضمون ابھی آپ کی زبان میں دستیاب نہیں، اس لیے آپ انگریزی اصل پڑھ رہے ہیں۔
Context
GeoIPHub is an IP intelligence API. You send one address and get back one complete answer: location, the network that routes it, the registry record, reverse DNS, anonymizer detection, threat flags and a fraud score from 0 to 100. I am its lead architect.
The project is a monorepo with four parts: a Rust scanner that builds the dataset, a NestJS API, a customer dashboard, and a marketing site and help centre built with Next.js.
Constraints
- The answer has to be fast. The site says known IPs answer in under a millisecond. A lookup API sits in someone else's request path, so slow lookups make their product slow too.
- The data goes stale on its own. VPN servers move, datacenter ranges change hands, and threat feeds update daily. The docs list refresh cadences from daily (threat feeds, crawler ranges) to weekly (WHOIS records, cloud provider ranges).
- A score has to be defensible. If someone blocks a user because of a number, they need to see where that number came from.
- Every plan gets every field. The Free plan sees exactly what Business sees. Plans differ only in how many lookups you get each day, so the expensive parts can't be held back for paying customers.
Architecture
There are two halves, and they share one file.
The scanner is written in Rust and writes what it finds into Postgres. It runs its steps in two lanes side by side: cheap steps that pull feeds, and network-heavy steps such as active probing and DNS. Each step has its own interval. Every night at 00:00 UTC, a build turns the Postgres tables into intel.mmdb. The API pulls the new file each day, and the docs say it is live by 04:00 UTC.
The core API is a NestJS service. A lookup reads two memory-mapped files: our own intel.mmdb and DB-IP City Lite for geolocation. It never touches Postgres. When an address is new to us, the API classifies it live before answering. The docs say that takes up to about 2.5 seconds, and the result is saved so the next lookup is fast.
Everything around the lookup lives elsewhere. Users, API keys, usage and billing events are in MongoDB. Valkey holds the public rate limits and an email queue, which a separate BullMQ worker drains to send verification and reset emails.
Decisions and alternatives I rejected
The file is the contract
I could have had the API query the scanner's Postgres tables directly. Instead, the only thing the scanner hands the API is a built intel.mmdb. The scanner can change its schema without the API needing to know, and a heavy scan never competes with a lookup for the same database.
Reasons, not just a number
A single opaque score would have been simpler to ship. The response includes scoring.detection_methods instead: the exact signals that fired, in a form a machine can read. Some signals lower the score, such as a residential ISP or a verified crawler. A coverage field says whether we hold any evidence for the address at all, so a score of 0 with no data reads as "unobserved", not "clean".
Scanning in its own process
Probing ports and resolving DNS for large address ranges is slow, bursty and network-bound. Answering lookups has to be quick and steady. Running both in one service would let the scanner's load show up in lookup latency. So the scanner is its own Rust program on its own host, and the API only ever sees its output.
Trade-offs I accepted
The daily build has a cost. The lookup database is rebuilt once a day, so the docs warn that a change can take up to about a day to show. New addresses are classified live, but that live path is the slow one. I chose fast, predictable reads for known addresses over freshness to the minute.
Outcome
GeoIPHub is live at geoiphub.com. The site lists a Free plan with 1,500 lookups a day and every field included, plus anonymous access without a key at 30 requests a minute.
ClickFortify, the ad-fraud product I also architect, called GeoIPHub's API from its tracking service. Later we replaced those calls with local MMDB lookups.
The docs are also clear about the limits: an IP address is not a person, location is approximate, and "not detected" is not the same as "clean".