Check if an IP address is valid in Node.js: stdlib, regex, and classification
Node has a built-in that beats every IP regex you'll find. Here's how to use it — and where "is it valid" stops being the useful question.
You've got a string that's supposed to be an IP address — from a form, a log line, an X-Forwarded-For header — and you need to know if it's real. In Node.js this is one of the rare validation problems the standard library already solves cleanly, so the main thing is to not reach for a regex. Here's the right way, and where the interesting questions start once the format check passes.
Skip the regex
The IPv4 regex you'll find looks reasonable and is quietly wrong:
const IPV4 = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
IPV4.test("999.999.999.999"); // true — but that's not a valid IP
Each octet has to be 0–255, which the pattern above doesn't enforce, so you end up bolting on range checks. And the IPv6 equivalent — with its :: zero-compression, embedded IPv4, and zone IDs — is a genuine monster that almost nobody gets right. Don't write it.
Use net.isIP
Node's built-in net module parses IP addresses properly, IPv4 and IPv6, with no dependency:
import net from "node:net";
net.isIP("8.8.8.8"); // 4
net.isIP("2606:4700:4700::1111"); // 6
net.isIP("999.999.999.999"); // 0 (not valid)
net.isIP("not-an-ip"); // 0
net.isIPv4("10.0.0.1"); // true
net.isIPv6("::1"); // true
net.isIP returns 0, 4, or 6 — falsy when invalid, and the version number when valid. That's the whole format check:
function isValidIp(value) {
return net.isIP(value) !== 0;
}
For validating that a string is a well-formed IP address, that's the answer. No library, no regex, no edge cases you forgot. If you only needed the format check, you can stop reading here — genuinely.
"Valid" is not "trustworthy"
Here's where it gets interesting. net.isIP tells you 10.0.0.1 is a perfectly valid IPv4 address. It does not tell you that 10.0.0.1 is a private address that should never appear as the source of a public request — which, if you're reading it out of a signup or an X-Forwarded-For header, is a red flag worth acting on.
That's a classification question, and the standard library doesn't answer it. The categories that matter:
- Private (
10.x,172.16–31.x,192.168.x) — only valid inside a local network. - Loopback (
127.x,::1) — the machine talking to itself. - Link-local, reserved, documentation — ranges that shouldn't be a real user's public source.
- Bogon — an address from unallocated or reserved space appearing where a routable one should be; a classic spoofing tell.
You can implement these range checks yourself — it's arithmetic on the address bytes — or get them from an API that already has them. Boundstone's IP endpoint returns the classification alongside the format check, and it's pure arithmetic too, so it's part of the free tier:
const res = await fetch("https://api.boundstone.io/v1/verify/ip", {
method: "POST",
headers: { authorization: "Bearer bs_live_YOUR_KEY", "content-type": "application/json" },
body: JSON.stringify({ ip: "10.0.0.1" }),
});
const data = await res.json();
console.log(data.valid, data.version, data.classification, data.is_public, data.is_bogon);
// true 4 "private" false true
The honest boundary
Classification is where our IP endpoint stops today, and the response says so out loud:
console.log("checked: ", data.checks.performed);
// ['format', 'version', 'range_classification']
console.log("not checked:", data.checks.not_performed);
// ['geolocation', 'asn', 'hosting_datacenter', 'proxy_vpn_tor', 'reputation']
Everything on that second list — where the IP is, which network owns it, whether it's a datacenter, whether it's a VPN or Tor exit — needs licensed data we haven't shipped. So it's marked not_performed rather than approximated. That's a deliberate line: validating and classifying an address is honest, cheap, and useful; guessing its geolocation and calling it a fact is not. When the licensed intelligence layer ships, it'll appear in performed the day it's real, and not a day before.
You can try the classification with no key on the IP validator.
The short version
To check whether a string is a valid IP in Node.js, use net.isIP — it's built in, correct, and handles IPv4 and IPv6. That answers format. If your real question is "should I trust an address coming from this source," you want classification — public vs. private vs. bogon — which the stdlib doesn't do but you can compute or call for. And treat anything past that (geolocation, VPN detection) as a separate, heavier problem that any honest tool will tell you when it hasn't actually solved.