Node.js / DNS / Performance

Resolve once. Move faster.

A zero-dependency DNS resolver with intelligent caching, provider control and request deduplication for Node.js.

ZERO DEPENDENCIESNODE 20+TYPESCRIPT

THE ROUTE BEFORE THE REQUESTREQ 01 · REQ 02
APPSMARTDNScache · dedupettl · swr · statsRESOLVER1.1.1.1IP142.250.72.14MISSHIT
REQ 01cache miss · resolver lookup
REQ 02cache hit · shortest path

The invisible cost

The expensive part is invisible.

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.

WITHOUT A CACHEthe resolver is asked again and again
  1. 01request
  2. 02DNS lookup
  3. 03connect
  4. 04response
WITH SMARTDNSthe answer is already in memory
  1. 01request
  2. 02cache hit
  3. 03connect
  4. 04response

The mechanism

A resolver that remembers.

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.

Node.js appfetch · axios · http
SmartDns
  • cache
  • deduplication
  • TTL
  • SWR
  • statistics
DNS provider9 public providers · or custom servers
One module, resolved before the request leaves the process.
  1. 01HITValid cache hitThe address is returned straight from the in-memory cache. No resolver contact.
  2. 02STALEStale, still usefulWith SWR enabled, an expired entry is served immediately while it refreshes in the background.
  3. 03NEGATIVERemembered failureA recently failed hostname is rejected from the negative cache instead of pressuring the resolver again.
  4. 04MISSReal lookupOnly here is a DNS query actually issued. The answer is cached and the record TTL is respected.

Behavior

What the cache actually does.

Five behaviors, each one driven by the real record and the real request — not a settings panel.

01

Cache that respects DNS

Cache lifetime follows the record TTL, with configurable bounds. Answers do not outlive the authority that issued them.

A142.250.72.14TTL 300s
300240180120600
02

One lookup. Many callers.

Concurrent lookups for the same hostname share one in-flight resolution. Five calls, one query.

DNSapp.example.com ×55 requests · 1 query
03

Stale, then refresh

Optionally serve stale entries immediately while refreshing them in the background, so the request never waits.

EXPIREDSERVE STALE
REFRESHin the background, non-blocking
04

Failed lookups are remembered too

Errors are held in a short negative cache, avoiding repeated resolver pressure for hostnames that will not resolve.

NXDOMAIN / ERRORNEGATIVE CACHERESOLVER
repeated failures are answered locally
05

Choose the resolver

Switch between nine public providers — or hand it your own servers — without changing how the request is written.

selected provider

3 of 9 providers shown — 6 more built in.

Also built in: DNS.WATCH · Google · OpenDNS · Quad9 · Verisign · Yandex

Integration

It sits before the request.

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 ↗
request interceptor
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
})

Process-wide

Configure DNS once.
Let the process use it.

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.

fetchaxiosnode:httpthird-party deps
SmartDnsprocess-global DNS
resolver / servers
provider selection
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

What it did, in numbers.

The resolver keeps a running account of hits, misses, errors, revalidations and average resolve time, exposed through a single readable property.

CACHE HITS16,987
MISSES1,205
ERRORS211
REVALIDATIONS184
AVG RESOLVE8.4 ms
DEMO VALUESIllustrative readout of dns.stats. The library records real numbers at runtime — no benchmarks are claimed here.
dns.stats
const {
    avgResolveMs,
    errors,
    hits,
    misses,
    revalidations,
} = dns.stats

console.log({
    hits,
    misses,
    errors,
    revalidations,
    avgResolveMs,
})

Installation

One package. No dependencies.

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.

install
npm install @darcas/smart-dns-promises
quick start
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

Small surface, made to last.

The package exposes one factory, a small set of options and its own error classes. Everything else is the standard library.

NODE
20+engines: 20 || >= 22
RUNTIME DEPENDENCIES
0nothing to audit
LANGUAGE
TypeScriptstrict, types included
MODULES
ESM / CJSboth entry points

The network already knows where to go.
Don't ask it twice.

Install the package, resolve with the cache in place, and keep repeated DNS lookups out of the critical path.

npm install @darcas/smart-dns-promises