d
Amit DhamuSoftware Engineer
 

Update Netlify DNS with JavaScript

6 minute read 00000 views

If you use Netlify to manage your DNS, you can use their API to programtically add, remove or update records.

For me, the use case is that I run a home server and I want to be able to dynamically map my IP address to a domain. This is also known as Dynamic DNS and there are many providers out there that offer this service such as Afraid FreeDNS and No-IP.

Having used some of those services in the past, I realised that I didn't really need them and could write a small script to do this myself.

As my ISP issues dynamic IP addresses, I use the following code inside a Docker container to insert (or update if it exists) an A record to ensure the domain always resolves to the correct IP.

This guide however, will only concentrate on using the Netlify API with JavaScript and no dependencies!

Prerequisites

  • Netlify Personal Access Token. This can be retrieved or generated here.
  • Node 18+ - we want to use 18 because it comes with fetch.

Setup

  1. Let's create a folder where we're going to store our code and cd into it.
mkdir dns-updater
cd $_
  1. Export your Netlify Personal Access Token in the terminal. (You could store this in an .env file and use a package like dotenv to retrieve it but for the purpose of this guide, we're not going to.)
export ACCESS_TOKEN=MY_PERSONAL_ACCESS_TOKEN
  1. Let's also create an .nvmrc to ensure we're using the correct Node version.
echo '18' > .nvmrc
fnm use

Note

If you haven't tried FNM as a drop-in replacement for nvm, I highly recommend it. After countless hours of trying to speed up my terminal with nvm, I came across this and have never looked back.

  1. Create an executable Node file and open it using your favourite editor (I'm using VSCode).
echo '#!/usr/bin/env node' > index.js
code index.js
  1. To start with, let's retrieve the access token in our script and throw an error if it doesn't exist.
const ACCESS_TOKEN = process.env.ACCESS_TOKEN

if (!ACCESS_TOKEN) {
  throw new Error('Please set the ACCESS_TOKEN environment variable')
}
  1. We also want to setup some more variables like the domain we're dealing with, the subdomain etc.
const ipValue = '8.8.8.8' // the value of the DNS record
const domainName = 'mydomain.com'
const host = 'www'
const zoneId = domainName.replace('.', '_') // replace any dots in your domain with an underscore
const baseUrl = `https://api.netlify.com/api/v1/dns_zones/${zoneId}/dns_records`
const headers = {
  'Content-type': 'application/json',
  Authorization: `Bearer ${ACCESS_TOKEN}`,
  'User-Agent': 'DNS Updater',
}
  1. Next, we need to delete the record if it exists before we add it again. At the time of writing, Netlify's API does not support a patch or update method for a DNS record.
const getExistingRecordIfExists = async hostname => {
  const records = await fetch(baseUrl, {
    method: 'GET',
    headers,
  }).then(response => response.json())

  return records.find?.(record => record.hostname === hostname)?.id
}
  1. Now let's write our DNS updater function
const updateDns = async () => {
  const fullHost = `${host}.${domainName}`

  console.log(`Setting A record for: ${fullHost}`)

  const id = await getExistingRecordIfExists(fullHost)

  if (id) {
    console.log(`${fullHost} exists. Deleting first...`)

    await fetch(`${baseUrl}/${id}`, {
      method: 'DELETE',
      headers,
    })
  }

  const {
    type,
    hostname: savedHostname,
    value: savedValue,
    errors,
  } = await fetch(baseUrl, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      type: 'A',
      hostname: host,
      value: ipValue,
    }),
  }).then(response => response.json())

  if (errors.length) {
    throw new Error(JSON.stringify(errors))
  }

  console.log(`Record ${type} created for ${savedHostname}: ${savedValue}`)
}
  1. Now all we have to do is call the function.
updateDns()
  1. Putting it altogether
#!/usr/bin/env node

const ACCESS_TOKEN = process.env.ACCESS_TOKEN

if (!ACCESS_TOKEN) {
  throw new Error('Please set the ACCESS_TOKEN environment variable')
}

const ipValue = '8.8.8.8' // the value of the DNS record
const domainName = 'mydomain.com'
const host = 'www'
const zoneId = domainName.replace('.', '_') // replace any dots in your domain with an underscore
const baseUrl = `https://api.netlify.com/api/v1/dns_zones/${zoneId}/dns_records`
const headers = {
  'Content-type': 'application/json',
  Authorization: `Bearer ${ACCESS_TOKEN}`,
  'User-Agent': 'DNS Updater',
}

const getExistingRecordIfExists = async hostname => {
  const records = await fetch(baseUrl, {
    method: 'GET',
    headers,
  }).then(response => response.json())

  return records.find?.(record => record.hostname === hostname)?.id
}

const updateDns = async () => {
  const fullHost = `${host}.${domainName}`

  console.log(`Setting A record for: ${fullHost}`)

  const id = await getExistingRecordIfExists(fullHost)

  if (id) {
    console.log(`${fullHost} exists. Deleting first...`)

    await fetch(`${baseUrl}/${id}`, {
      method: 'DELETE',
      headers,
    })
  }

  const {
    type,
    hostname: savedHostname,
    value: savedValue,
    errors,
  } = await fetch(baseUrl, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      type: 'A',
      hostname: host,
      value: ipValue,
    }),
  }).then(response => response.json())

  if (errors.length) {
    throw new Error(JSON.stringify(errors))
  }

  console.log(`Record ${type} created for ${savedHostname}: ${savedValue}`)
}

updateDns()
  1. To execute and test, simply run from the terminal
node index.js

Further Reading