Build a URL shortener using FastAPI and Redis

In this post, we are going to build a simple URL shortening API using FastAPI and Redis. Redis is a key: value based database, where everything is stored as key-value pairs(much like the python dictionary). Install Redis from its official website.

Start the Redis server before going any further(If you have installed Redis already!)

redis-server /etc/redis/6379.conf

Import all necessary things

import uuid
import redis
from fastapi import FastAPI
from pydantic import BaseModel
from starlette.responses import RedirectResponse
  • uuid for generating random strings
  • redis for connecting to our Redis server
  • FastAPI is the app
  • BaseModel for data validation
  • RedirectResponse for redirecting to original URL

Create the app and redis client

app = FastAPI()
r = redis.Redis()

Create a test endpoint

@app.get("/")
def read_root():
    return {"message": "Welcome to url shortening app"}

Run the app using uvicorn main:app --reload. Now navigate to http://localhost:8000 to see our endpoint working successfully.

Define the URL shortening endpoint

class Item(BaseModel):
    url: str
    custom_target: str = None

@app.post("/")
def shorten_url(item: Item):
    url = item.url
    if r.get(url) is None:
        new_name = item.custom_target or str(uuid.uuid4())[-6:]
        if r.mset({url: new_name}):
            return {"url": url, "short": r.get(url)}
        else:
            return {"message": "failed"}
    return {"message": "URL already exists", "short": r.get(url)}

POST "/" should receive data in the specified format. A URL of type string and a custom short name (Optional). In case you didn't provide this, the program will create one for you(of length 6). We use mset(you could also use set) to set values in Redis. All values must be of dictionary type. You can retrieve any value using get. Both set and get returns None if not found.

So if I post data as {"url":"google.com", "custom_target": "amal"}, Redis will save this as {"google.com": "amal"}.

Define the redirect endpoint

@app.get("/{short}")
def redirect_url(short: str):
    for key in r.keys():
        if r.get(key).decode("utf8") == short:
            return RedirectResponse(url=key.decode("utf8"))

    return {"message": "URL not defined"}

This will search for the short URL in our database. After a successful URL is created, navigate to http://localhost:8000/<short_url>, It'll redirect you to the original URL.

Define an endpoint to retrieve all URLs in database

@app.get("/get")
def get_all_urls():
    data = []
    for key in r.keys():
        data.append({key.decode("utf8"): r.get(key).decode("utf8")})
    return data
  • All the returned values should be decoded as they're binary encoded. Simply use string.decode('utf8').

  • VERY IMPORTANT /get should be defined before /{short}, else FastAPI will read get as a string and return "URL not defined".

Test the endpoints

❯ curl "http://localhost:8000/"
{"message":"Welcome to url shortening app"}%
❯ curl -X POST "http://localhost:8000/" -H  "accept: application/json" -H  "Content-Type: application/json" -d "{\"url\":\"https://youtube.com\"}"
{"message":"URL already exists","short":"4fb5bb"}%
❯ curl -X POST "http://localhost:8000/" -H  "accept: application/json" -H  "Content-Type: application/json" -d "{\"url\":\"https://gmail.com\"}"
{"url":"https://gmail.com","short":"f5ae2a"}%
❯ curl "http://localhost:8000/get"
[{"https://gmail.com":"f5ae2a"},{"https://youtube.com":"4fb5bb"},{"https://google.com":"amal"}]%

Complete Code: here

Improvements

This is just an example program. You need a lot of improvements if you're deploying this into production. You can add a nice frontend using vue/react. Use docker to deploy as it can help scale when deployed to GAE or any other services. URL shortening apps are used mainly because of the easiness and the analytics they provide, therefore add a lot of analytics😉.</short_url>

No Comments Yet