Skip to content

Shortify is a URL shortener RESTful API built with Python and FastAPI ⚡

License

Notifications You must be signed in to change notification settings

IHosseini083/Shortify

Repository files navigation

Table of Contents

Introduction

Shortify is a fast, fully async and reliable URL shortener RESTful API built with Python and FastAPI framework. It uses the open source MongoDB database for storing shortened URLs data and implements user registration via OAuth2 JWT authentication.

Features

  • Dockerized and ready to be deployed.
  • Fully async and non-blocking.
  • Uses FastAPI framework for API development:
  • Uses MongoDB as data store for users and shortened URLs.
  • Extensible architecture for adding new API endpoints and services.
  • Descriptive and well-documented code.
  • OAuth2 (with hashed passwords and JWT tokens) based user authentication.
  • Uses Poetry for dependency management.
  • Automated code formatting and linting with pre-commit and black.
  • CORS (Cross Origin Resource Sharing) support.
  • Pagination support for listing shortened URLs and users.
  • Structured logging with structlog.
  • Correlation ID middleware for logging and tracing requests across services.
  • Class-based API endpoints for better code organization and reducing duplication.
  • Fully type annotated code for better IDE support and code quality.

Requirements

Manual installation:

  • Python 3.8 or higher.
  • Poetry for dependency management.
  • Up and running MongoDB instance (locally or remotely).

Using Docker:

Setup

1. Clone the repository

git clone https://github.com/IHosseini083/Shortify.git

2. Install dependencies

⚠️ Skip this step if you want to use docker for running the application.

You need to configure Poetry to place the virtual environment in the project directory. To do so, run the following command:

poetry config --local virtualenvs.in-project true

Then, install dependencies:

poetry install

If you're on Linux, you can just skip all the above steps and run the scripts/install script:

chmod +x scripts/install  # make the script executable
./scripts/install

3. Configure environment variables

You can see the table of all environment variables below. Those marked with * are required and MUST be set before running the application. Rest of them are optional and have default values.

Note: To set any of these environment variables below, you MUST prefix them with SHORTIFY_ and then set them in your shell or in a .env file placed at the root directory (e.g. you can copy .env.example to .env). For example, to set DEBUG environment variable, you can do the following:

export SHORTIFY_DEBUG=True

Also note that ALL environment variables are CASE SENSITIVE.

FastAPI Application

Name Description Default Type
PROJECT_NAME Project name. Shortify string
PROJECT_VERSION Project version. Current version of the project. string
API_V1_STR API version 1 prefix. v1 string
DEBUG Debug mode for development. True boolean
CORS_ORIGINS Allowed origins for CORS. An empty list to allow all origins. list of string
USE_CORRELATION_ID Use correlation ID middleware for logging. True boolean
UVICORN_HOST* Host address for uvicorn server. - string
UVICORN_PORT* Port number for uvicorn server. - integer

Logging

Name Description Default Type
LOG_LEVEL A logging level from the logging module. INFO string

MongoDB

Name Description Default Type
MONGODB_URI MongoDB connection URI. mongodb://db:27017/ string
MONGODB_DB_NAME MongoDB database name. shortify string

Superuser

Name Description Default Type
FIRST_SUPERUSER* Username of the first superuser. - string
FIRST_SUPERUSER_EMAIL* Email of the first superuser. - string
FIRST_SUPERUSER_PASSWORD* Password of the first superuser. - string

Authentication

Name Description Default Type
ACCESS_TOKEN_EXPIRE_MINUTES Access token expiration time in minutes. 1440 (24 hours) integer
SECRET_KEY Secret key for signing JWT tokens. 43 random characters generated by secrets module. string

Short URLs

Name Description Default Type
URL_IDENT_LENGTH Length of the shortened URL ID. 7 integer

4. Run the application

After setting all the required and optional environment variables in .env.example file, copy it to .env file so that it's usable both by Docker and Shortify app.

Run the following commands to start up the services:

Using Docker (Recommended)

This will start two containers, one for Shortify and another one for MongoDB.

docker compose up -d

Manually

An up & running MongoDB instance is required and SHORTIFY_MONGODB_URI environment variable must be handled accordingly because its default value is only compatible with Docker installation of the app.

python -m shortify

If the SHORTIFY_DEBUG environment variable is set to True, the application will be run in debug mode and will be reloaded automatically on code changes.

Now your application is available at http://{SHORTIFY_UVICORN_HOST}:{SHORTIFY_UVICORN_HOST}/.

Documentation and Usage

After running the application, you can access the OpenAPI (Swagger) documentation at /api/v1/docs endpoint.

Project Structure, Modifications and Best Practices

Structure of shortify folder containing main files and folders of the application is consistent and straightforward and just by looking at module names it gives you an idea of what's inside it!

./shortify
│    __init__.py
│    __main__.py        # Runs the development server
├─── app                # Primary app folder
│   │    main.py        # Contains FastAPI application and its settings
│   │    __init__.py    # Contains project version variable
│   ├─── api            # All API views/routes are here
│   ├─── core           # Core configs and utils for the application
│   ├─── db             # Database initialization and session (if needded)
│   ├─── middlewares    # ASGI middlewares for FastAPI application
│   ├─── models         # Database models
│   ├─── schemas        # Pydantic schemas
│   ├─── static         # Static files served at /static endpoint
│   ├─── utils          # Utilites used by the API

Creating new API routes

To create new API routes and add them to your main application, you need to create new fastapi.APIRouter instances or use the existing ones depending on the endpoint you need to implement. All API routers for API version one (v1) are located at shortify/app/api/v1/endpoints folder and then grouped together in shortify/app/api/v1/__init__.py file by including them in a separate fastapi.APIRouter instance that will be added to main app:

# shortify/app/api/v1/__init__.py
from fastapi import APIRouter

from shortify.app.api.v1.endpoints import auth, urls, users
from shortify.app.core.config import settings

router = APIRouter(prefix=f"/{settings.API_V1_STR}")
router.include_router(auth.router, prefix="/auth", tags=["Authentication"])
router.include_router(users.router, prefix="/users", tags=["Users"])
router.include_router(urls.router, prefix="/urls", tags=["URLs"])

Now let's say you want to add a new router for statistics about short URLs and then add it to your main app, this is how you're going to do it:

  1. Create new module named stats.py in shortify/app/api/v1/endpoints package.
  2. Create an fastapi.APIRouter instance in stats.py module.
# shortify/app/api/v1/endpoints/stats.py
from fastapi import APIRouter

router = APIRouter()
  1. E.g. Create an API route to get most visited short URLs in descending order
# shortify/app/api/v1/endpoints/stats.py
from fastapi import APIRouter
from typing import List
from shortify.app.models import ShortUrl

router = APIRouter()


@router.get("/most-visited")
async def get_most_visited_urls(skip: int = 0, limit: int = 10) -> List[ShortUrl]:
    # Sort URLs in descending order based on their views
    return await ShortUrl.find(skip=skip, limit=limit).sort(-ShortUrl.views).to_list()

You could also implement the route using class-based views decorator but since we don't have any dependencies or code duplications, this approach was simply enough for our use-case.

  1. Finally, add the newly created router to the main router for version one
# shortify/app/api/v1/__init__.py
from fastapi import APIRouter

from shortify.app.api.v1.endpoints import stats
from shortify.app.core.config import settings

router = APIRouter(prefix=f"/{settings.API_V1_STR}")
# Other routers omitted for brevity
router.include_router(stats.router, prefix="/stats", tags=["Statistics"])
  1. That's it! now you can see the created endpoint in your API docs.

FastAPI Best Practices

You want to extend the application with the best practices available? check out the below repositories:

Stack

Frameworks and technologies used in Shortify

License

This project is licensed under the terms of the GPL-3.0 license.

— ⚡ —