Node.js DNS Resolution

Distinguish Node.js operating-system lookup from DNS queries, use an isolated resolver, handle record types and TTLs, and test without public DNS.

Lesson 30 of 43Intermediate10 min read

A hostname is not itself an address that a socket can contact. Name resolution supplies one or more candidate addresses, and Node exposes more than one route to that information. Understanding those routes helps explain why a hostname works in a browser or command-line tool but behaves differently in a DNS diagnostic script. This guide assumes Node 24 and uses the promise DNS APIs. Its main fixture supplies a tiny local UDP DNS responder for one A-record query. That makes the output deterministic without depending on public DNS, an internet connection, or the computer's hosts-file contents.

Choose lookup or a DNS record query

dns.lookup uses the operating system's name-resolution facilities. Depending on the operating system and its configuration, those facilities can consult sources such as a hosts file as well as DNS. This is the usual behavior expected when connecting to a hostname through many Node network APIs. Passing an IP literal does not require discovering that address from a DNS record. The promise lookup result includes an address and its numeric family, or an array when all is requested.

resolve4 and resolve6 query DNS for A and AAAA records respectively. They do not provide a substitute for every operating-system name-resolution source. Other methods query MX, TXT, SRV, and other record types for applications that need those records. Choosing resolve4 simply because an HTTP client eventually needs an IPv4 address can change the application's resolution behavior. Decide whether you need the system's connection-oriented name resolution or a direct DNS record query; the similar method names do not make their contracts identical.

Keep resolver configuration local

A Resolver instance has its own configured DNS servers. Setting its servers avoids mutating the process-wide default for unrelated code. This is valuable in a diagnostic tool, integration test, or application querying an explicitly chosen private DNS service. Configure the resolver before starting queries. Server strings can include a port, which lets the fixture listen on a nonprivileged automatically selected local port. A production tool should validate user-provided resolver settings instead of accepting arbitrary network destinations without policy.

The fixture's UDP responder implements only enough of a DNS response to answer its one controlled query. It echoes the question, supplies an A record, and retains the transaction identifier. It is not a general DNS server and should not be exposed to untrusted traffic. Node's actual Resolver parses the response, so the example exercises the API's query behavior rather than mocking its return value. Bind the responder to loopback and close it in finally. DNS uses other protocol behaviors, including TCP in some cases, that this limited responder intentionally does not implement.

Treat address lists and TTLs as data

A hostname can have several addresses and both IPv4 and IPv6 records. A result list is not a promise that the first address will be reachable from your environment. Resolution order and connection strategies can vary with Node options and system configuration. Avoid printing public DNS answers as an exact universal expected result. If a lesson needs fixed answers, supply a controlled fixture or clearly mark external output as illustrative.

resolve4 with ttl enabled returns objects containing an address and a TTL. A TTL is information for cache lifetime, not a guarantee that a connection to the address will succeed or that the address can never change earlier. A custom cache needs bounded size, expiration handling, and a strategy for failed queries. Do not cache every result indefinitely. Negative answers and transient failures require separate treatment. Also avoid treating a TXT record or MX preference as an IP address; different record types have different structures and purposes.

Handle failures at the correct layer

A name-resolution error occurs before an HTTP response exists. Report it separately from connection refusal, TLS failure, or an unsuccessful HTTP status. DNS error codes provide a more useful decision point than matching the wording of an error message. An absent record type does not necessarily mean the entire domain is nonexistent. A domain can have an A record but no MX record, for example. Handle only failures your application knows how to recover from and preserve the details needed for diagnosis.

DNS resolution also does not establish whether a destination is permitted. A server-side downloader needs a network access policy, including attention to local and private addresses, redirects, and address changes between validation and connection. Checking a hostname string alone is insufficient. This guide does not build a general safe downloader; it teaches the resolution boundary. Keep the chosen address family, timeout behavior, and cancellation strategy explicit in tools that perform many queries, and close any local test sockets so the process can finish.

Node.js documentation and version notes

Node.js DNS Resolution Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Querying a local DNS responder with Resolver

Run this .mjs fixture locally. The initial lookup uses an IPv4 literal, producing a stable address-family result. The Resolver then queries only the local responder for fixture.test and requests TTL information. The server copies the controlled question and returns the address 127.0.0.42 with a TTL of sixty seconds. A real DNS administrator would maintain records independently; the responder here exists solely to make an instructional test reproducible. No global resolver setting changes, and the local UDP socket closes after the answer or an error. The printed JSON uses only values intentionally fixed by the fixture.

JavaScript
import dgram from 'node:dgram';
import { once } from 'node:events';
import { lookup, Resolver } from 'node:dns/promises';
console.log(JSON.stringify(await lookup('127.0.0.1')));
const server = dgram.createSocket('udp4');
server.on('message', (query, peer) => {
  let end = 12;
  while (query[end] !== 0) end += query[end] + 1;
  end += 5;
  const header = Buffer.alloc(12);
  header.writeUInt16BE(query.readUInt16BE(0), 0);
  header.writeUInt16BE(0x8180, 2);
  header.writeUInt16BE(1, 4);
  header.writeUInt16BE(1, 6);
  const answer = Buffer.from([0xc0, 0x0c, 0, 1, 0, 1, 0, 0, 0, 60, 0, 4, 127, 0, 0, 42]);
  server.send(Buffer.concat([header, query.subarray(12, end), answer]), peer.port, peer.address);
});
server.bind(0, '127.0.0.1');
await once(server, 'listening');
try {
  const resolver = new Resolver({ timeout: 1000, tries: 1 });
  resolver.setServers(['127.0.0.1:' + server.address().port]);
  console.log(JSON.stringify(await resolver.resolve4('fixture.test', { ttl: true })));
} finally {
  await new Promise(resolve => server.close(resolve));
}
Output
{"address":"127.0.0.1","family":4}
[{"address":"127.0.0.42","ttl":60}]

Node.js DNS Resolution in Real Projects

Configure separate Resolver instances without queries

Demonstrate that two Resolver instances can hold different server settings without making queries. Configure one with a loopback DNS server and the other with a documentation-range server. Then use lookup on IPv4 and IPv6 literals and print their families. This practice isolates configuration and result shapes from actual network availability. The documentation-range address is never contacted. A loopback server setting does not automatically create a DNS service; the main fixture explicitly starts one. Keeping that distinction clear helps diagnose an application that configured a resolver correctly but has no reachable DNS server at its selected address.

JavaScript
import { lookup, Resolver } from 'node:dns/promises';
const first = new Resolver();
const second = new Resolver();
first.setServers(['127.0.0.1']);
second.setServers(['192.0.2.53']);
console.log(JSON.stringify(first.getServers()));
console.log(JSON.stringify(second.getServers()));
console.log((await lookup('127.0.0.1')).family);
console.log((await lookup('::1')).family);
Output
["127.0.0.1"]
["192.0.2.53"]
4
6

Node.js DNS Resolution FAQ

Why can lookup find a name that resolve4 cannot?

lookup follows operating-system name-resolution facilities, which can include sources other than DNS. resolve4 asks for an A record through DNS. A hosts-file entry can therefore affect one operation without creating a DNS record for the other. Compare the intended resolution contract before assuming one result is wrong. Also check the requested family and record type: a name with only an IPv6 record may not have an A answer.

Does setServers on one Resolver affect other code?

It configures that Resolver instance. This lets a diagnostic operation use a particular DNS service without changing unrelated default resolution behavior. The instance must be configured before its queries begin. A DNS server setting is also not the same as selecting the source network interface or rewriting a hosts file. Keep those concerns separate when diagnosing which component actually determines a query's destination.

Should I hard-code public DNS output in a test?

Public answers can vary by location, time, service changes, and network configuration. Test the behavior against a controlled service when exact values matter, or assert an appropriate result shape for an external integration test. The local fixture fixes the record data and never relies on a real public hostname. It is limited to its one controlled query, which makes it suitable for teaching rather than general DNS service operation.

Does a successful DNS answer prove a service is healthy?

No. It proves the query produced an answer. Connecting to the resulting address can still fail, the server's certificate can be invalid, and the application can return an error. Health checks should measure the layer that matters to the user. Keep DNS diagnostics useful by reporting address and record information, but do not convert a successful resolution into an unsupported claim that the entire service works.

Node.js DNS Resolution Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choiceeasy

What TTL does the main Resolver receive for fixture.test?

Node.js DNS Resolution Quiz

2 questions to check what you learned.

  1. Which operation can use operating-system sources such as a hosts file?

    • lookup
    • resolveMx
    • resolveTxt
  2. What does querying resolve4 request?

    • A records representing IPv4 addresses
    • Every available DNS record in one combined response
    • The operating system process ID for a hostname