cf-doh: look up DNS records from your frontend javascript

June 18, 2024 —

2 Minute Read

Recently when working on Archival, I found myself needing to check the DNS records of a domain.

Background

Archival Pro allows users to point custom domains at Archival sites. To enable https for these sites, we use a TXT-record based domain verifier to verify ownership before issuing an SSL. To make this process simpler, I needed to query the records to check if the CNAME and TXT records are configured correctly and show some helpful messages if not.

In a node.js or C-like environment this is quite simple, as each OS has a local DNS cache and lookup tools. However, I was running this in a cloudflare worker, which is actually a v8 isolate and therefore doesn't have access to the OS's dns system. In fact, even with nodejs_compat turned on, the dns library from node's stdlib will just return an empty object.

DNS-over-HTTPS

After doing a bit of research, I realized that the modern DNS-over-HTTPS would be a good fit for this problem, and has uses outside of my narrow case. However I didn't love any of the libraries on npm for this use case - what I wanted was something more similar to node's DNS library, which abstracts away the lookup servers.

Cloudflare offers a DNS-over-HTTPs solution, which you can read about here: https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/

The primary downside to this API is that it can be a bit opaque to average users - the status codes are returned as numbers that map to a spec, and the response format is in Question and Answer format and includes granular data that, while useful, is overkill for most casual use.

A simple package

To make this easy for myself and others, I published cf-doh.

This is a simple library that makes it super simple to query records from the browser, cloudflare workers, or anywhere else that javascript runs. It depends on a native fetch implementation, and allows setting one if you don't already have one, so if you desire you can even run in node with a polyfilled whatwg fetch implementation - however if you're in node, you'll also have node:dns which is likely what you want.

In addition to wrapping the fetch calls, this library provides full types for the entire spec, handles quoted string responses, and converts http errors and status responses into exceptions.

Usage is simple:

import { queryDNS } from "cf-doh";

const records = await queryDNS("_verification.jesseditson.com", "TXT");

records.forEach((r) => console.log(r));

If you prefer a typed value, you can even import all valid record types and use them instead of strings:

import { queryDNS, DNSRecordType } from "cf-doh";

const records = await queryDNS("_verification.jesseditson.com", DNSRecordType.TXT);

If you want to handle statuses other than NoError, you can instead use a lower level API:

import { queryDNSRecords, DNSRecordType, DOHStatus, DOHStatusMessage } from "cf-doh";

const response = await queryDNSRecords("_verification.jesseditson.com", DNSRecordType.TXT);

switch (response.status) {
  case DOHStatus.NoError:
    console.log("This record exists!");
    break;
  case DOHStatus.NXDomain:
    console.log("Domain wasn't found");
    break;
  default:
    console.log(DOHStatusMessage[response.status]);
    break;
}

To add this to your project, just run npm install --save cf-doh. If you're curious about the source or full API surface, check out the repo:

https://github.com/jesseditson/cf-doh