Build and Deploy a Serverless URL Shortener with Python and Fauna
Tags: fauna, serverless, python, webdev
Published: 2020-12-17 19:51:42

Recently, I have been exploring and experimenting with serverless databases and built a [Telegram Bot to manage a to-do list with Fauna](https://dev.to/lordghostx/building-a-telegram-bot-with-python-and-fauna-494i). So I decided to create something a bit complex this time around like a URL shortener.

A URL shortener is a service that shortens long web addresses making them easily shareable on social media. They work by generating a short identifier to represent a long web address. So, when a user submits the identifier to the platform, they are automatically redirected to the original URL.

In this article, we will be building a simple URL shortener using the Python programming language, Flask web framework, and the serverless database, [Fauna](https://fauna.com/). We will also be deploying our app to Heroku.

# Why Use Fauna?

[Fauna](https://fauna.com/) is a serverless document database that offers two interfaces, GraphQL and the Fauna Query Language (FQL). It can store collections, indexes, and even other databases (multi-tenancy). Here are a few reasons for using Fauna:

- Fauna is very easy to learn and integrate into projects.
- Increase in productivity as Fauna handles a lot of database operations under the hood for you.
- Fauna provides infinite scalability for your projects.
- Fauna allows you to model any data that is required by your business e.g relations, documents, graphs, or a combination.
- Fauna provides native GraphQL support for your database along with functionalities such as transactions, custom logic, and access control.
- Fauna databases have low-latency, so you can serve your users everywhere with consistent data that is being replicated in real-time to multiple regions and served from the closest locations to them.

# Getting Started with Fauna

## Step 1: Setting up our Fauna Database

The first thing we need to do is create the database for our app in our Fauna dashboard. If you have not created an account on Fauna before now, create one here: [https://dashboard.fauna.com/accounts/register](https://dashboard.fauna.com/accounts/register)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dwprs4756tzz34rqbrgs.png)

Provide a name for your database then press the `SAVE` button. The next thing we have to do is create a collection in our database. A collection is similar to SQL tables that contain data with similar characteristics e.g user collection that contain information about users in the database. We will start by pressing the `NEW COLLECTION` button.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zk7egrpgxuws1w02o20g.png)

Then, we supply a name for the collection we will be creating and working with (we will be using `urls` for this example). Once you have supplied this information proceed by pressing the `SAVE` button.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lh3tpj0dfhqgj7cwbivy.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/erlt04ly5o9s1g07tsl3.png)

We also need to create an index for our collection. A Fauna index allows us to browse through data that is stored in a database collection, based on specific attributes. To create one, navigate to the `DB Overview` tab on the Fauna sidebar (left side of the screen) then click the `NEW INDEX` button.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/tkut670to862ag3d5uka.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zsrmbif030yhj9vaz4a8.png)

Supply the name you want to use for your index, set the terms to `data.identifier` as we will be using that variable to reference our URLs later. Also, remember to select the `Unique` attribute for our index, this ensures that we do not have a duplicate in our database entries.

## Step 2: Generating Fauna API Key

We will need to create a Fauna API Key to connect to our database from our Python application. To do this, navigate to the security settings on the Fauna sidebar (left side of the screen).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/5h3c6432h3tzo7z83vai.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/l99na6wh10j4je1rd84y.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/80qxqejaacqpwooo8rc3.png)

Once you have done this, you will be presented with your API key (hidden here for privacy reasons). The key should be copied as soon as it is generated then stored somewhere you can easily retrieve.

## Step 3: Integrating Fauna with Python

Next, we need to get the Python library for Fauna. It’s available on pip and can be installed with a single line in our terminal.

```
$ pip install faunadb
```

After this is installed, we are going to run the sample code provided in Fauna Python driver docs [https://docs.fauna.com/fauna/current/drivers/python.html](https://docs.fauna.com/fauna/current/drivers/python.html)

```python
from faunadb import query as q
from faunadb.objects import Ref
from faunadb.client import FaunaClient

client = FaunaClient(secret="your-secret-here")

indexes = client.query(q.paginate(q.indexes()))

print(indexes)
```

The code above shows how the Fauna Python driver connects to a database with its API key and prints the indexes associated with it. The result from running this code is similar to the image below.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xvpyzbnkgsdzb5qg1zua.png)

# Building our Shortener with Flask

Now that we have successfully integrated our Python script with Fauna, let’s get started with building our shortener with Flask. To keep things simple, we’ll be building our app as an API instead of a full-stack web application with HTML and CSS.

## Step 1: Setting up our Flask Server

We need to install the flask library the same way we installed Fauna earlier using pip and our terminal.

```
$ pip install flask
```

Let’s get a basic server running in Flask that will display the text `Hello World` when opened. Create a new project folder and a python file with the name `app.py` and type the following code inside.

```python
from flask import Flask

app = Flask(__name__)


@app.route("/")
def home():
    return "Hello World!"


if __name__ == "__main__":
    app.run(debug=True)
```

When we run our `app.py` file we should get a response similar to the image below

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/5tx2mcyonvycqjwexdor.png)

If you get this, you’re on the right track. Open the following in your browser `http://127.0.0.1:5000` to access the app.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/h6fi2xvyn8udubpi8g2d.png)

## Step 2: Create the Shortener Function

This is a core part of our application, we need a function that generates a random string that will serve as our identifier for our shortened URLs.

```python
import string
import random

def generate_identifier(n=6):
    identifier = ""
    for i in range(n):
        identifier += random.choice(string.ascii_letters)
    return identifier
```

We used the `choice` method in the `random` library to randomly select a letter provided by `string.ascii_letters` variable then concatenated the results to generate a random string. When we run this function we should get results like the ones in the image below.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dnko06434mp50ov59vbz.png)

You might also want to look at the [`NewId` function](https://docs.fauna.com/fauna/current/api/fql/functions/newid?lang=python) function provided by Fauna which can be used to generate time-based random numbers that are guaranteed to be unique across the entire cluster of your database. Also, once generated they can never be generated a second time.

## Step 3: Build the Shortener Endpoint

We have now created a function that is responsible for generating the identifiers, we need to create an endpoint in our web application that will take a web address as input, generate an identifier, save it in our database and give the user the shortened URL.

```python
import string
import random
from flask import Flask, jsonify, request
from faunadb import query as q
from faunadb.objects import Ref
from faunadb.client import FaunaClient

app = Flask(__name__)
client = FaunaClient(secret="your-secret-here")


def generate_identifier(n=6):
    identifier = ""
    for i in range(n):
        identifier += random.choice(string.ascii_letters)
    return identifier


@app.route("/")
def home():
    return "Hello World!"


@app.route("/generate/<path:address>/")
def generate(address):
    identifier = generate_identifier()
    client.query(q.create(q.collection("urls"), {
        "data": {
            "identifier": identifier,
            "url": address
        }
    }))

    shortened_url = request.host_url + identifier
    return jsonify({"identifier": identifier, "shortened_url": shortened_url})


if __name__ == "__main__":
    app.run(debug=True)
```

We used the `create` method of the FQL client to store the original URL provided by the user along with the short identifier we generated in our database collection using `client.query(q.create(q.collection(collection_name), data))`.

Here, we imported the `jsonify` method from Flask which is used in REST APIs to return JSON data and the `request` variable so we can access data our browser sent to the server along with its request. When we access this endpoint in our browser, we should get a response similar to this. We can also see our newly created document in our Fauna dashboard.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/347v6rf6tjo7979cjkad.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/3hhgqhfblhuj6hwmbuih.png)

## Step 4: Write the URL Redirection Logic

The only thing left is building the logic that will redirect the user to the original URL when it receives the shortened URL. For this to work, we would build an endpoint that will take the identifier as input, query our database then redirect the user to the original URL if it exists, or display a 404 page if it doesn’t.

```python
@app.route("/<string:identifier>/")
def fetch_original(identifier):
    try:
        url = client.query(q.get(q.match(q.index("urls_by_identifier"), identifier)))
    except:
        abort(404)

    return redirect(url["data"]["url"])
```

We imported 2 more methods from Flask (`abort` and `redirect`); `abort` will stop the code that is currently running and trigger the response of a provided status code while `redirect` will redirect a user to another web address, either on your server or an external one. The full code should resemble the one below.

```python
import string
import random
from flask import Flask, jsonify, request, abort, redirect
from faunadb import query as q
from faunadb.objects import Ref
from faunadb.client import FaunaClient

app = Flask(__name__)
client = FaunaClient(secret="your-secret-here")


def generate_identifier(n=6):
    identifier = ""
    for i in range(n):
        identifier += random.choice(string.ascii_letters)
    return identifier


@app.route("/")
def home():
    return "Hello World!"


@app.route("/generate/<path:address>/")
def generate(address):
    identifier = generate_identifier()
    client.query(q.create(q.collection("urls"), {
        "data": {
            "identifier": identifier,
            "url": address
        }
    }))

    shortened_url = request.host_url + identifier
    return jsonify({"identifier": identifier, "shortened_url": shortened_url})


@app.route("/<string:identifier>/")
def fetch_original(identifier):
    try:
        url = client.query(q.get(q.match(q.index("urls_by_identifier"), identifier)))
    except:
        abort(404)

    return redirect(url["data"]["url"])


if __name__ == "__main__":
    app.run(debug=True)
```

Now that wraps up the whole code for our URL shortener service with Python and Fauna.

# Deploying our Python App to Heroku

Deploying an application means you are uploading it online so it can be accessible to anyone from anywhere in the world. In this article, we would be deploying our Python application to a free and popular hosting platform called Heroku.

The prerequisites needed to work with Heroku is an account on Heroku, Heroku CLI, and GIT.
- Create a Heroku account here - [https://signup.heroku.com/login](https://signup.heroku.com/login)
- Follow this guide to install Heroku CLI - [https://devcenter.heroku.com/articles/heroku-cli](https://devcenter.heroku.com/articles/heroku-cli)
- Follow this guide to install GIT - [https://git-scm.com/book/en/v2/Getting-Started-Installing-Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)

## Step 1: Install Gunicorn

Gunicorn is a pure-Python HTTP server for WSGI applications. It allows you to run any Python application concurrently by running multiple Python processes within a single dyno. It provides a perfect balance of performance, flexibility, and configuration simplicity when deploying a web app to somewhere such as [Heroku](https://www.heroku.com/)

```
$ pip install gunicorn
```

## Step 2: Create a `wsgi.py` file

```python
from app import app

if __name__ == "__main__":
    app.run()
```

A WSGI file is an entry to our Python application, create a file with the name `wsgi.py` in the same folder as our `app.py` and save the content above in it. Our file structure should look like this.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jfdyk0prfwxn94hbjjx9.png)

## Step 3: Generate your `requirements.txt` file

The next step is to generate your `requirements.txt` file with either `pip freeze` or `pipreqs`. Whatever method you use, make sure `gunicorn` is in your requirements file.

```
$ pip freeze > requirements.txt
```

OR

```
$ pip install pipreqs
$ pipreqs .
```

Our file structure and `requirements.txt` should look like the image below.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/opa2iriat3kz168lzo43.png)

## Step 4: Create a `Procfile`

```
web: gunicorn wsgi:app
```

A `Procfile` is used to specify commands that should be executed by a Heroku app when they are started. Create a file with the name `Procfile` in the same folder as our `app.py` and `wsgi.py` then save the content above in it. Our file structure should look like this.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/avunucnsl148grjdruyy.png)

## Step 5: Create our App in Heroku

Navigate to the `Create new app` tab in your Heroku dashboard and you will be presented with a screen similar to the one below. Choose a name for your app and the region it should be deployed to.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6t6ejjtowy0adk98fjd3.png)

## Step 6: Deploy our App to Heroku

```
$ heroku login
$ git init
$ heroku git:remote -a fauna-shortener
$ git add .
$ git commit -m "deploying to heroku"
$ git push heroku master
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/efjq6t4w3s74ndmc6ln6.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ha47btz0vsl0tdww0gu2.png)

Congratulations, we have successfully deployed our application to Heroku and it can be accessed from anywhere in the world with the web address that will be given to you by Heroku after deployment. Since the name of the app created in this tutorial is `fauna-shortener`, its URL would be [https://fauna-shortener.herokuapp.com/](https://fauna-shortener.herokuapp.com/)

# Testing our URL Shortener

Now we have deployed our web app to Heroku, we can shorten URLs using the `generate` endpoint.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/z9x7zkce2fodjcuerhk8.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4mpj93zcof4ukqi5bukh.gif)

# Conclusion

A URL shortener is a service that takes a long URL and converts it to a shorter one. When the shortened link is visited, the user is redirected to the original URL. In this article, we used Fauna's serverless database to build a simple URL shortener. We also saw how easy it was to integrate Fauna with Python, got the chance to explore its CRUD functionalities, and finally deploy our app to Heroku.

The source code of the app is available on [GitHub](https://github.com/LordGhostX/fauna-url-shortener). If you have any questions, don't hesitate to contact me on Twitter:  [@LordGhostX](https://twitter.com/LordGhostX)