loading...

Build Url Shortener API In Django - Redis

magesh236 profile image Mageshwaran Updated on ・5 min read

We will build a simple Url Shortener using django and redis, data will be stored in SQLite and redis will be used as cache storage.

Setup a virtual environment for this project

Setup a virtual environment for this project, so it will not affect the projects in your machine.

Installing the dependency for the project

pip install django
pip install redis

above command will install the latest version of the packages

Setting up the django project

for detailed reference about django visit the page
Now we can create our project

django-admin startproject urlshortener
cd urlshortener

To check the app running successfully, run this it will open a rocket page(in terminal you can see unapplied migration, we will see this in migrate)

python manage.py runserver

Alt Text

The below command will create a app call short(where logic will be written) inside the urlshortener

python manage.py startapp short

now we have to add the app short to the settings.py, this will help django to identify the changes in models

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'short',
]
What is django model?
You can think model as a table in django, in SQL we use tables to store the data, right, In the same way model is being used. Table schema can be created in model then it can be migrated to the database.

by default django shipped with SQLite DB, you can change it to whatever data source you want to connect

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
models

Now add this code in short/models.py

from django.db import models

class URL(models.Model):
    full_url = models.TextField()
    hash = models.TextField(unique=True)

    class Meta:
        db_table = 'url_shortener'

in full_url we will hold the given url
in hash will store the unique key
db_table holds the name of the table

Run this

python manage.py makemigrations short

By running makemigrations, you’re telling Django that you’ve made some changes to your models, the above command only looks for the changes in short app, to load changes from all the app models just run this

 python manage.py makemigrations

this will creates migrations files inside the story/migrations directory, after that to move the change to SQLite db run this

python manage.py migrate

when you run this command this will apply the admin, session related migration to the database, all the warnings in the terminal will be gone

View and Urls

Where we will write the logic

create a urls.py inside short directory, after that include the file in urlshortener/urls.py, just like this

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include("short.urls")),
]

now lets write a simple HttpResponse view called as home, in short/views.py

from django.http import HttpResponse

def home(request):
    current_site = get_current_site(request)
    return HttpResponse("<h1>Url Shortener</h1>")

then link the view with a url

from django.urls import path
from short import views

urlpatterns = [
    path('', views.home, name="Home"),
]

open the home url (http://127.0.0.1:8000/) you will get the HttpResponse
Alt Text

project is working fine, now lets write some logic for the shortener
for detailed reference check this Designing a URL Shortening service like TinyURL

this will generate a random key value of length 7 with the combination of uppercase, lowercase and numbers. N can be adjusted as we want

import string, random 
# hash length
N = 7
s = string.ascii_uppercase + string.ascii_lowercase + string.digits
# generate a random string of length 7
url_id = ''.join(random.choices(s, k=N))

convert the logic to a function and it will create a new record in table for each new url short request

import string, random 
from short.models import URL

def shortit(long_url):
    # hash length
    N = 7
    s = string.ascii_uppercase + string.ascii_lowercase + string.digits
    # generate a random string of length 7
    url_id = ''.join(random.choices(s, k=N))
    # check the hash id is in
    if not URL.objects.filter(hash=url_id).exists():
        # create a new entry for new one
        create = URL.objects.create(full_url=long_url, hash=url_id)
        return url_id
    else:
        # if hash id already exists create a new hash
        shortit(url)

this view function will get the request and use the above function to convert it

from django.http import JsonResponse
from django.contrib.sites.shortcuts import get_current_site
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def short_url(request):
    long_url = request.POST.get("url")
    # get the unique id for long url
    hash = shortit(long_url)
    # get the host url
    current_site = get_current_site(request)
    data = {
        "success": True,
        "id": hash,
        "link": "http://{}/{}".format(current_site, hash),
        "long_url": long_url
    }
    return JsonResponse(data)

link this view function to short/urls.py urlpatterns list

   path('shorten', views.short_url, name="ShortUrl"),

Alt Text

Now lets do the retrieving logic
redis - in-memory data structure store
we will use redis as a cache storage, I'm using redis in my local system

import redis
# host - running host
# port - the port in which its running
# db - by default 0 is the default db, you can change it
rds = redis.Redis(host='localhost', port=6379, db=0)

just run it using redis-server, once the redis server is stoped data will be cleared.
Terminal

from django.shortcuts import redirect

def redirector(request,hash_id=None):
    # get the value from redis key, if value not in return None
    hash_code = rds.get(hash_id)
    if hash_code is not None:
        return redirect(hash_code.decode('ascii'))

    if URL.objects.filter(hash=hash_id).exists():
        url = URL.objects.get(hash=hash_id)
        # set the value in redis for faster access
        rds.set(hash_id,url.full_url)
        # redirect the page to the respective url
        return redirect(url.full_url)
    else:
        # if the give key not in redis and db
        return JsonResponse({"success":False})

Note: redis in python 3 always return binary string for that we used decode('ascii')

link the redirector view function in short/urls.py urlpatterns list

    path('<str:hash_id>/', views.redirector, name="Redirector"),

hash_id is a URL parameters used to get the value from url

Giphy

Postman

DONE RIGHT, this is the project structure
Vscode

Thanks you, have a great day ahead.🤪😎

Posted on by:

magesh236 profile

Mageshwaran

@magesh236

Software Engineer - Newbie Photography

Discussion

pic
Editor guide
 

Thanks. It looks very good <3. For improvement;

You can find count for all possibilities to hash length. Because you can check it like that;

if n_possible_count == redis_n_count:
    n_possible_count += 1

So you avoid duplicates. Of course, you should check the hash either exists in the database or not.

 

Sure, we can use incremental value. Instead of checking the hash is already exists.
This will improve the performance.