Skip to content
Node.js nd core 4 min read

The dns Module

The Domain Name System translates human-friendly hostnames like example.com into the IP addresses your machine actually connects to. Node.js exposes this machinery through the built-in dns module, which lets you resolve hostnames, query specific record types (A, AAAA, MX, TXT, and more), and reverse-resolve IP addresses back to names. Understanding the difference between its two core operations — dns.lookup and dns.resolve — is the key to using it correctly, because they reach the network in fundamentally different ways.

Importing the module

The dns module ships with Node.js, so no installation is needed. Use the node: prefix to make the import unambiguous. There are two flavors: the classic callback-based API and the modern promise-based API under dns/promises.

import dns from 'node:dns';
import dnsPromises from 'node:dns/promises';

// CommonJS equivalent:
// const dns = require('node:dns');
// const dnsPromises = require('node:dns/promises');

You can also pull dns/promises straight off the default export as dns.promises, but importing it directly reads more cleanly in modern code.

dns.lookup vs dns.resolve

These two functions both turn a hostname into an address, but they take entirely different paths to get there — and that distinction matters for correctness, performance, and behavior under load.

dns.lookup() uses the operating system’s resolver via the underlying getaddrinfo() system call. It honors your machine’s full name-resolution configuration: /etc/hosts, nsswitch.conf, mDNS, and the system DNS cache. This is what most HTTP clients (including fetch and http) use under the hood, so it reflects how your application actually connects.

dns.resolve() (and its resolveX variants) bypasses the OS and talks directly to a DNS server over the network using Node’s bundled c-ares library. It ignores /etc/hosts entirely and lets you query any record type, making it the right tool for DNS-specific tasks like fetching MX or TXT records.

Aspectdns.lookupdns.resolve
MechanismOS resolver (getaddrinfo)Direct DNS query (c-ares)
Reads /etc/hostsYesNo
Record typesA / AAAA onlyA, AAAA, MX, TXT, NS, CNAME, SRV, etc.
Threadpool impactRuns on libuv threadpoolFully async, no threadpool
Best forMirroring real connection behaviorDNS infrastructure queries

Because dns.lookup runs on libuv’s threadpool (default size 4), a burst of slow lookups can block other threadpool work like file I/O and crypto. For high-throughput DNS work, prefer dns.resolve, which uses non-blocking sockets.

import dns from 'node:dns/promises';

const { address, family } = await dns.lookup('example.com');
console.log(`lookup ->  ${address} (IPv${family})`);

const addresses = await dns.resolve4('example.com');
console.log('resolve4 ->', addresses);

Output:

lookup ->  93.184.216.34 (IPv4)
resolve4 -> [ '93.184.216.34' ]

Resolving specific record types

dns.resolve() accepts a record type string as its second argument, but Node also provides dedicated helpers that return nicely shaped objects. Here are the most common ones.

import dns from 'node:dns/promises';

// A records (IPv4) and AAAA records (IPv6)
console.log('A   ', await dns.resolve4('github.com'));
console.log('AAAA', await dns.resolve6('github.com'));

// Mail exchange records, sorted by priority
const mx = await dns.resolveMx('gmail.com');
console.log('MX  ', mx);

// TXT records (SPF, domain verification, etc.)
const txt = await dns.resolveTxt('google.com');
console.log('TXT ', txt);

// Reverse lookup: IP address back to hostnames
console.log('PTR ', await dns.reverse('8.8.8.8'));

Output:

A    [ '140.82.121.3' ]
AAAA [ '2606:50c0:8000::153' ]
MX   [ { exchange: 'gmail-smtp-in.l.google.com', priority: 5 }, ... ]
TXT  [ [ 'v=spf1 include:_spf.google.com ~all' ], ... ]
PTR  [ 'dns.google' ]

Note that resolveTxt returns an array of arrays — each record may be split into multiple chunks, which you typically join with chunks.join(''). resolveMx returns objects with exchange and priority, letting you select the lowest-priority mail server first.

The dns/promises API

The dns/promises API mirrors the callback API method-for-method but returns promises, so it composes cleanly with async/await. You can also instantiate a Resolver to scope queries to specific DNS servers — useful for testing or querying internal name servers — without affecting the rest of your process.

import { Resolver } from 'node:dns/promises';

const resolver = new Resolver();
resolver.setServers(['1.1.1.1', '8.8.8.8']);

const records = await resolver.resolve4('cloudflare.com');
console.log('via custom servers:', records);

Output:

via custom servers: [ '104.16.132.229', '104.16.133.229' ]

You can also influence the default resolution order globally with dns.setDefaultResultOrder('ipv4first' | 'verbatim' | 'ipv6first'), which controls how dns.lookup orders mixed IPv4/IPv6 results.

Handling errors

DNS operations fail with descriptive error codes exposed as constants on the dns module, such as dns.NOTFOUND, dns.NODATA, and dns.TIMEOUT. Always wrap calls in try/catch and inspect err.code.

import dns from 'node:dns/promises';

try {
  await dns.resolve4('this-domain-does-not-exist-xyz.com');
} catch (err) {
  console.error(`Failed (${err.code}):`, err.message);
}

Output:

Failed (ENOTFOUND): queryA ENOTFOUND this-domain-does-not-exist-xyz.com

Best practices

  • Use dns.lookup when you want behavior consistent with how fetch/http actually connect, since they share the OS resolver and honor /etc/hosts.
  • Use dns.resolve* for any record type beyond A/AAAA, and for high-volume DNS work to avoid saturating the libuv threadpool.
  • Prefer the dns/promises API with async/await over the callback API for cleaner, more composable code.
  • Remember that resolveTxt yields nested arrays — join the inner chunks before parsing values like SPF strings.
  • Use a scoped Resolver instance with setServers() instead of mutating global state when you need custom name servers.
  • Always handle errors by code (ENOTFOUND, ENODATA, ETIMEOUT) rather than parsing message strings, which are not stable.
  • Increase UV_THREADPOOL_SIZE if your app relies heavily on dns.lookup and you observe threadpool contention.
Last updated June 14, 2026
Was this helpful?