Deploy a Python serverless function on ZEIT Now

Let's write an API endpoint using a Python serverless function that will give us DNS records in JSON format from a given domain. We will send a GET request with a query parameter named "domain", i.e.,

GET https://localhost:3000/api?domain="jolvera.dev"

The tools we are going to use are a ZEIT account, npm, now-cli and a browser. I will assume that you already have npm and a browser installed.

The API that we are going to write is actually how I started the prototype of DNS check. DNS check was born out of the necessity to explain clients and co-workers what's happening with a domain when we make a DNS change. Tools already exist that provide DNS lookup information, but most of them aren't simple enough, in my opinion.

We only need two dependencies for this project, Bottle and dnspython.

Create the Python function

The first step is to create a function that returns the DNS records (A, CNAME, MX, for example.) of a given domain name using dnspython.

import dns.resolver

ids = [
    "A",
    "NS",
    "CNAME",
    "SOA",
    "MX",
    "TXT",
    "AAAA",
]

def resolve(domain: str):
    result = []
    for record in ids:
        try:
            answers = dns.resolver.query(domain, record)
            data = [{"record": record, "value": rdata.to_text()} 
                    for rdata in answers]
            for item in data:
                result.append(item)
        except Exception as e:
            print(e)
    return result

print(resolve('jolvera.dev'))

Before creating any files in our computer, let's try this function in Repl.it. If everything is okay you will get something like:

[
    {
        'record': 'A',
        'value': '3.19.25.128'
    },
    {
        'record': 'A',
        'value': '3.19.23.166'
    },
    {
        'record': 'NS',
        'value': 'b.zeit-world.org.'
    },
    {
        'record': 'NS',
        'value': 'e.zeit-world.net.'
    },
    {
        'record': 'NS',
        'value': 'f.zeit-world.com.'
    },
    {
        'record': 'NS',
        'value': 'a.zeit-world.co.uk.'
    },
    {
        'record': 'MX',
        'value': '10aspmx1.migadu.com.'
    },
    {
        'record': 'TXT',
        'value': '"v=spf1 a mx include:spf.migadu.com ~all"'
    }
]

Try with different domains, make sure it works, then proceed to create the API endpoint.

Convert the function to a Serverless function

The serverless platform we are going to use is ZEIT Now. They make serverless development extremely easy and projects deployments a joy.

To follow this example, create an account at zeit.co and install Now globally in your system by running npm i -g now, and with that you are ready to go.

Folder structure

Create a folder and call it dnscheck then create another folder inside called api.

$ mkdir dnscheck
$ mkdir dnscheck/api

This folder structure is all you need.

Virtual environment

Python best practices say we need a virtual environment, so we don't install our dependencies globally in the system (Like what we did with npm i -g now but in the Python world). In this example, I'm going to use virtualenv, but you can use Poetry or Pipenv as well. I chose virtualenv here to keep things simple. Inside the dnscheck/api folder, create a new virtual environment.

$ cd dnscheck/api
(dnscheck/api) $ virtualenv venv

Then, activate the virtual environment.

(dnscheck/api) $ ./venv/bin/activate

With this, we have our virtual environment ready to go, and we can start installing dependencies that are only available in our project. We start by installing dnspython and bottle.

(dnscheck/api) $ pip install bottle dnspython

Then we create a requirements.txt file that lists all the dependencies we are using. Now uses this to install the dependencies while it's deploying.

(dnscheck/api) $ pip freeze > requirements.txt

API endpoint with Bottle

Inside the api/ folder, create a file named index.py. Inside write the following code.

from bottle import Bottle, request
import dns.resolver

app = Bottle()

ids = [
    "A",
    "NS",
    "CNAME",
    "SOA",
    "MX",
    "TXT",
    "AAAA",
]


@app.get('/api')
def api():
    domain = request.query.get('domain')
    result = resolve(domain)
    return dict(data=result)
    

def resolve(domain: str):
    result = []
    for record in ids:
        try:
            answers = dns.resolver.query(domain, record)
            data = [{"record": record, "value": rdata.to_text()} 
                    for rdata in answers]
            for item in data:
                result.append(item)
        except Exception as e:
            print(e)
    return result

This code listens for GET requests in http://localhost:3000/api and tries to grab a query parameter named domain, so to be able to get the data we have to send a get request to http://localhost:3000/api?domain=jolvera.dev or simply open the URL in the browser.

To test it locally, inside the dnscheck/ folder run now dev,

(dnscheck/api) $ cd ..
(dnscheck) $ now dev

if everything is okay, you should get the development URL.

> Ready! Available at http://localhost:3000

If so, go to your browser and visit http://localhost:3000/api?domain=jolvera.dev and you should see a response in JSON like shown in the following screenshot.

Screenshot of the Python function in the browser

Deploy to Now

Everything is ready to deploy our serverless function to Now and share it to the world!

Inside the dnscheck/ folder run now

(dnscheck) $ now

and If everything goes okay, we will get a URL like https://dnscheck-example-2k7pyj3cr.now.sh/, in my case I got https://dnscheck-post-example.jolvera.now.sh/, and you can test my domain going to https://dnscheck-post-example.jolvera.now.sh/api?domain=jolvera.dev.

See the project in action.

Conclusion

We were able to prototype a small idea into a workable API and share it on the web with ease. What we did in this post was the premise of my project and the train of thought I had to get something working. After I got the example working, I kept trying new ideas on it and added domain validation, a Next.js frontend and more.

Being able to prototype an idea into an API with these few tools encourage me to try new things more often. Once you get a first working example you will get motivate to keep going and build lots of things. You can check my finished project dnscheck.app in these two repositories:

Also published in:

  • DEV.to