Chapter 33: Node.js DNS Module
1. What is the DNS module in Node.js?
The node:dns module lets your Node.js program perform DNS lookups (resolve domain names to IP addresses), reverse lookups (IP → domain name), and some other DNS-related operations.
In simple words:
When you type https://api.github.com in your browser, the browser asks DNS: “What is the IP address of api.github.com?” DNS answers: “140.82.112.25” (or similar)
The dns module does exactly that — but from inside your Node.js code.
Why would you need this?
Very common real-world cases:
- Resolve domain names before connecting (e.g., database, Redis, external APIs)
- Implement service discovery
- Build health-checks / monitoring tools
- Log real client IP (reverse DNS for analytics)
- Validate / canonicalize hostnames
- Custom DNS resolution for proxies, VPNs, or testing
Important note 2025–2026:
The promise-based API (dns/promises) is now the recommended way for almost all modern code.
The callback-based API is still supported but considered legacy.
2. Modern import styles
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Recommended — promise-based (2025–2026 style) import dns from 'node:dns/promises'; // Classic callback-based (still common in older code) import dns from 'node:dns'; // Or named imports (very clean) import { promises as dnsPromises, lookup, resolve4 } from 'node:dns'; |
Always prefer node:dns/promises for new code — it works perfectly with async/await.
3. Most important functions – with real examples
Let’s look at the ones you will use 90% of the time.
3.1 dns.lookup() – most commonly used (domain → IP)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import dns from 'node:dns/promises'; async function exampleLookup() { try { // Returns first IPv4 or IPv6 address const address = await dns.lookup('api.github.com'); console.log(address); // → { address: '140.82.112.25', family: 4 } // Force IPv4 only const ipv4 = await dns.lookup('api.github.com', { family: 4 }); console.log('IPv4 only:', ipv4.address); // Force IPv6 const ipv6 = await dns.lookup('api.github.com', { family: 6 }); console.log('IPv6:', ipv6?.address || 'No IPv6 available'); // All addresses const all = await dns.lookup('google.com', { all: true }); console.log('All addresses:', all); // → array of { address, family } } catch (err) { console.error('DNS lookup failed:', err.message); } } exampleLookup(); |
Very common real pattern – connect to database by hostname
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import dns from 'node:dns/promises'; import { createConnection } from 'node:net'; async function connectToRedis() { const { address } = await dns.lookup('redis.internal.company', { family: 4 }); const client = createConnection({ host: address, port: 6379 }); client.on('connect', () => console.log('Connected to Redis at', address)); } |
3.2 dns.resolve*() family – more specific queries
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
async function resolveExamples() { // A records (IPv4 addresses) const aRecords = await dns.resolve4('nodejs.org'); console.log('IPv4 addresses:', aRecords); // AAAA records (IPv6) const aaaaRecords = await dns.resolve6('nodejs.org'); console.log('IPv6 addresses:', aaaaRecords); // MX records (mail servers) const mx = await dns.resolveMx('gmail.com'); console.log('Mail servers:', mx); // TXT records (SPF, DKIM, verification) const txt = await dns.resolveTxt('example.com'); console.log('TXT records:', txt); // CNAME / alias const cname = await dns.resolveCname('www.github.com'); console.log('CNAME points to:', cname); } resolveExamples().catch(console.error); |
Real use-case: SPF / DKIM check
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
async function checkSPF(domain) { const txtRecords = await dns.resolveTxt(domain); const spfRecords = txtRecords.flat().filter(r => r.startsWith('v=spf1')); console.log(`SPF records for ${domain}:`); spfRecords.forEach(r => console.log('→', r)); } |
3.3 Reverse lookup – IP → domain names (PTR records)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
async function reverseLookup() { try { const domains = await dns.reverse('8.8.8.8'); console.log('Domains pointing to 8.8.8.8:', domains); // → ['dns.google'] } catch (err) { console.log('No reverse record or error:', err.message); } } |
Real use-case – client IP logging / analytics
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
app.use((req, res, next) => { const ip = req.socket.remoteAddress; dns.reverse(ip).then(hostnames => { console.log(`Client IP: ${ip} → hostnames: ${hostnames.join(', ') || 'none'}`); }).catch(() => { console.log(`Client IP: ${ip} → no reverse DNS`); }); next(); }); |
3.4 dns.lookupService() – reverse + port name
|
0 1 2 3 4 5 6 7 8 9 10 |
async function lookupServiceExample() { const { hostname, service } = await dns.lookupService('8.8.8.8', 53); console.log(`8.8.8.8:53 → ${hostname} (${service})`); // → dns.google (domain) } |
Useful for logging more meaningful service names.
4. Callback vs Promise API – quick comparison
Callback style (older projects)
|
0 1 2 3 4 5 6 7 8 9 |
dns.lookup('nodejs.org', (err, address, family) => { if (err) console.error(err); else console.log(address, family); }); |
Promise style (modern & recommended)
|
0 1 2 3 4 5 6 7 8 9 10 11 |
try { const { address, family } = await dns.lookup('nodejs.org'); console.log(address, family); } catch (err) { console.error(err); } |
Rule: Use promise-based (node:dns/promises) for all new code.
5. Real-world patterns & best practices (2025–2026)
Pattern 1 – Safe hostname resolution with fallback
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
async function resolveWithFallback(hostname, fallbackIp = '127.0.0.1') { try { const { address } = await dns.lookup(hostname, { family: 4, all: false }); return address; } catch (err) { console.warn(`DNS lookup failed for ${hostname}: ${err.message} → using fallback ${fallbackIp}`); return fallbackIp; } } |
Pattern 2 – Health check / DNS monitoring
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
async function checkDnsHealth() { const domains = ['api.stripe.com', 'api.github.com', 'auth0.com']; for (const domain of domains) { try { const start = Date.now(); await dns.lookup(domain); console.log(`${domain} resolved OK (${Date.now() - start} ms)`); } catch (err) { console.error(`${domain} DNS failure: ${err.message}`); } } } |
Pattern 3 – Prefer IPv4 or IPv6
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
async function getPreferredAddress(host) { try { // Try IPv6 first const res = await dns.lookup(host, { family: 6 }); return res.address; } catch { // Fallback to IPv4 const res = await dns.lookup(host, { family: 4 }); return res.address; } } |
6. Common mistakes & traps
| Mistake | Consequence | Fix / Best Practice |
|---|---|---|
| Using callback API without error handling | Silent failures | Always check err in callbacks |
| Assuming lookup always returns IPv4 | Breaks on IPv6-only hosts | Use { family: 0 } or handle both |
| Doing many lookups synchronously | Blocks event loop | Use Promise.all() for parallel lookups |
| Caching DNS forever | Stale IPs after failover | Respect TTL or use short cache |
| Trusting reverse DNS for security | Easily spoofed | Use only for logging / analytics, never auth |
Summary – Quick cheat sheet 2025–2026
| You want to… | Recommended function | Typical real-world example |
|---|---|---|
| Domain → IP (most common) | dns.lookup(hostname) | Connect to DB, Redis, external API |
| Get all IPv4 addresses | dns.resolve4(hostname) | Multi-homed hosts, load balancing |
| Get IPv6 addresses | dns.resolve6(hostname) | IPv6-first environments |
| Get mail servers | dns.resolveMx(domain) | Email routing / validation |
| Get SPF / DKIM / verification records | dns.resolveTxt(domain) | Email authentication checks |
| IP → domain name (reverse) | dns.reverse(ip) | Logging real hostnames, analytics |
Which part of the DNS module would you like to explore much deeper next?
- Parallel DNS lookups with Promise.all() for speed
- Custom DNS resolver (using dns.Resolver)
- DNS caching – when & how to implement
- Full example – service discovery + health check + fallback
- DNS-based load balancing patterns
Just tell me which direction feels most useful right now — I’ll continue with detailed, production-ready examples. 😊
