DEV Community

qing
qing

Posted on

Build a URL Shortener with FastAPI and Redis

Build a URL Shortener with FastAPI and Redis

tags: python, fastapi, redis, tutorial


tags: python, fastapi, redis, tutorial


Imagine typing a 200-character analytics link into a tweet and watching your audience scroll past it, bored. URL shorteners are the unsung heroes of digital communication, turning chaos into clarity. But most tutorials stop at “it works,” ignoring the real challenges: speed, scalability, and reliability. That’s where FastAPI and Redis come in. FastAPI gives you blazing-fast async performance, while Redis offers the low-latency storage needed to handle millions of redirects without breaking a sweat. Today, you’ll build a production-ready URL shortener that you can actually deploy.

Why FastAPI and Redis?

Before diving into code, let’s break down why this combo is a powerhouse.

FastAPI is built on Starlette and Pydantic, making it one of the fastest Python frameworks available. It supports async/await natively, which is crucial for non-blocking database calls. Redis, on the other hand, is an in-memory data store that operates at sub-millisecond speeds. For a URL shortener, where the primary operation is a simple GET to retrieve a URL and redirect, Redis is infinitely faster than traditional SQL databases.

When you combine them, you get an API that can handle thousands of requests per second with minimal overhead. Plus, both tools have excellent documentation and Python libraries, making the setup straightforward.

Setting Up the Environment

First, create a project directory and install the necessary dependencies. You’ll need FastAPI for the API layer, Uvicorn as the ASGI server, and the redis Python client.

mkdir url-shortener
cd url-shortener
pip install fastapi uvicorn redis python-dotenv
Enter fullscreen mode Exit fullscreen mode

We’ll also use python-dotenv to manage our Redis connection string securely, avoiding hardcoding secrets in your source code. Create a .env file:

REDIS_URL=redis://localhost:6379
Enter fullscreen mode Exit fullscreen mode

If you don’t have Redis running locally, you can spin it up quickly with Docker:

docker run --name redis -p 6379:6379 redis:alpine
Enter fullscreen mode Exit fullscreen mode

Designing the Data Structure

How do we store the mapping? Redis is flexible, but for a URL shortener, we want simplicity. We’ll use a String data type. The key will be the short code (e.g., abc123), and the value will be the original long URL.

To handle expiration, Redis allows us to set a TTL (Time To Live) on keys. This means if a user wants their link to expire after 24 hours, we just set the key’s expiration time, and Redis automatically deletes it. No cleanup scripts needed.

We also need a way to generate unique short codes. While you could use a hash function, a random string is safer to prevent collisions. We’ll use Python’s secrets module for cryptographically secure random tokens.

Building the FastAPI Application

Now, let’s write the code. Create a file named main.py. This will contain our API logic, Redis connection, and endpoints.

import redis
import secrets
from fastapi import FastAPI, HTTPException
from fastapi.responses import RedirectResponse
from pydantic import BaseModel
from dotenv import load_dotenv

load_dotenv()

# Initialize Redis client
redis_client = redis.from_url("redis://localhost:6379", decode_responses=True)

app = FastAPI(title="URL Shortener")

class ShortenRequest(BaseModel):
    url: str
    ttl_days: int = None

def generate_short_code(length: int = 8) -> str:
    return secrets.token_urlsafe(length)[:length]

@app.post("/shorten")
async def shorten_url(request: ShortenRequest):
    short_code = generate_short_code()
    key = f"url:{short_code}"

    # Set the URL in Redis
    redis_client.set(key, request.url)

    # Set expiration if TTL is provided
    if request.ttl_days:
        redis_client.expire(key, request.ttl_days * 24 * 60 * 60)

    return {
        "short_code": short_code,
        "short_url": f"http://localhost:8000/{short_code}",
        "original_url": request.url
    }

@app.get("/{short_code}")
async def redirect_url(short_code: str):
    key = f"url:{short_code}"
    original_url = redis_client.get(key)

    if not original_url:
        raise HTTPException(status_code=404, detail="URL not found")

    return RedirectResponse(url=original_url, status_code=302)
Enter fullscreen mode Exit fullscreen mode

This script is minimal but powerful. The /shorten endpoint accepts a JSON payload with the url and an optional ttl_days. It generates a unique code, stores it in Redis, and returns the short URL. The /{short_code} endpoint looks up the key and redirects the user.

Notice the use of redis_client.expire. This is the key to making your service actionable for real-world scenarios where links shouldn’t live forever.

Running the Server

With the code in place, start your development server using Uvicorn:

uvicorn main:app --reload
Enter fullscreen mode Exit fullscreen mode

The --reload flag enables auto-reloading when you change the code, which is perfect for development. Once running, you can test the API using curl or any HTTP client.

Test creating a short URL:

curl -X POST http://localhost:8000/shorten \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.example.com/very-long-analytics-link", "ttl_days": 1}'
Enter fullscreen mode Exit fullscreen mode

You should get a JSON response with your new short code. Then, visit that short code in your browser or via curl to see the redirect in action:

curl http://localhost:8000/abc123
Enter fullscreen mode Exit fullscreen mode

You’ll see a 302 Found response redirecting you to the original URL.

Adding Production Considerations

While the code above works, a real-world system needs a few more touches.

Error Handling: What if Redis is down? You should wrap Redis calls in try-except blocks to catch connection errors and return a meaningful 500 error to the user.

Validation: Ensure the url field is a valid HTTP/HTTPS link. You can use pydantic validators or the validators library to enforce this.

Concurrency: In a high-load environment, generating a short code might collide if two requests happen simultaneously. While secrets.token_urlsafe is very unlikely to collide, you can add a check: if redis_client.exists(key): generate again.

Analytics: One of the most valuable features of a URL shortener is tracking clicks. You can implement this by incrementing a counter in Redis whenever a redirect happens.

@app.get("/{short_code}")
async def redirect_url(short_code: str):
    key = f"url:{short_code}"
    original_url = redis_client.get(key)

    if not original_url:
        raise HTTPException(status_code=404, detail="URL not found")

    # Track click
    redis_client.incr(f"clicks:{short_code}")

    return RedirectResponse(url=original_url, status_code=302)
Enter fullscreen mode Exit fullscreen mode

Now, every time someone clicks your link, Redis stores the count. You can add a new endpoint like /stats/{short_code} to fetch these numbers.

Why This Matters for You

Building this isn’t just about learning syntax; it’s about understanding how to architect for performance. By using Redis, you’ve eliminated the latency of a disk-based database. By using FastAPI, you’ve ensured your API can scale with async I/O.

This project is a perfect template for a microservice. You can containerize it with Docker, deploy it to a cloud provider like AWS or Google Cloud, and connect it to a managed Redis instance (like AWS ElastiCache or Upstash).

The code is small enough to fit in a single file but robust enough to handle real traffic. You can take this today and start building your own link-sharing platform, a marketing campaign tracker, or even a feature in a larger application.

Let’s Get Building

You now have a fully functional, high-performance URL shortener. The code is ready to run, the concepts are clear, and the architecture is solid. Don’t let this sit in your notes—copy the code, run it, and tweak it. Add custom aliases, implement analytics, or set up a dashboard to view your links.

The best way to master these tools is to build something real. Drop a comment on this post if you add a new feature, or share your deployment link with the community. Let’s see what you build!


If you found this helpful, consider buying me a coffee ☕ — it keeps these articles coming!

Also check out my AI tools collection: AI 次元世界 — free AI tools for developers.

Top comments (0)