Introduction
This is a simple url shortener application developed in Python FastAPI.
Click here to access the Github repo.
Click here to access Postman API collection.
In this tutorial we will focus mainly on 3 things
- It's beginner friendly.
- Focused on industry best practices.
- Deploy to cloud.
The scope of this tutorial is to focus mostly on building APIs rather than Python basics. If you're completely new to Python I would highly encourage you to have a strong knowledge on Python basics before going into this article.
Prerequisites
What is a Virtual environment ?
A virtual environment is an isolated environment used to keep your project dependencies independently from any other projects or system configurations. This helps to avoid dependency conflicts with different versions.
Live demo
click here to test live endpoints
Develop url shortener APIs
Setup
- Open the VS code and create a new file named
main.py
in the root folder. - This
main.py
file is responsible for running your application and acts as an entry point for your app. - Create a Python virtual environment using this command
python -m venv virtual_env_name
- Activate your virtual environment with respective operating system commands.
# Linux
source virtual_env_name/bin/activate
# Windows
virtual_env_name\Scripts\activate.bat
#Mac
source virtual_env_name/bin/activate
- Install all the required dependencies in the Python virtual environment.
# add fast api package
pip install fastapi
# add web server for running fast api
pip install "uvicorn[standard]"
# add package for cleaning cache files
pip install pyclean
-
Add below snippet to the
main.py
file. This snippet has two endpoints of request method type GET. One endpoint returns{"hello" : "world"}
json response and other returns the dynamic parameter you passed asitem_id
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersfrom fastapi import FastAPI from url_shortener import router # Create the app app = FastAPI() @app.get("/") def read_root(): return {"Hello": "World"} @app.get("/items/{item_id}") def read_item(item_id: int): return {"item_id": item_id} Run the application using the below command. The default host url is
http://localhost:8000
uvicorn main:app --reload
- Open the default endpoint and if you see something like this, then you have successfully 🔥 deployed your application locally.
- Similar to GET methods you can define any supported request methods you need.
- Remove the two methods you have added i.e
read_root
,read_item
and it's finally time to implement the actual url shortener endpoints.
Create url shortener endpoints.
- Create the below files and directories in the root folder.
# folder :
url_shortener
# files
url_shortener/__init__.py
url_shortener/database.py
url_shortener/router.py
url_shortener/models.py
-
__init__.py
file is used for representing the current folder as a package. So that it can be referenced in other packages. -
router.py
file is responsible for handling url shortener routes. -
models.py
is a common file for defining all the necessary models for url shorteners. -
database.py
file is responsible for handling database operations. Since there are hundreds of database options available on the internet and for the purpose of simplicity, here we mimic database operations with sleep commands and store the data in memory variables.
Define models
- Below is the snippet for
models.py
. As you can see in the code, It has 2 models. -
CreateUrlShortener
model is responsible for validating creation of short URLs. -
CreateUrlShortenerResponse
model is used as a json response model.This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersfrom pydantic import BaseModel # Define the model for the URL class CreateUrlShortener(BaseModel): url: str # Config for pydantic to validate the data in the request body class Config: orm_mode = True # Define the model for the URL Response class CreateUrlShortenerResponse(BaseModel): # short_url is the short url generated by the server short_url: str url: str # Config for pydantic to validate the data in the request body class Config: orm_mode = True
Define database operations
- Below is the snippet for the
database.py
file. It has a member variableall_data
which stores all the data in memory. Here you can define your respective database instance instead of thein memory
variable. As well as it has 3 functions for creating a short url, deleting a short url and fetching all the short urls. - This snippet has well defined comments to help you understand the logic in detail.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
import time class MockDBOperations: ''' Created a class to mimic the database operations. You can use this class to implement the actual database operations. ''' # Initialize the database def __init__(self): # Here I used the dictionary to store the data. # You can use actual database connection to store the data. self.all_data = {} # Add the data to the database async def add_data_to_db(self, url : str , short_url : str) -> bool : # added a sleep to simulate the database operation time.sleep(0.2) try: # Check if the url already exists in the database if url in self.all_data: # If the url already exists, return False return False else: # If the url does not exist, add it to the database self.all_data[short_url] = url return True except: return False # Delete the data from the database async def delete_data_from_db(self, short_url : str) -> bool : # added a sleep to simulate the database operation time.sleep(0.2) try: # Check if the url already exists in the database if short_url in self.all_data: # If the url already exists, delete it from the database del self.all_data[short_url] return True else: # If the url does not exist, return False return False except: return False # Get the data from the database async def fetch_all_data(self) -> dict : # added a sleep to simulate the database operation time.sleep(0.2) # Return the data return self.all_data
Create endpoints
- Here is the snippet for the
router.py
file. - In this we have defined an api router named
url_shortener
and database instance namedmock_db_operations
. -
url_shortener
is used for mounting these endpoints to the main router. -
mock_db_operations
is used for performing db operations. - It has 3 methods defined for creating, deleting, listing endpoints and 1 method for testing url redirects in action. Use the inline comments to understand the logic in detail.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
import secrets import string from fastapi import APIRouter from url_shortener.models import CreateUrlShortener, CreateUrlShortenerResponse from url_shortener.database import MockDBOperations from starlette.responses import RedirectResponse # Create the router url_shortener = APIRouter() # Create the database mock_db_operations = MockDBOperations() # Create the short url # This function is used to generate the short url # returns the CreatedUrlShortenerResponse @url_shortener.post("/create", response_model=CreateUrlShortenerResponse) async def create(shortner : CreateUrlShortener): # Generate a random string of 7 characters short_url_length = 7 # Generate a random string of 7 characters res = ''.join(secrets.choice(string.ascii_uppercase + string.digits) for i in range(short_url_length)) # Convert the url to string short_url = str(res) # Add the url to the database status = await mock_db_operations.add_data_to_db(url=shortner.url, short_url=short_url) # If the url is added to the database, return the short url if status: return CreateUrlShortenerResponse(short_url=short_url, url=shortner.url) else: # If the url is not added to the database, return the error message return CreateUrlShortenerResponse(short_url="", url="") # Get the urls from the database @url_shortener.get("/list", response_model=list[CreateUrlShortenerResponse]) async def list(): # Get the data from the database data = await mock_db_operations.fetch_all_data() # Create a list of CreateUrlShortenerResponse arr = [] # Loop through the data for key, value in data.items(): # Add the data to the list arr.append(CreateUrlShortenerResponse(short_url=key, url=value)) # Return the list return arr # Delete the url from the database @url_shortener.delete("/delete/{short_url}") async def delete_short_url(short_url : str): # Delete the url from the database status = await mock_db_operations.delete_data_from_db(short_url = short_url) # If the url is deleted from the database, return the status if status: return {"message": "Successfully deleted"} else: # If the url is not deleted from the database, return the error message return {"message": "Failed to delete"} # Redirect the user to the original url @url_shortener.get("/test/{short_url}") async def test(short_url : str): # Get the url from the database data = await mock_db_operations.fetch_all_data() # Check if the url exists in the database if short_url in data: # redirect to this url url = data[short_url] # return the redirect response response = RedirectResponse(url=url) return response else: # return the error message return {"message": "Failed to fetch"}
Update main file
- Below is the snippet for the
main.py
file which has the url router mounted i.eurl_shortener
.This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersfrom fastapi import FastAPI from url_shortener import router # Create the app app = FastAPI() # Add the router app.include_router(router.url_shortener, prefix="/url-shortener")
Run the application locally using below command
uvicorn main:app --reload
- Use this postman collection as API specs and test the endpoints.
- Below are the few snapshots of API requests and responses in action using Postman.
Create url shortener snapshot
List all short urls
Test redirects in action
Delete short URLs
Deploy to cloud
Know how to deploy this repo to Heroku ? Click here to find out right way
Summary
Awesome 🔥, you have successfully completed this tutorial. I would 💝 to hear your feedback and comments on the great things you're gonna build with this. If you are struck somewhere feel free to comment. I am always available.
Please find the complete code at github
Top comments (0)