Build a URL Shortener API with FastAPI

Build a REST API that shortens URLs and redirects using short codes.

Introduction

30 Second Summary

Every time you click a short link on social media or in an email, a URL shortener is doing the work behind the scenes. It maps a tiny code to the full destination and redirects your browser in milliseconds.

In this project, you will build your own URL Shortener API using FastAPI. You will create endpoints that turn long URLs into 6-character short codes and redirect anyone who visits those codes to the original destination.

What You'll Build

You will paste a long URL into your running API, get back a unique short code, and then watch your browser redirect to the original page when you visit that code.

By the end of this project, you'll have:

  • A working REST API that accepts any URL, generates a unique short code, and redirects visitors back to the original destination when they use it.
  • Proper error handling with HTTP status codes that gives users clear, helpful feedback when something goes wrong.
  • Auto-generated interactive API documentation where you can test every endpoint directly in your browser without writing any frontend code.
  • Secret Mission: Add URL management endpoints to delete URLs and view stats about your shortener.

Are there any prerequisites?

You should be comfortable with basic Python concepts like variables, functions, dictionaries, and strings. No prior experience with web frameworks or APIs is needed.

Before We Start

Before diving into code, let's lock in what you're about to build and why it matters.

Set Up FastAPI

Building an API starts with having the right tools in place. FastAPI needs a Python virtual environment to keep your project's dependencies separate from other Python projects on your machine.

In this step, you will create a project folder, install FastAPI, and confirm everything works by running your first endpoint in the browser.

In this step, get ready to:
  • Create a project folder and Python virtual environment.
  • Install FastAPI and write a hello world endpoint.
  • Run the development server and see your API respond in the browser.
Create your project folder and virtual environment

A virtual environment is an isolated space where Python stores packages for one specific project. This prevents conflicts between projects that might need different versions of the same library.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir url-shortener
cd url-shortener

Now set up your virtual environment and activate it. The commands differ slightly between macOS and Windows.

macOS

  • Create and activate your virtual environment by running these commands:
python3 -m venv venv
source venv/bin/activate

Windows

  • Create and activate your virtual environment by running these commands:
python -m venv venv
venv\Scripts\activate

You should see (venv) appear at the beginning of your terminal prompt. This confirms the virtual environment is active.

Don't see (venv) in your prompt?

Make sure you ran the activate command (the second line), not just the venv creation command. On Windows, if you see an error about execution policies, run Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned first, then try the activate command again.

help me activate my Python virtual environment

Install FastAPI and create a hello world endpoint
  • Install FastAPI and all its standard dependencies by running:
pip install "fastapi[standard]"

You will see pip downloading and installing several packages. When it finishes, the last line says Successfully installed followed by a list of package names.

What does fastapi[standard] include?

The [standard] extra bundles everything you need for development: an HTTP server (Uvicorn), auto-generated API documentation, and data validation. One install command gives you a complete API framework.

Seeing a permissions error?

Make sure your virtual environment is active (you should see (venv) in your terminal prompt). If it is not active, run the activate command from the previous substep before trying again.

help me fix a permissions error when installing FastAPI with pip

Now open your project folder in VS Code so you can create your first file.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Select the url-shortener folder on your Desktop, then click Open.
  • Create a new file by clicking File in the top menu bar, then New File.
  • Save the file immediately with Cmd+S (macOS) or Ctrl+S (Windows).
  • Name the file main.py and save it inside the url-shortener folder.
  • Copy the following code into main.py:
from fastapi import FastAPI

# Create the FastAPI application instance
app = FastAPI()


# Define a GET endpoint at the root URL "/"
@app.get("/")
async def root():
    return {"message": "Hello World"}

What does this code do?

  • from fastapi import FastAPI brings in the FastAPI framework so you can build an API.
  • app = FastAPI() creates your application instance. Every endpoint you build gets attached to this object.
  • @app.get("/") is a decorator that tells FastAPI to run the function below whenever someone visits the root URL with a GET request.
  • The function returns a Python dictionary, which FastAPI automatically converts to JSON for the browser.
  • Save main.py with Cmd+S (macOS) or Ctrl+S (Windows).

✔️ Awesome, I've got everything!

Great. Make sure you have saved main.py before moving on to running the server.

ⓧ I'd like to double check the full code

Here is the complete main.py file. Compare it to yours and make sure every line matches:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}
Run the development server

Your code is ready. Time to start the server and see your API respond.

  • Switch back to your terminal (where (venv) is still active).
  • Start the FastAPI development server by running:
fastapi dev main.py

What does this command do?

fastapi dev starts a local development server with auto-reload enabled. Any time you save changes to your code, the server restarts automatically so you can see updates without stopping and restarting manually.

This command takes over your terminal. You will not be able to type other commands in this terminal while the server is running. Press Ctrl+C to stop the server when you need your terminal back.

You will see output confirming the server started, ending with a line that mentions http://127.0.0.1:8000 and Application startup complete.

Before you open the browser, what do you think you will see at http://127.0.0.1:8000? Think about what your root() function returns.

  • Open your browser and go to http://127.0.0.1:8000.

You should see the JSON response {"message": "Hello World"} displayed in your browser. Your API is live and responding to requests.

Seeing "Unable to connect" or a blank page?

Check your terminal for error messages. If you see ModuleNotFoundError, make sure your virtual environment is active ((venv) in the prompt) and that you ran pip install "fastapi[standard]" successfully.

Also confirm the URL is exactly http://127.0.0.1:8000 with no trailing slash missing or extra path.

help me troubleshoot why my FastAPI dev server is not responding in the browser

FastAPI also auto-generates interactive documentation for every endpoint you create.

  • Open a new browser tab and go to http://127.0.0.1:8000/docs.

You should see Swagger UI with your GET / endpoint listed. This is where you will test all your endpoints throughout this project without needing to build a frontend.

What is Swagger UI?

Swagger UI is an interactive API documentation page that FastAPI generates automatically from your code. You can expand any endpoint, click Try it out, and send real requests to your API directly from the browser.

Your development environment is ready and your first endpoint is live. Next up, you will build the core feature: the endpoint that actually shortens URLs.

Build the Shorten Endpoint

Your development server is running and you have a live API endpoint. Now it is time to build the feature that makes this a URL shortener.

The core value of a URL shortener is turning a long URL into a short code. In this step, you will build a POST endpoint that accepts any URL, generates a random 6-character code, and returns the mapping. You can test it immediately in the Swagger UI without writing any frontend code.

In this step, get ready to:
  • Define Pydantic data models that describe what your API accepts and returns.
  • Create a helper function that generates random short codes.
  • Build and test a POST endpoint that shortens URLs.
Define the data models and storage

Before you can shorten URLs, the API needs to know the shape of incoming requests and outgoing responses. Pydantic models define these shapes using Python type hints.

  • Switch back to main.py in VS Code.
  • Select all the existing code and replace it with the following:
import string
import random
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

# In-memory storage
url_store: dict[str, str] = {}


class URLRequest(BaseModel):
    url: str


class URLResponse(BaseModel):
    short_code: str
    short_url: str
    original_url: str

What does this code do?

  • string and random are Python standard library modules you will use to generate short codes.
  • url_store is a plain Python dictionary that maps short codes to original URLs. It lives in memory, so data resets when you restart the server.
  • URLRequest defines what the API expects to receive: a single url string field.
  • URLResponse defines what the API sends back: the generated short_code, the full short_url, and the original_url.
  • Save main.py.
  • Check the terminal where your dev server is running. You should see it reload successfully with no errors.

Seeing an error in the terminal?

  • Make sure the class definitions use a capital B in BaseModel and that each field has a type annotation after the colon.
  • Confirm you have from pydantic import BaseModel on its own line. A missing import causes a NameError.

Why is my FastAPI server showing an import error?

Add the short code generator and update the root endpoint

You need a function that produces random codes, and an updated root endpoint message that reflects what this API actually does.

  • Add the following code below your URLResponse class in main.py:
def generate_short_code(length: int = 6) -> str:
    characters = string.ascii_letters + string.digits
    return "".join(random.choices(characters, k=length))


@app.get("/")
async def root():
    return {"message": "URL Shortener API is running"}

What does this code do?

  • generate_short_code picks 6 random characters from all lowercase letters, uppercase letters, and digits. That gives over 56 billion possible combinations.
  • The root endpoint now returns a message that describes the API. This replaces the old "Hello World" response.
  • Save main.py.
  • Open http://127.0.0.1:8000 in your browser. You should see the updated message: {"message":"URL Shortener API is running"}.

Still seeing "Hello World"?

  • Make sure you deleted the old @app.get("/") block from the file. Having two routes with the same path causes only the first one to match.
  • Confirm you saved the file. The dev server only reloads when it detects a file change on disk.

Why is my root endpoint still showing the old message?

Create the POST /shorten endpoint

This is the main feature of your API. It accepts a URL in the request body, generates a unique short code, stores the mapping, and returns the result.

  • Add the following code below the root endpoint in main.py:
@app.post("/shorten", status_code=201)
async def shorten_url(request: URLRequest) -> URLResponse:
    short_code = generate_short_code()
    while short_code in url_store:
        short_code = generate_short_code()
    url_store[short_code] = request.url
    return URLResponse(
        short_code=short_code,
        short_url=f"http://127.0.0.1:8000/{short_code}",
        original_url=request.url
    )

What does this code do?

  • @app.post("/shorten", status_code=201) tells FastAPI this endpoint responds to POST requests and returns HTTP status 201 (Created) on success.
  • request: URLRequest makes FastAPI automatically parse the JSON body and validate it against your Pydantic model.
  • The while loop regenerates a code if it already exists in the store. This guarantees uniqueness.
  • The function returns a URLResponse object, which FastAPI automatically converts to JSON in the response body.
  • Save main.py.

Before you test, think about what you expect to happen. If you send a JSON body with a url field to this endpoint, what should come back?

  • Open http://127.0.0.1:8000/docs in your browser to access the Swagger UI.
  • Click the green POST /shorten bar to expand the endpoint.
  • Click the Try it out button on the right.
  • Replace the example JSON in the request body with:
{"url": "https://www.python.org"}
  • Click Execute.

You should see a 201 response with a JSON body containing short_code, short_url, and original_url. The short_code is a random 6-character string that will be different every time.

  • Click Execute again with the same URL.

You get a different short_code this time. Each request generates a unique code, even for the same URL.

  • Now try submitting garbage text. Replace the request body with:
{"url": "not a url"}
  • Click Execute.

Notice the API happily accepts this and returns a short code for not a url. There is no validation checking whether the input is an actual URL. You will fix this in a later step.

Not seeing the POST /shorten endpoint in Swagger UI?

  • Hard-refresh the docs page with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows). The Swagger UI caches the API schema.
  • Check your terminal for errors. If the server failed to reload, you may have a syntax error in the new code.
  • Make sure your @app.post decorator is outside and above the function definition, not inside it.

Why doesn't my new endpoint show up in the FastAPI docs?

✔️ Awesome, I've got everything!

Great. Double check you have saved main.py and your server is still running without errors.

ⓧ I'd like to double check the full code

Scroll up and confirm your main.py contains the following sections in this order:

  • Imports: string, random, FastAPI, BaseModel.
  • app = FastAPI() and url_store: dict[str, str] = {}.
  • URLRequest class with a url: str field.
  • URLResponse class with short_code, short_url, and original_url fields.
  • generate_short_code function.
  • GET / root endpoint returning "URL Shortener API is running".
  • POST /shorten endpoint with status_code=201.

You now have a working URL shortener that generates unique codes. But right now those codes do not actually redirect anywhere. In the next step, you will build the redirect endpoint that makes short codes come alive.

Build the Redirect Endpoint

Your /shorten endpoint is working. You can submit a URL and get back a unique short code. But a short code that doesn't take anyone anywhere is just a random string.

In this step, you'll build the redirect endpoint that makes short codes useful. When someone visits a short code URL, your API will look up the original destination and redirect their browser there. You'll also discover what happens when that lookup fails.

In this step, get ready to:
  • Create a GET endpoint that redirects short codes to their original URLs.
  • Test the redirect with a real short code and observe it working.
  • Discover what happens when someone uses an invalid short code.
Add the redirect endpoint

When someone visits a short code URL, the API needs to look up the original URL in url_store and send the browser there. FastAPI provides a RedirectResponse class that handles this.

  • In main.py, find this import line near the top of the file:
from fastapi import FastAPI
  • Add this new import directly below it:
from fastapi.responses import RedirectResponse
  • Scroll to the very bottom of main.py (after the shorten_url function) and add this new endpoint:
@app.get("/{short_code}")
async def redirect_to_url(short_code: str):
    return RedirectResponse(url=url_store[short_code])

What does this code do?

This endpoint uses a path parameter ({short_code}) to capture whatever value appears after the slash in the URL.

It looks up that short code in the url_store dictionary to find the original URL, then returns a RedirectResponse that tells the browser to navigate to the original destination.

  • Save main.py.
  • Check the terminal where your dev server is running. You should see the server auto-reload with a fresh set of startup messages.
  • Open http://127.0.0.1:8000/docs in your browser. You should see a new GET /{short_code} endpoint listed alongside your existing endpoints.

Don't see the new endpoint in Swagger UI?

  • Make sure you saved main.py. The dev server only reloads when it detects a file change on disk.
  • Check the terminal for error messages. A typo in the import line will prevent the server from restarting.
  • Hard-refresh the Swagger UI page with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows).

Why isn't my new endpoint showing up in the Swagger UI?

✔️ Awesome, I've got everything!

Great. Double check you've saved main.py and the server has reloaded.

ⓧ I'd like to double check the full code

Your main.py should have these pieces in order:

  • The from fastapi.responses import RedirectResponse import on line 4 (between the fastapi and pydantic imports).
  • All your existing code (models, generate_short_code, root endpoint, shorten_url endpoint) unchanged.
  • The new redirect_to_url endpoint at the very bottom of the file.
Test the redirect with valid and invalid codes

Your redirect endpoint is live. Time to prove it actually sends the browser to the right place.

  • Open http://127.0.0.1:8000/docs in your browser.
  • Expand the POST /shorten endpoint and click Try it out.
  • Replace the request body with this:
{"url": "https://www.python.org"}
  • Click Execute.
  • In the response, find the short_code value (a random 6-character string like aB3xYz).
  • Open a new browser tab and visit http://127.0.0.1:8000/ followed by your short code (for example, http://127.0.0.1:8000/aB3xYz).

Your browser redirects to https://www.python.org. The redirect is working.

What just happened?

Your API received the short code from the URL, looked it up in url_store, found the original URL, and returned a RedirectResponse. Your browser followed the redirect and loaded the Python homepage.

Now, what do you think will happen if you visit a short code that does not exist? Before you try it, predict whether the API will return an error message or crash entirely.

  • Open a new browser tab and visit http://127.0.0.1:8000/doesnotexist.

Your browser shows an Internal Server Error page. Check your terminal where the dev server is running.

  • Look at the terminal output. You should see a KeyError traceback ending with something like KeyError: 'doesnotexist'.

Why did the API crash?

When Python tries to access a dictionary key that does not exist, it raises a KeyError. Your endpoint uses url_store[short_code] directly without checking whether the key exists first.

FastAPI catches unhandled exceptions and returns a generic 500 Internal Server Error. This tells the user nothing about what went wrong. A well-designed API would return a 404 with a helpful message explaining that the short code was not found.

This crash is the problem you will fix in the next step. The endpoint works perfectly for valid short codes, but gives users zero information when something goes wrong.

Your redirect endpoint works for valid short codes, but crashes with a useless error for invalid ones. Next up, you'll add proper error handling so users get clear, helpful responses when something goes wrong.

Add Error Handling and URL Validation

Your redirect endpoint works for valid short codes, but you saw firsthand what happens when someone uses an invalid one. A 500 Internal Server Error tells users absolutely nothing about what went wrong.

Real APIs communicate clearly. They return specific status codes like 404 ("not found") and 400 ("bad input") alongside helpful messages that explain the problem. In this step, you will fix the crash on invalid short codes and add validation to reject garbage URLs.

In this step, get ready to:
  • Add a 404 error response for invalid short codes.
  • Add URL validation that rejects inputs missing http:// or https://.
  • Test both improvements in the Swagger UI.
Import HTTPException and fix the redirect endpoint

FastAPI provides a built-in HTTPException class for returning proper error responses. Instead of letting Python crash with a KeyError, you will check whether a short code exists first and return a clear 404 if it does not.

  • In main.py, find the import line at the top of the file:
from fastapi import FastAPI
  • Add HTTPException to the import, so the line now looks like this:
from fastapi import FastAPI, HTTPException

Now you can use HTTPException to send proper error responses. Next, update the redirect endpoint to check whether the short code exists before trying to use it.

  • Scroll down to the redirect_to_url function at the bottom of the file. Replace the entire function body so the endpoint looks like this:
@app.get("/{short_code}")
async def redirect_to_url(short_code: str):
    # Check if the short code exists before using it
    if short_code not in url_store:
        raise HTTPException(
            status_code=404,
            detail=f"Short code '{short_code}' not found"
        )
    return RedirectResponse(url=url_store[short_code])

What does this code do?

Instead of directly accessing url_store[short_code] (which crashes if the key is missing), you check first with if short_code not in url_store.

If the short code does not exist, raise HTTPException sends back a 404 status code with a helpful detail message. The user sees clear JSON explaining what went wrong instead of a cryptic 500 error.

  • Save main.py.
  • Open your browser and visit http://127.0.0.1:8000/doesnotexist.

You should see a clean JSON response: {"detail": "Short code 'doesnotexist' not found"} with a 404 status. No more 500 crashes.

Still seeing a 500 error?

  • Make sure you saved main.py. The dev server reloads automatically on save.
  • Check that HTTPException is included in the from fastapi import line at the top of your file.
  • Confirm the if check is above the return RedirectResponse line, not below it.

Help me fix my redirect endpoint error handling.

Add URL validation to the shorten endpoint

In the previous step, you noticed the shorten endpoint happily accepted garbage text like "not a url" without complaining. A real API should reject invalid input with a 400 status code and a message explaining what the user needs to fix.

  • In main.py, find the shorten_url function. Add the following validation code at the very top of the function body, above the line short_code = generate_short_code():
    # Validate the URL format before processing
    if not request.url.startswith(("http://", "https://")):
        raise HTTPException(
            status_code=400,
            detail="Invalid URL. Must start with http:// or https://"
        )

What does this code do?

Before generating a short code, you check whether the submitted URL starts with http:// or https://. If it does not, the API immediately returns a 400 error with a clear message telling the user what format is expected.

Status 400 means "bad request". It tells the client that the problem is with their input, not with your server.

  • Save main.py.

Why not use Pydantic's built-in URL validation?

Pydantic has a dedicated HttpUrl type that validates URLs automatically. For this project, we are using a simple startswith check so you can see exactly how validation logic works under the hood. You will encounter Pydantic's built-in validators in later projects.

Test both improvements

Both error handlers are in place. Before you test, think about what you expect to see. What should happen when you submit "not a real url" to the shorten endpoint now?

  • Open http://127.0.0.1:8000/docs in your browser to return to the Swagger UI.
  • Expand the POST /shorten endpoint and click Try it out.
  • Change the request body to:
{"url": "not a real url"}
  • Click Execute.

You should see a 400 response with the message: "Invalid URL. Must start with http:// or https://". Your API now rejects bad input with a helpful explanation instead of silently accepting it.

  • Now test with a valid URL. Change the request body to:
{"url": "https://www.python.org"}
  • Click Execute.

You should see a 201 response with a short_code, short_url, and original_url. Valid URLs still work perfectly.

  • Finally, open a new browser tab and visit http://127.0.0.1:8000/doesnotexist to confirm the 404 response.

You should see {"detail":"Short code 'doesnotexist' not found"} as a clean JSON response. Both error cases are now handled gracefully.

Seeing unexpected responses?

  • If the invalid URL still returns 201, make sure the if not request.url.startswith check is above the short_code = generate_short_code() line, not below it.
  • If you see a syntax error in the terminal, check that the parentheses in startswith(("http://", "https://")) have double parentheses. The inner pair creates a tuple.

Help me debug my URL validation.

✔️ Awesome, I've got everything!

Great. Double check you have saved main.py and your dev server is still running.

ⓧ I'd like to double check the full code

Your main.py should have these pieces in order. Scroll up and confirm each one is present:

  • Imports at the top including HTTPException from fastapi.
  • The url_store dictionary, URLRequest and URLResponse models, and generate_short_code function (unchanged from before).
  • The root GET / endpoint (unchanged).
  • The POST /shorten endpoint with the startswith validation at the top of the function body.
  • The GET /{short_code} redirect endpoint with the if short_code not in url_store check before the redirect.

Your API now communicates clearly when things go wrong. Users get specific error codes and helpful messages instead of cryptic 500 crashes. This is the foundation of any production-quality API.

Secret mission

Add URL Management Endpoints

Your URL shortener can create and redirect, but what about managing the URLs you've already shortened? In this bonus challenge, you'll add endpoints to delete URLs and view stats about your shortener.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally on your machine, so there are no ongoing costs. Decide whether to keep your resources in place, pause them to come back later, or delete them entirely.

Resources you used:

  • Your project folder containing main.py and your virtual environment.
  • The FastAPI dev server process running in your terminal.

Keep everything running

No action needed. Choose this if you are still actively building or planning to continue to the next project in the series.

Your virtual environment, installed packages, and code will be right where you left them.

Pause - I'll come back to this later

Shut down running processes and deactivate your environment to free up resources. Your files stay on disk so you can pick up where you left off.

  • Stop the FastAPI dev server by pressing Ctrl+C in the terminal where it is running.
  • Deactivate your virtual environment by running:
deactivate

When you are ready to come back, navigate to your project folder in the terminal and reactivate your virtual environment.

macOS

source venv/bin/activate

Windows

venv\Scripts\activate

Delete - I don't want to use this again

Remove all project files and start fresh. This permanently deletes your code and virtual environment.

  • Stop the FastAPI dev server by pressing Ctrl+C in the terminal where it is running.
  • Deactivate your virtual environment by running:
deactivate
  • Close VS Code.
  • Navigate to your Desktop in Finder (macOS) or File Explorer (Windows).
  • Right-click your project folder and delete it.
  • Empty your Trash (macOS) or Recycle Bin (Windows) to free up disk space.

Nice Work!

Nice Work!

You just built a fully functional URL Shortener API from scratch using FastAPI. Your API accepts long URLs, generates unique short codes, redirects visitors, and returns clear error messages when something goes wrong.

Here's what you learned:

  • Built a working REST API with FastAPI that shortens URLs and redirects users to the original destination using short codes.
  • Used Pydantic models to define request and response data shapes with Python type hints, giving you automatic validation and auto-generated interactive documentation.
  • Implemented proper HTTP error handling with appropriate status codes (201, 400, 404) so your API communicates clearly with any client.
  • Secret Mission: Added DELETE and stats endpoints to manage the full lifecycle of shortened URLs.

Ready to quiz yourself?