Domain Validation in Node.js — IDN, DNS Checks & TLD Parsing
A domain that looks valid syntactically may not exist in DNS. An Internationalized Domain Name may trip up your regex. New gTLDs appear every year. Here's how to validate domains properly — format, structure, and DNS — in a single API call.
In this guide
1. Why domain validation is harder than it looks
A domain name is more than a string that matches a pattern. It lives in a layered system of registrars, DNS servers, and internationalisation standards. Checking format alone misses most of the real-world complexity.
Internationalized Domain Names (IDN)
Domains can contain non-ASCII characters like "münchen.de" or "例え.日本". Under the hood they use punycode (xn--), but users type Unicode. Your validator must handle both representations.
TLD proliferation
There are now over 1,500 valid TLDs — from .com and .org to .photography, .xn--vermgensberatung-pwb, and .localhost. Hardcoding a list is a losing battle.
DNS vs format validity
A domain can be syntactically perfect yet point to nothing. "this-domain-does-not-exist-xyz.com" passes every regex but has no DNS records. Only a live DNS lookup confirms the domain is actually in use.
A simple regex can tell you whether a string looks like a domain — but it cannot tell you whether it actually resolves, what its TLD category is, or whether it uses internationalised characters that need special handling.
2. Common domain validation mistakes
Most domain validation code falls into one or more of these traps.
// Common but broken domain validation const DOMAIN_REGEX = /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; // Problems: // 1. Rejects valid IDN domains like "münchen.de" or "例え.日本" // 2. Hardcodes TLD as 2+ ASCII letters — misses numeric and long TLDs // 3. Allows consecutive dots, leading hyphens, and other invalid syntax // 4. No DNS check — "fake-domain-xyz.com" passes // 5. No structure parsing — you don't know the TLD, SLD, or subdomain parts
Regex that rejects IDN domains
Patterns that only allow [a-zA-Z0-9] will reject millions of valid internationalised domain names used across Asia, Europe, and the Middle East.
Hardcoded TLD lists
New gTLDs are added regularly by ICANN. A static list in your codebase will become outdated within months and silently reject valid domains.
No DNS verification
Format validation alone cannot distinguish a registered, resolving domain from a syntactically correct but non-existent one.
Ignoring punycode
IDN domains have two representations: the Unicode label users see and the ACE (punycode) label used in DNS. Validators must normalise between the two.
3. The right solution
The IsValid Domain API handles format validation, IDN normalisation, TLD/SLD parsing, and live DNS verification in a single request. Pass any domain — Unicode or punycode — and get a complete breakdown.
Format & syntax validation
Validates label lengths, allowed characters, and overall domain structure per RFC 5891/5892
IDN & punycode support
Accepts Unicode domains and returns normalised form plus IDN detection flag
TLD & SLD parsing
Extracts the top-level domain and second-level domain from any valid domain
Live DNS verification
Checks for A and AAAA records to confirm the domain actually resolves
Full parameter reference and response schema: Domain Validation API docs →
4. Node.js code example
Using the IsValid SDK or the native fetch API.
import { createClient } from '@isvalid-dev/sdk'; const iv = createClient({ apiKey: process.env.ISVALID_API_KEY }); const result = await iv.domain('example.com'); console.log(result.valid); // true console.log(result.domain); // 'example.com' console.log(result.tld); // 'com' console.log(result.sld); // 'example' console.log(result.isIDN); // false console.log(result.dnsValid); // true console.log(result.hasA); // true console.log(result.hasAAAA); // false
In a registration form handler — validate the domain before accepting user input:
// routes/register.js (Express) app.post('/register', async (req, res) => { const { website } = req.body; // Extract domain from URL if the user pasted a full URL let domain = website; try { const url = new URL(website.startsWith('http') ? website : `https://${website}`); domain = url.hostname; } catch { // Not a URL — treat as bare domain } let check; try { check = await validateDomain(domain); } catch { return res.status(502).json({ error: 'Domain validation service unavailable' }); } if (!check.valid) { return res.status(400).json({ error: `"${domain}" is not a valid domain name.`, }); } if (!check.dnsValid) { return res.status(400).json({ error: `"${domain}" does not resolve in DNS. Please check for typos.`, }); } // Proceed with registration await createUser({ ...req.body, domain: check.domain }); res.json({ success: true }); });
domain field in the response returns the normalised form of the domain. Use it for storage and comparison instead of the raw user input to avoid duplicates caused by casing or Unicode variations.5. cURL example
Standard domain:
curl -G -H "Authorization: Bearer YOUR_API_KEY" \ --data-urlencode "value=example.com" \ "https://api.isvalid.dev/v0/domain"
Internationalized domain name:
curl -G -H "Authorization: Bearer YOUR_API_KEY" \ --data-urlencode "value=münchen.de" \ "https://api.isvalid.dev/v0/domain"
Invalid domain:
curl -G -H "Authorization: Bearer YOUR_API_KEY" \ --data-urlencode "value=not a domain" \ "https://api.isvalid.dev/v0/domain"
6. Understanding the response
Valid domain with DNS records:
{ "valid": true, "domain": "example.com", "tld": "com", "sld": "example", "isIDN": false, "dnsValid": true, "hasA": true, "hasAAAA": false }
IDN domain:
{ "valid": true, "domain": "münchen.de", "tld": "de", "sld": "münchen", "isIDN": true, "dnsValid": true, "hasA": true, "hasAAAA": true }
Invalid domain:
{ "valid": false, "domain": "not a domain", "tld": null, "sld": null, "isIDN": false, "dnsValid": false, "hasA": false, "hasAAAA": false }
| Field | Type | Description |
|---|---|---|
| valid | boolean | Whether the domain is syntactically valid |
| domain | string | Normalised form of the domain |
| tld | string | Top-level domain (e.g., "com", "de", "photography") |
| sld | string | null | Second-level domain (e.g., "example" in "example.com") |
| isIDN | boolean | Whether the domain is an Internationalized Domain Name containing non-ASCII characters |
| dnsValid | boolean | Whether DNS A or AAAA records exist for the domain |
| hasA | boolean | Whether the domain has at least one IPv4 (A) record |
| hasAAAA | boolean | Whether the domain has at least one IPv6 (AAAA) record |
7. Edge cases
Internationalized Domain Names (IDN)
IDN domains use Unicode characters — for example, "münchen.de" or "例え.jp". In DNS they are represented as punycode (e.g., "xn--mnchen-3ya.de"). The API accepts both forms and returns the normalised Unicode representation. The isIDN flag tells you whether special handling is needed.
const result = await iv.domain('münchen.de'); console.log(result.isIDN); // true console.log(result.domain); // 'münchen.de' (normalised Unicode) console.log(result.tld); // 'de'
New gTLDs
ICANN has delegated over 1,500 generic TLDs beyond the classic .com, .net, and .org. Domains like "my.photography", "startup.io", and "company.technology" are all valid. The API maintains an up-to-date TLD registry so you never reject a legitimate new extension.
Subdomains
A domain like "blog.example.com" is a subdomain of "example.com". The API validates the full domain including subdomains and correctly parses the TLD and SLD from the effective registrable domain. If your use case requires only the root domain, strip subdomains before calling the API.
.localhost and reserved TLDs
Reserved TLDs like .localhost, .test, .example, and .invalid are defined in RFC 6761. These domains are syntactically valid but will never resolve in public DNS. The API will report dnsValid: false for these, allowing you to detect development or placeholder domains that should not be used in production.
Summary
See also
Validate domains instantly
Free tier includes 100 API calls per day. No credit card required. Format validation, IDN support, TLD parsing, and live DNS checks in a single call.