Serverless URL Shortener

Have you haver wanted to have you own Url shortener? Here you can find a simple implementation with only 68 lines of code!

Open-source code can be found here: Repository

How it works

Using chalice (python framework) to deploy the lambda functions and create an API gateway to handle the endpoints. Functions are deploy in AWS Lambda with API Gateway to provide the endpoints and DynamoDB to store the original url and the corresponding short code.

This is in production at my website, I have it hosted in netlify, and to make it as simple as possible I just created a file called “_redirects”.

/*  https://[API_ENDPOINT].execute-api.[API_REGION].amazonaws.com/api/:splat  200

Netlify dns interprets the content of this file and creates a redirect route to my API for any unknown path after the “/”. The “:splat” variable is the input that is received on the path after the “/” and is sent to my API, which is a url shortener code. If my API doesn’t recognizer the code, it redirects to /404 page. You can find more information here

Shortening

I have a /shorten endpoint which creates a short unique ID and adds a new entry to the database, or I simply use the DynamoDB interface and specify a custom url shortener code.

Each item on dynamo is simply:

{
  originalUrl: "example.com"
  code: "example"
}

I chose not to develop my own interface since I already had one for such a simple operation (DynamoDB interface) avoiding work overhead, having to meet minimum security requirements and having to deploy any frontend.

Benefits

Seamless integration with current website content, every path is still available, and only unknown paths will be sent to decodify into and original url.

Code

To deploy simply check the chalice ReadMe: https://github.com/aws/chalice/

Repository

from chalice import Chalice, Response
import shortid
import boto3
import os

dynamodb = boto3.resource('dynamodb')
sid = shortid.ShortId()
app = Chalice(app_name=os.environ['DB_NAME'])

@app.route('/{code}', cors = True)
def get_original_url(code):
    table = dynamodb.Table(os.environ['DB_NAME'])
    try:
        response = table.get_item(
            Key={
                'code': code
            }
        )
    except Exception as e:
        return Response(
            body='',
            status_code=302,
            headers={
                'Location': os.environ['BASE_URL'] + '404'
            }
        )
    else:
        return Response(
            body='',
            status_code=302,
            headers={
                'Location': response['Item']['originalUrl']
            }
        )

@app.route('/shorten', cors = True, methods=['POST'])
def create_short_url():
    table = dynamodb.Table(os.environ['DB_NAME'])
    params = app.current_request.json_body
    try:
        response = table.get_item(
            Key={
                'code': params['code']
            }
        )
        return {
                'Error': 'Code is already in use to redirect to: ' + response['Item']['originalUrl'],
                'url': response['Item']['originalUrl']
            }
    except Exception as e:
        try:
            code = params['code']
        except Exception as e:
            code = sid.generate()

        try:            
            response = table.put_item(
                Item={
                        'code': code,
                        'originalUrl': params['originalUrl']
                    }
            )
            return{
                    'url': os.environ['BASE_URL'] + code,
                    'originalUrl': params['originalUrl']
                }
        except Exception as e:
            return(e)