DEV Community

Mageshwaran
Mageshwaran

Posted on • Edited on

Build Url Shortener API In Django - Redis

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

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

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

Alt Text

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

python manage.py startapp short
Enter fullscreen mode Exit fullscreen mode

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',
]
Enter fullscreen mode Exit fullscreen mode
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.
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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

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

then link the view with a url

from django.urls import path
from short import views

urlpatterns = [
    path('', views.home, name="Home"),
]
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

   path('shorten', views.short_url, name="ShortUrl"),
Enter fullscreen mode Exit fullscreen mode

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

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

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

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.๐Ÿคช๐Ÿ˜Ž

Top comments (5)

Collapse
 
itachiuchiha profile image
Itachi Uchiha • Edited

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.

Collapse
 
max236 profile image
Mageshwaran

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

Collapse
 
sivamuthu252 profile image
Sivamuthu T

Awesome

Collapse
 
zdev1official profile image
ZDev1Official

Cool!

Collapse
 
nikhilroy2 profile image
Nikhil Chandra Roy

import redis
Import 'redis' could not be solved from resource
can you tell me how to import it, I did pip install redis and then import it but not working