node:dns is two APIs wearing one name. dns.lookup — what http, net and fetch use — asks the operating system through getaddrinfo, so it honors the hosts file, and because getaddrinfo blocks it runs on the libuv thread pool, four threads by default. Everything else (resolve4, resolveMx, Resolver) queries DNS itself with c-ares, returning TTLs and record types lookup cannot see. Stability 2 – Stable; use node:dns/promises.
import dns from 'node:dns';
import { lookup, Resolver } from 'node:dns/promises';
console.log('order:', dns.getDefaultResultOrder(), '| servers:', dns.getServers());
const all = await lookup('example.com', { all: true });
console.log('lookup:', all.map((a) => `${a.address}/v${a.family}`).slice(0, 2).join(' '));
const r = new Resolver({ timeout: 2000, tries: 2 }); r.setServers(['1.1.1.1', '8.8.8.8']);
const a = await r.resolve4('example.com', { ttl: true });
console.log('A :', a.map((x) => `${x.address} ttl=${x.ttl}`).join(', '));
console.log('MX :', (await r.resolveMx('nodejs.org'))[0].exchange);Output
order: verbatim | servers: [ '127.0.0.1' ] lookup: 2606:4700:10::6814:179a/v6 2606:4700:10::ac42:93f3/v6 A : 172.66.147.243 ttl=119, 104.20.23.154 ttl=119 MX : aspmx.l.google.com
The 127.0.0.1 in servers is a local stub, normal in containers and why resolve4 can fail where lookup works. verbatim, the default since Node 17, leaves addresses in the resolver's order — IPv6 first here, harmless because net.connect races the families (Happy Eyeballs, RFC 8305).