← Blog

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.

Contents

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.

Frequently asked questions

How can I check if a string is a valid IP address in Node.js without a library?

Node's built-in net module does it with zero dependencies: net.isIP(str) returns 0 for invalid, 4 for IPv4, and 6 for IPv6, while net.isIPv4 and net.isIPv6 return booleans. A hand-rolled regex can validate IPv4 reasonably well but tends to break on IPv6, where you have to handle :: compression, embedded IPv4, and zone IDs, so the stdlib check is usually safer. If you also need to know whether the address is public, private, or reserved, you add range classification on top, because net.isIP only confirms the format, not the meaning.

Does validating an IP address tell me its location or whether it's a VPN?

No. Validating and classifying an IP confirms the format, the version (IPv4 or IPv6), and which range it falls in, such as public, private, loopback, or reserved, plus whether it is publicly routable or a bogon. It does not reveal geolocation, the owning network or ASN, whether it is a hosting or datacenter address, or whether it is a proxy, VPN, or Tor exit. Boundstone's IP endpoint is explicit about this: those intelligence checks appear under checks.not_performed rather than being guessed at, so a valid result never implies more than it proves.

What is the difference between checking if an IP is valid and classifying it?

Validity just means the string is well-formed for its version, IPv4 or IPv6, which is what a stdlib check like net.isIP confirms. Classification goes further by mapping the address to its IANA special range, such as public, private, loopback, link-local, or reserved, and telling you whether it is publicly routable or a bogon. That distinction matters at signup or in logs, where a syntactically valid 10.0.0.1 or 127.0.0.1 is real but not a usable public address. Boundstone returns both format validity and range classification in one call, along with an explicit list of what it did and did not check.

Thomas Tsui

Founder of Boundstone — building phone, email and IP validation you can actually verify.

One honest API for email, phone and IP — every response lists what it checked and what it didn't claim to. Free tier: 250 credits/month, no card, credits never expire.

More from Boundstone — API documentation · Benchmark methodology · The benchmark series · Buyer's checklist