Building a DNS Lookup Tool in the Browser: Google DNS API in Practice
Building a DNS Lookup Tool in the Browser: Google DNS API in Practice#
2026-04-29 18:25
While developing JsonKit, I needed to implement a DNS lookup tool. The traditional approach is to call dig or nslookup from the backend, but this time I wanted to try a pure frontend solution—directly calling the Google DNS API. Turns out there are quite a few gotchas. Here’s what I learned.
Why Google DNS API?#
DNS queries use UDP protocol, which browsers can’t directly send. So the frontend has to use an HTTP API. There are three main options:
- Google DNS API -
https://dns.google/resolve - Cloudflare DNS API -
https://cloudflare-dns.com/dns-query - Self-hosted backend proxy - Call system commands
Cloudflare’s API requires DOH (DNS over HTTPS) protocol and returns DNS wire format binary data, which is a pain to parse. Google’s API returns JSON directly, making it much more frontend-friendly:
// Google DNS API response format
{
"Status": 0,
"TC": false,
"RD": true,
"RA": true,
"AD": false,
"CD": false,
"Question": [
{ "name": "google.com.", "type": 1 }
],
"Answer": [
{ "name": "google.com.", "type": 1, "TTL": 299, "data": "142.250.189.238" }
]
}
Core Implementation: Types and Requests#
DNS has many record types. The common ones are A, AAAA, CNAME, MX, TXT, NS, SOA, SRV, and CAA. Let’s define the types:
const RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SOA', 'SRV', 'CAA'] as const
type RecordType = (typeof RECORD_TYPES)[number]
interface DnsRecord {
name: string // Domain name
type: number // DNS record type code (1=A, 28=AAAA)
TTL: number // Cache time (seconds)
data: string // Record value
}
The request logic is straightforward—just construct the URL:
const handleLookup = async () => {
const trimmed = domain.trim()
if (!trimmed) return
// Domain validation
const domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/
if (!domainRegex.test(trimmed)) {
setError('Invalid domain format')
return
}
setLoading(true)
const start = performance.now()
try {
const response = await fetch(
`https://dns.google/resolve?name=${encodeURIComponent(trimmed)}&type=${recordType}`
)
const data = await response.json()
const elapsed = Math.round(performance.now() - start)
setQueryTime(elapsed)
if (data.Answer && data.Answer.length > 0) {
setResults(data.Answer)
} else {
setResults([])
}
} catch {
setError('Query failed')
} finally {
setLoading(false)
}
}
Gotcha #1: Domain Validation Edge Cases#
Domain validation looks simple, but there are many edge cases:
example.com✅sub.example.com✅a-b-c.example.com✅-example.com❌ (can’t start with hyphen)example-.com❌ (can’t end with hyphen)example.com-❌ (TLD can’t end with hyphen)123.example.com✅ (can start with digit)
Here’s the regex I use:
const domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/
Key points:
- Each label is max 63 characters (
{0,61}plus two boundary characters) - Can’t start or end with hyphen
- TLD is at least 2 letters
Gotcha #2: DNS Record Type Codes#
Google API returns numeric type codes, not strings:
{ "name": "google.com.", "type": 1, "data": "142.250.189.238" }
Common type mappings:
const TYPE_MAP: Record<number, string> = {
1: 'A', // IPv4 address
28: 'AAAA', // IPv6 address
5: 'CNAME', // Alias
15: 'MX', // Mail server
16: 'TXT', // Text record
2: 'NS', // Name server
6: 'SOA', // Start of authority
33: 'SRV', // Service record
257: 'CAA', // Certificate authority authorization
}
Displaying raw numbers would confuse users. Convert at the UI layer:
const getTypeLabel = (type: number): string => {
return TYPE_MAP[type] || `TYPE${type}`
}
Gotcha #3: MX Record Special Format#
MX record data field has format priority mailserver:
{ "name": "google.com.", "type": 15, "data": "10 smtp.google.com." }
Note the trailing dot (FQDN format). Format it for display:
const formatMxRecord = (data: string): { priority: number; server: string } => {
const [priority, server] = data.split(' ')
return {
priority: parseInt(priority),
server: server.replace(/\.$/, '') // Remove trailing dot
}
}
Gotcha #4: TXT Record Quoting#
TXT records are used for SPF, DKIM verification. The format is "v=spf1 include:_spf.google.com ~all". The data field already includes quotes.
But there’s a catch: long TXT records (>255 chars) are split into multiple strings:
{ "data": "\"v=spf1\" \"include:_spf.google.com\" \"~all\"" }
Handle this by joining:
const formatTxtRecord = (data: string): string => {
// Remove outer quotes, join multiple strings
return data.replace(/" "/g, '').replace(/^"|"$/g, '')
}
Performance: Query Time Tracking#
DNS query time is useful for diagnosing network conditions. Use performance.now() instead of Date.now() for higher precision:
const start = performance.now()
const response = await fetch(...)
const elapsed = Math.round(performance.now() - start)
performance.now() has microsecond precision, while Date.now() only has millisecond precision. For fast operations like DNS queries, precision matters.
History Implementation#
Query history is a practical feature. Manage it with React state:
interface HistoryEntry {
domain: string
type: RecordType
timestamp: number
}
const [history, setHistory] = useState<HistoryEntry[]>([])
// Update history after successful query
setHistory((prev) => {
const filtered = prev.filter(
(h) => !(h.domain === trimmed && h.type === recordType)
)
return [{ domain: trimmed, type: recordType, timestamp: Date.now() }, ...filtered].slice(0, 10)
})
Deduplication: if the same domain+type combination exists, remove the old one first, then add the new one to the top. Limit to 10 entries to prevent unbounded growth.
CORS Considerations#
Google DNS API supports CORS, so you can call it directly from the browser:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Cloudflare’s API requires setting Accept: application/dns-json header, otherwise it returns 415 error. If you hit CORS issues:
- Use a CORS proxy (like
cors-anywhere) - Self-host a backend proxy
- Switch to a CORS-friendly API
Practical Use Cases#
This DNS lookup tool is useful during development:
- Check DNS resolution - Confirm domain resolves to expected IP
- Verify MX records - Debug email delivery issues
- View TXT records - Verify SPF, DKIM configuration
- Check CNAME - Confirm alias configuration
- Compare different DNS - Switch record types to compare results
Summary#
Building a frontend DNS lookup tool comes down to choosing the right API. Google DNS API returns JSON, making it frontend-friendly, but watch out for:
- Strict domain validation regex
- Type code to readable name mapping
- Special formats for MX, TXT records
- Use
performance.now()for query timing - Deduplication and limit for history
Try the complete DNS lookup tool at JsonKit DNS Lookup.
Related Tools:
- HTTP Header Analyzer - Analyze HTTP response headers and security config
- IP Location - Query geographic location of IP addresses
- Whois Lookup - Query domain registration information