Features

DNS Cache

Rezo includes a DNSCache that stores DNS lookup results in an LRU cache, avoiding repeated DNS queries for the same hostnames. This significantly improves performance for applications making many requests to the same servers by eliminating DNS resolution latency on repeat requests.

Quick Start

import { Rezo } from 'rezo';

// Enable both response and DNS caching
const client = new Rezo({
  cache: true
});

// First request: DNS lookup for api.example.com, result cached
await client.get('https://api.example.com/users');

// Subsequent requests: DNS served from cache (no network DNS query)
await client.get('https://api.example.com/posts');
await client.get('https://api.example.com/comments');

Configuration

DNS caching is configured through the cache option on the Rezo constructor.

Enable with Defaults

// Boolean: enables both response cache and DNS cache
const client = new Rezo({ cache: true });

// Object: enable DNS cache independently
const client = new Rezo({
  cache: {
    dns: true,
    response: false  // DNS only, no response caching
  }
});

Custom Configuration

const client = new Rezo({
  cache: {
    dns: {
      enable: true,
      ttl: 300000,        // 5 minutes (default: 60000 = 1 minute)
      maxEntries: 500      // default: 1000
    }
  }
});

DNSCacheOptions

interface DNSCacheOptions {
  /** Enable or disable DNS caching */
  enable?: boolean;
  /** Time-to-live for cached entries in milliseconds */
  ttl?: number;
  /** Maximum number of entries in the LRU cache */
  maxEntries?: number;
}

Defaults

OptionDefaultDescription
enabletrue (when configured)Cache is active
ttl60000 (1 minute)How long cached DNS entries are valid
maxEntries1000Maximum hostnames to cache

API

lookup(hostname, family?)

Resolve a hostname to a single IP address. If cached, returns immediately from cache. Otherwise performs a DNS lookup, caches the result, and returns it.

import { DNSCache } from 'rezo';

const dns = new DNSCache({ ttl: 120000 });

const result = await dns.lookup('api.example.com');
// { address: '93.184.216.34', family: 4 }

// Request IPv6 specifically
const v6 = await dns.lookup('api.example.com', 6);
// { address: '2606:2800:220:1:248:1893:25c8:1946', family: 6 }

When multiple addresses are available for a hostname, lookup() returns one at random from the cached set, providing basic load balancing across DNS round-robin records.

Returns: { address: string; family: 4 | 6 } | undefined

lookupAll(hostname, family?)

Resolve a hostname to all available IP addresses. Useful for connection pools or failover logic.

const all = await dns.lookupAll('api.example.com');
// [
//   { address: '93.184.216.34', family: 4 },
//   { address: '93.184.216.35', family: 4 }
// ]

// IPv4 only
const v4Only = await dns.lookupAll('api.example.com', 4);

Returns: Array<{ address: string; family: 4 | 6 }>

invalidate(hostname)

Remove all cached entries for a specific hostname (both IPv4, IPv6, and unspecified family).

dns.invalidate('api.example.com');
// Removes keys: 'api.example.com', 'api.example.com:4', 'api.example.com:6'

Use this when you know a hostname’s DNS has changed (e.g., after a failover or deployment):

// After detecting the server moved
dns.invalidate('old-server.example.com');

// Next request will perform a fresh DNS lookup
await client.get('https://old-server.example.com/health');

clear()

Remove all entries from the cache.

dns.clear();
console.log(dns.size); // 0

size

The number of entries currently in the cache.

console.log(dns.size); // 42

isEnabled

Whether the cache is currently active.

console.log(dns.isEnabled); // true

setEnabled(enabled)

Dynamically enable or disable the cache at runtime. When disabled, all lookups go directly to DNS without caching.

// Temporarily disable caching
dns.setEnabled(false);

// Re-enable
dns.setEnabled(true);

Global Singleton

Rezo provides a global DNS cache singleton for sharing across multiple Rezo instances or manual DNS operations:

import { getGlobalDNSCache, resetGlobalDNSCache } from 'rezo';

// Get or create the global singleton
const globalDns = getGlobalDNSCache();

// Optionally pass options on first creation
const globalDns = getGlobalDNSCache({ ttl: 300000, maxEntries: 2000 });

// Use it directly
const result = await globalDns.lookup('cdn.example.com');

// Reset the global singleton (clears cache and nulls the reference)
resetGlobalDNSCache();

The global cache is created lazily on the first call to getGlobalDNSCache(). Options are only applied on creation — subsequent calls return the existing instance regardless of options passed.

How It Works Internally

The DNS cache uses an LRU (Least Recently Used) eviction strategy backed by a Map:

  1. Cache key: hostname or hostname:family (e.g., api.example.com:4 for IPv4)
  2. Cache entry: { addresses: string[], family: 4 | 6, timestamp: number }
  3. On lookup: Check cache. If hit and not expired, return (random address for lookup, all for lookupAll). If miss, call dns.lookup() from Node.js, cache result, return.
  4. Expiration: Entries expire after ttl milliseconds from insertion. Expired entries are evicted on access.
  5. Capacity: When the cache exceeds maxEntries, the least recently used entry is evicted.

Cache Key Strategy

Different address families are cached separately:

LookupCache Key
lookup('example.com')example.com
lookup('example.com', 4)example.com:4
lookup('example.com', 6)example.com:6

This means IPv4 and IPv6 results for the same hostname are stored independently with separate TTLs.

Practical Examples

High-Throughput API Client

const client = new Rezo({
  baseURL: 'https://api.example.com',
  cache: {
    dns: {
      ttl: 300000,       // 5 minutes
      maxEntries: 100
    },
    response: false       // DNS only, no response cache
  }
});

// All requests reuse cached DNS for 5 minutes
for (let i = 0; i < 1000; i++) {
  await client.get(`/item/${i}`);
}

Web Scraper with DNS Cache

const scraper = new Rezo({
  cache: {
    dns: {
      ttl: 600000,        // 10 minutes
      maxEntries: 5000    // Many different hosts
    }
  }
});

// Each unique hostname is resolved once, then cached
const urls = [
  'https://site-a.com/page',
  'https://site-b.com/page',
  'https://site-a.com/other',  // DNS cached from first request
];

for (const url of urls) {
  await scraper.get(url);
}

Shared Cache Across Instances

import { getGlobalDNSCache } from 'rezo';

// All clients share the same DNS cache
const apiClient = new Rezo({
  baseURL: 'https://api.example.com',
  cache: { dns: true }
});

const cdnClient = new Rezo({
  baseURL: 'https://cdn.example.com',
  cache: { dns: true }
});

// Or use the global singleton directly for manual lookups
const globalDns = getGlobalDNSCache({ ttl: 300000 });
const ip = await globalDns.lookup('custom-service.internal');

Invalidation After Failover

const client = new Rezo({
  cache: { dns: { ttl: 60000 } }
});

try {
  await client.get('https://primary.example.com/api/data');
} catch (error) {
  if (error.code === 'ECONNREFUSED') {
    // Primary is down, invalidate DNS and retry
    // (DNS may have been updated to point to the new server)
    client.dnsCache?.invalidate('primary.example.com');
    await client.get('https://primary.example.com/api/data');
  }
}

Integration with the onDns Hook

The onDns hook fires on every DNS resolution, whether cached or not. Use it for metrics:

const client = new Rezo({
  cache: { dns: true },
  hooks: {
    onDns: [
      (event) => {
        metrics.histogram('dns_lookup_duration_ms', event.duration, {
          hostname: event.hostname,
          family: String(event.family)
        });
      }
    ]
  }
});

Accessing the Cache Instance

The DNS cache instance is available on the Rezo client for direct access:

const client = new Rezo({ cache: { dns: true } });

// Check stats
console.log(client.dnsCache?.size);       // number of cached entries
console.log(client.dnsCache?.isEnabled);  // true

// Manual operations
await client.dnsCache?.lookup('example.com');
client.dnsCache?.invalidate('example.com');
client.dnsCache?.clear();

// Disable at runtime
client.dnsCache?.setEnabled(false);

The clearCache() method on the Rezo instance clears both response and DNS caches:

client.clearCache();  // Clears both response cache and DNS cache