5 min read

Design a minimalist URL shortening service with AWS Lambda and seamlessly integrated with GitHub as the storage

A. Architecture overview:

What we gonna to build today is a simple shorten URLs service like: bit.ly, Facebook, Twitter shorten URLs service, ...

Instead of using the a very long url like: example.com/this-is-very-long-long-long-long-long-url, we can use something like: example.com/my-url

How it works:

We will create a JSON file on Github that contains our shorten urls as keys and our original link as values.

And when users make request to our Lambda function using shorten URLs, we will retrieve the original links from Github and redirect users to the original links.

B. Tutorial

Step 1: Download Python Serverless template.

Run the following command to get the template:

# Clone the template, make sure there is no folder named gh-short-url before:
git clone -b without_serverless_account https://github.com/sexydevops/fastapi-aws-starter-kit.git gh-short-url

# Change directory to the template:
cd gh-short-url

For more detail about the template, you can read the post.

Step 2: Setup the project

a. If it is the first time you are using the template please check the section A to setup necessarily things: AWS credentials, Nodejs, Python, Make, ...

b. If you have setup the template before and ensure everything is okay: Conda and its env, Python, Nodejs, Make, AWS credentials, simply run the following command:

conda activate serverless_env
npm i

Step 3: Deploy the project to make sure everything is okay

1️⃣. Activate our Conda env if you have not (just a reminder):
conda activate serverless_env
2️⃣. Edit the service name:

Open the serverless.yaml file and find the line

service: fastapi-aws-starter-kit

Replace fastapi-aws-starter-kit with anything you like, e.g: gh-shorten-url, please make sure that you do not have any existing services with the name: gh-shorten-url

3️⃣. Deploy the service:

Make sure that you have necessary permission to deploy to AWS.

Run the following command to deploy your service:

make deploy
# or if you do not have make
npx sls deploy

You will get the output like that:

Let's test our service:

curl your_service_endpoint

Perfect! Let's move on!

Step 4: Create a data file on Github

  • Go to Github and create a new repo to store our shorten URLs data (choose Public)
  • Important: At the time of the post, the tutorial only support public link only, please do not store your sensitive link publicly in place like Github.
  • And then click on Create a new file
  • We then can create a file named: data.json with its content:
{
  "goo": "https://google.com"
}

Important: we need to include http or https or each url. Otherwise, it wont work.

  • Click on Commit changes... to create the file.

Step 5: Develop our function

Go back to our project that we have done from Step 1 to Step 3. Please make sure that we already activated serverless_env env in case you missed it :

conda activate serverless_env

a. Add requests module:

poetry add requests

The output will be something like this:

Typically, it will update the pyproject.toml and poetry.lock file (just like package.json and package-lock.json in Nodejs).

You can use the git diff command to see the diff.

b. Update the fastapi_aws_starter_kit/fastapi_app.py file with the following content:

import requests
import json
import os
from fastapi import FastAPI, Response
from starlette.status import HTTP_404_NOT_FOUND, HTTP_301_MOVED_PERMANENTLY
from starlette.middleware.cors import CORSMiddleware
from fastapi_aws_starter_kit.config import PROJECT_NAME, API_VERSION

# Declare the application
app = FastAPI(title=PROJECT_NAME, debug=False, version=API_VERSION)


app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

### Our main logic ###
@app.get("/shorten/{shorten_url}")
def get_original_url(shorten_url: str, response: Response):
    # declare link to our data file on Github repo, you need to update to your own link
    ## replace sexydevops with your Github organization/user name
    ## replace shorten-urls-data with your reponame
    data_link = 'https://raw.githubusercontent.com/sexydevops/shorten-urls-data/main/data.json'
    
    # convert file your file data to a dict 
    response_json = json.loads(requests.get(data_link).text)
    
    # find the original url based on the shorten_url argument
    original_url = response_json.get(shorten_url)

    # if the original url does not exist, we will return 404 status code
    if original_url is None:
        response.status_code = HTTP_404_NOT_FOUND
        return { "message": "can not find the original url" }
      
    # else 
    # - we will return status_code 301 to our function
    response.status_code = HTTP_301_MOVED_PERMANENTLY
    #print(original_url)
    # - with a "Location" response header <= Magic begins here
    # Read more about the Location response header here
    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location
    response.headers["Location"] = original_url
    return { "message": original_url }

c. Testing our function in local server:

  • Open your terminal tab and if you forget to activate serverless_env conda env, please run this:
conda activate serverless_env
  • Start the offine server:
# If you have make installed on your machine:
make serve

# If you do not have make installed, run the following command instead:
npx sls offline
{
  "message": "can not find the original url"
}

d. Update the data file

It is cool, right? Now you can update the content of the data file to create more shorten links.

{
    "goo": "https://google.com",
    "fb": "https://facebook.com",
    "yt": "https://youtube.com"
}

Due to Github servers, it may take time to get the latest content of the data file.

e. Deployment:

  • Grab the endpoint and you can test the app in your browsers like the section c. Testing our function in local server above