Cache that respects DNS
Cache lifetime follows the record TTL, with configurable bounds. Answers do not outlive the authority that issued them.
Node.js / DNS / Performance
A zero-dependency DNS resolver with intelligent caching, provider control and request deduplication for Node.js.
The invisible cost
DNS resolution runs before the connection is opened, so it never appears in your request timings. It simply happens — for every host, in every process, on every call that does not already know the answer.
The mechanism
SmartDns sits between your application and the network. It keeps resolved hostnames in a TTL-aware cache, collapses concurrent lookups for the same host and only touches the resolver when it actually has to.
Behavior
Five behaviors, each one driven by the real record and the real request — not a settings panel.
Cache lifetime follows the record TTL, with configurable bounds. Answers do not outlive the authority that issued them.
Concurrent lookups for the same hostname share one in-flight resolution. Five calls, one query.
Optionally serve stale entries immediately while refreshing them in the background, so the request never waits.
Errors are held in a short negative cache, avoiding repeated resolver pressure for hostnames that will not resolve.
Switch between nine public providers — or hand it your own servers — without changing how the request is written.
3 of 9 providers shown — 6 more built in.
Also built in: DNS.WATCH · Google · OpenDNS · Quad9 · Verisign · Yandex
Integration
SmartDns does not replace your HTTP client. It resolves the hostname first, then hands the client a URL that already points at the address. The original hostname travels in the Host header.
Full examples in the README ↗import axios from 'axios'
import { SmartDns, DnsProvider } from '@darcas/smart-dns-promises'
const dns = SmartDns.factory(DnsProvider.CloudFlare)
axios.interceptors.request.use(async (config) => {
const { hostname, urlReplaced } = await dns.resolver(config.url!)
config.headers.Host = hostname
config.url = urlReplaced
return config
})
import { SmartDns, DnsProvider } from '@darcas/smart-dns-promises'
const dns = SmartDns.factory(DnsProvider.CloudFlare)
const { hostname, urlReplaced } = await dns.resolver('http://service.internal/health')
// Request goes to the resolved IP; the Host header keeps the origin name.
const response = await fetch(urlReplaced, {
headers: { Host: hostname }
})
Process-wide
setProvider(), setServers() and Node's result ordering are process-global. Every HTTP client in the process — yours and your dependencies' — inherits the same resolution strategy.
import { SmartDns, DnsProvider } from '@darcas/smart-dns-promises'
const dns = SmartDns.factory()
dns.setProvider(DnsProvider.AdGuard) // 94.140.14.14, 94.140.15.15
dns.setProvider(DnsProvider.CloudFlare) // 1.1.1.1, 1.0.0.1
dns.setProvider(DnsProvider.Comodo) // 8.26.56.26, 8.20.247.20
dns.setProvider(DnsProvider.DnsWatch) // 84.200.69.80, 84.200.70.40
dns.setProvider(DnsProvider.Google) // 8.8.8.8, 8.8.4.4
dns.setProvider(DnsProvider.OpenDNS) // 208.67.222.222, 208.67.220.220
dns.setProvider(DnsProvider.Quad9) // 9.9.9.9, 149.112.112.112
dns.setProvider(DnsProvider.Verisign) // 64.6.64.6, 64.6.65.6
dns.setProvider(DnsProvider.Yandex) // 77.88.8.8, 77.88.8.1
Statistics
The resolver keeps a running account of hits, misses, errors, revalidations and average resolve time, exposed through a single readable property.
dns.stats. The library records real numbers at runtime — no benchmarks are claimed here.const {
avgResolveMs,
errors,
hits,
misses,
revalidations,
} = dns.stats
console.log({
hits,
misses,
errors,
revalidations,
avgResolveMs,
})
Installation
Add the resolver, point it at a provider and call it before the request. The first lookup is cached; the next ones never reach the resolver.
npm install @darcas/smart-dns-promises
import { SmartDns, DnsProvider } from '@darcas/smart-dns-promises'
const dns = SmartDns.factory(DnsProvider.CloudFlare)
const { address, hostname, urlReplaced } =
await dns.resolver('https://www.google.com')
// address → '142.250.72.14'
// hostname → 'www.google.com'
// urlReplaced → 'https://142.250.72.14/'
Technical facts
The package exposes one factory, a small set of options and its own error classes. Everything else is the standard library.
Install the package, resolve with the cache in place, and keep repeated DNS lookups out of the critical path.