Picture this: You walk into a restaurant kitchen where the head chef is making salads, the pastry chef is grilling steaks, and someone’s washing dishes in the pasta station. Chaos, right? That’s exactly what your FastAPI code looks like when you dump all your routes into one massive main.py file!
Just like a well-organised kitchen has different stations for different tasks, your FastAPI app needs proper organisation. Today, we’re going to transform your code from that chaotic kitchen into a Michelin-star operation where everything has its place and purpose.
Why Should You Care About This Stuff?
Before we dive in, let me tell you why this matters (and why your future self will thank you):
- 🧹 Clean Code: No more scrolling through 500 lines looking for one endpoint
- 🚀 Scalability: Add new features without breaking existing ones
- 👥 Team Friendly: Other developers can actually understand your code
- 🐛 Easier Debugging: Find and fix issues in seconds, not hours
- 📚 Better Documentation: FastAPI’s auto-docs become actually useful
The Magic of Routers: Your Code’s Best Friend
Think of routers as different departments in your company. Just like you wouldn’t handle HR issues in the accounting department, you shouldn’t mix your user authentication routes with your blog post routes.
Setting Up Your Project Structure
First, let’s create a folder structure that makes sense:
my_awesome_app/
├── main.py # The boss (keeps things simple)
├── routers/ # Different departments
│ ├── __init__.py # Makes it a proper Python package
│ └── types.py # Our testing playground
└── requirements.txt # Dependencies list
The Clean Main.py (Your New Best Friend)
Here’s how your main.py should look — clean, simple, and to the point:
from fastapi import FastAPI
from routers.types import router
def create_app():
app = FastAPI(title="My Awesome API", version="1.0.0")
# Health check - because we're responsible developers!
@app.get("/health")
def health_check():
return {"status": "I'm alive and kicking!"}
# Include our types router with a fancy /api prefix
app.include_router(router, prefix="/api")
return app
app = create_app()
Why the /api
prefix? It's like putting up a sign that says "Professional API Stuff Happens Here" – it keeps your API routes separate from any future frontend routes.
HTTP Methods: The Language of the Web
Let’s talk about HTTP methods — think of them as different ways to talk to your API, just like you might whisper, shout, or ask politely depending on the situation.
Creating Your Types Router
Create routers/types.py
and let's build something fun:
from fastapi import APIRouter
# Create our router - it's like a mini FastAPI app!
router = APIRouter()
# GET: "Hey, show me what you've got!"
@router.get("/types")
def get_types():
return {
"types": ["post", "get", "delete", "put", "patch"],
"message": "Here are all the HTTP methods I know!"
}
# POST: "I'm bringing you something new!"
@router.post("/types")
def create_type(data: dict):
return {
"message": f"Awesome! I just created: {data}",
"status": "success"
}
# DELETE: "Remove this, please!"
@router.delete("/types")
def delete_type(type_id: int):
return {
"message": f"Poof! Type with ID {type_id} has been deleted",
"deleted_id": type_id
}
# PUT: "Replace everything with this!"
@router.put("/types/{type_id}")
def update_type(type_id: int, type_name: str):
return {
"message": f"Type {type_id} has been completely updated to '{type_name}'",
"updated_type": {"id": type_id, "name": type_name}
}
# PATCH: "Just change this one little thing!"
@router.patch("/types/{type_id}")
def partial_update_type(type_id: int, type_name: str):
return {
"message": f"Made a small change to type {type_id} → '{type_name}'",
"updated_field": "name"
}
Understanding the Parameter Party
FastAPI handles three types of parameters, and knowing the difference is like knowing which fork to use at a fancy dinner:
1. Path Parameters (The VIPs)
@router.put("/types/{type_id}") # type_id is a path parameter
These live right in the URL path — they’re the VIP guests that get special treatment.
2. Query Parameters (The Question Askers)
def update_type(type_id: int, type_name: str): # type_name becomes a query parameter
These show up after the ?
in your URL like: /api/types/1?type_name=awesome
3. Request Body (The Package Delivery)
def create_type(data: dict): # This expects JSON data in the request body
This is like getting a package — all the good stuff is wrapped up inside.
Testing Your Shiny New API
Once you run your app with uvicorn main:app --reload
, you can test these endpoints:
- GET
/api/types
→ "Show me the list" - POST
/api/types
with JSON → "Create something new" - DELETE
/api/types?type_id=1
→ "Delete type 1" - PUT
/api/types/1?type_name=updated
→ "Replace type 1" - PATCH
/api/types/1?type_name=patched
→ "Update just the name"
FastAPI’s Built-in Superpowers
Here’s the cool part — FastAPI is like having a super-smart assistant who catches your mistakes:
- Wrong data type? FastAPI says “Nope, that’s not a number!”
- Missing endpoint? FastAPI responds with “404 — Never heard of it!”
- Wrong HTTP method? FastAPI replies “405 — That’s not how you talk to me!”
No extra code needed — it just works!
Real-World Testing Examples
Let’s test this puppy! Here are some example requests:
# Get all types
curl -X GET "http://localhost:8000/api/types"
# Create a new type
curl -X POST "http://localhost:8000/api/types" \
-H "Content-Type: application/json" \
-d '{"name": "awesome_type", "category": "web"}'
# Delete a type
curl -X DELETE "http://localhost:8000/api/types?type_id=123"
What’s Coming Next?
In our next adventure, we’re going to meet Pydantic models — think of them as your data’s personal bodyguard. No more accepting any random dictionary; we’re going full professional with proper data validation!
TL;DR — Quick Reference Cheat Sheet
🏗️ Project Structure:
app/
├── main.py (clean & simple)
├── routers/
│ └── types.py (organized routes)
🛠️ HTTP Methods Quick Guide:
GET
→ Retrieve dataPOST
→ Create new stuffPUT
→ Replace everythingPATCH
→ Update partiallyDELETE
→ Remove stuff
📋 Router Setup:
# In main.py
from routers.types import router
app.include_router(router, prefix="/api")
# In routers/types.py
from fastapi import APIRouter
router = APIRouter()
🎯 Parameter Types:
- Path:
/types/{id}
- Query:
?name=value
- Body: JSON data
In the next article, we shall discuss how we can validate the request using Pydantic models and have schemas for request and response data. Stay tuned and follow for upcoming articles.