DEV Community

loading...

Build and Deploy a Serverless URL Shortener with Python and Fauna

lordghostx profile image LordGhostX ・10 min read

Recently, I have been exploring and experimenting with serverless databases and built a Telegram Bot to manage a to-do list with Fauna. 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. We will also be deploying our app to Heroku.

Why Use Fauna?

Fauna 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

Alt Text

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

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

Alt Text

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

Alt Text

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

Alt Text

Alt Text

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
Enter fullscreen mode Exit fullscreen mode

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

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)
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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.

from flask import Flask

app = Flask(__name__)


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


if __name__ == "__main__":
    app.run(debug=True)
Enter fullscreen mode Exit fullscreen mode

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

Alt Text

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

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.

import string
import random

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

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

You might also want to look at the NewId function 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.

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)
Enter fullscreen mode Exit fullscreen mode

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

Alt Text

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.

@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"])
Enter fullscreen mode Exit fullscreen mode

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.

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)
Enter fullscreen mode Exit fullscreen mode

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.

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

$ pip install gunicorn
Enter fullscreen mode Exit fullscreen mode

Step 2: Create a wsgi.py file

from app import app

if __name__ == "__main__":
    app.run()
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

OR

$ pip install pipreqs
$ pipreqs .
Enter fullscreen mode Exit fullscreen mode

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

Alt Text

Step 4: Create a Procfile

web: gunicorn wsgi:app
Enter fullscreen mode Exit fullscreen mode

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

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

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
Enter fullscreen mode Exit fullscreen mode

Alt Text

Alt Text

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/

Testing our URL Shortener

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

Alt Text

Alt Text

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. If you have any questions, don't hesitate to contact me on Twitter: @LordGhostX

Discussion (3)

pic
Editor guide
Collapse
josiasaurel profile image
Josias Aurel

Interesting post. For the short string generation, you could have used the built-in Python secrets module and generate a URL safe string using secrets.token_urlsafe(<length>).

Collapse
lordghostx profile image
LordGhostX Author

Thanks for this, I actually know this library and method but it skipped my mind when writing the article 😅😅😅

Collapse
yoganandam profile image
Yogananda Muthaiah

Try to create a account in accounts.sap.com - you can deploy your code in cloundfoundry .. -you can see in blogs.sap.com