DEV Community

Aldorax
Aldorax

Posted on

πŸš€ How to Build a Scalable FastAPI Backend with Docker and Versioned APIs β€” The Right Way (Opinionated)

FastAPI makes it easy to get started with APIs.

But when you're thinking about building real projects β€” projects that might one day be used by thousands of users β€” structure and scalability matter from day one.

In this post, I'll show you:

  • How to structure your FastAPI project cleanly
  • How to organize versioned routes (so you can evolve your API over time)
  • Why using Docker even for small apps saves you headaches later
  • Clear explanations for every design decision (not just code)

πŸ“¦ Why Structure Matters

When you first start, it’s tempting to put everything in a main.py.

And that’s fine β€” at first.

But as soon as your app grows:

  • It becomes hard to maintain
  • You can’t version your API easily
  • Adding new features becomes a mess

That’s why we’ll set up a modular, versioned, dockerized FastAPI app β€” from the start.


πŸ— Project Structure (Professional Layout)

fastapi-app/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ api/
β”‚   β”‚   β”œβ”€β”€ v1/
β”‚   β”‚   β”‚   β”œβ”€β”€ endpoints/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ users.py
β”‚   β”‚   β”‚   β”‚   └── items.py
β”‚   β”‚   β”‚   └── __init__.py
β”‚   β”‚   └── __init__.py
β”‚   β”œβ”€β”€ core/
β”‚   β”‚   β”œβ”€β”€ config.py
β”‚   β”‚   └── __init__.py
β”‚   β”œβ”€β”€ main.py
β”‚   └── __init__.py
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ requirements.txt
└── README.md
Enter fullscreen mode Exit fullscreen mode

Why this structure?

  • app/api/v1/: all your routes go here, grouped by version (easy to maintain APIs even if your app lives 5+ years)
  • endpoints/: split your functionality logically β€” users.py for user routes, items.py for item routes, and so on.
  • core/: keep your app-wide configuration (env vars, settings) separate.
  • main.py: the real entry point β€” it brings everything together cleanly.

Good structure = easier scaling, faster onboarding of new developers, and fewer bugs over time.


🐍 Building the FastAPI App

requirements.txt

fastapi
uvicorn
Enter fullscreen mode Exit fullscreen mode

We keep dependencies lightweight to start. Later, you’ll add things like SQLAlchemy, Alembic, etc.


app/core/config.py

class Settings:
    PROJECT_NAME: str = "FastAPI App"
    API_V1_STR: str = "/api/v1"

settings = Settings()
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ Why?

Instead of hardcoding paths like /api/v1/ everywhere, we centralize configuration in one place.

Later, you can add database URLs, secrets, CORS configs here too.


app/api/v1/endpoints/users.py

from fastapi import APIRouter

router = APIRouter()

@router.get("/users", tags=["users"])
def get_users():
    return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ Why use an APIRouter?

  • Routers keep related endpoints grouped together
  • Easier to version, test, and maintain in the long run
  • You can apply middlewares, auth, and permissions per-router later

app/api/v1/endpoints/items.py

from fastapi import APIRouter

router = APIRouter()

@router.get("/items", tags=["items"])
def get_items():
    return [{"id": "book", "price": 12}, {"id": "laptop", "price": 999}]
Enter fullscreen mode Exit fullscreen mode

app/api/v1/__init__.py

from fastapi import APIRouter
from app.api.v1.endpoints import users, items

router = APIRouter()
router.include_router(users.router)
router.include_router(items.router)
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ Why a central router?

Instead of cluttering main.py, we import and group all v1 routes here neatly.


app/main.py

from fastapi import FastAPI
from app.core.config import settings
from app.api.v1 import router as v1_router

app = FastAPI(title=settings.PROJECT_NAME)

app.include_router(v1_router, prefix=settings.API_V1_STR)

@app.get("/")
def read_root():
    return {"message": "Welcome to the FastAPI backend!"}
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ Key points:

  • The root path gives a basic welcome message.
  • We attach all versioned routes under /api/v1, easily expandable to /api/v2 someday without chaos.

🐳 Why Docker?

Even if you're just starting, Docker brings 3 huge benefits:

Benefit Why It Matters
Consistency Same environment everywhere (local, staging, production)
Isolation No \"works on my machine\" problems
Deployment Ready You're always 1 docker build away from deploying

πŸ“„ Dockerfile

FROM python:3.11-slim

# Set working directory
WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy app
COPY app ./app

# Run app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
Enter fullscreen mode Exit fullscreen mode

Why this Dockerfile?

  • Python 3.11 slim image = fast and minimal
  • Workdir /app = standard for small backend services
  • No-cache pip install = smaller image sizes
  • Uvicorn reload mode = fast development cycles

πŸƒβ€β™‚οΈ Running the App

Local dev:

pip install -r requirements.txt
uvicorn app.main:app --reload
Enter fullscreen mode Exit fullscreen mode

Using Docker:

docker build -t fastapi-app .
docker run -p 8000:8000 fastapi-app
Enter fullscreen mode Exit fullscreen mode

Access your app:

  • http://localhost:8000/
  • http://localhost:8000/docs (beautiful auto Swagger UI)

🧠 Why Versioning Early?

Imagine:

  • /api/v1/ is live with 10,000 users
  • You want to change your API behavior β€” without breaking old clients

With versioning:

  • You simply create /api/v2/
  • Clients who want the new features switch to v2
  • Older apps can keep using v1 safely

This is how real companies (Stripe, Twilio, etc.) handle their APIs.


🏁 Final Thoughts

βœ… Structured project = easier scaling

βœ… Versioned APIs = future-proofing your backend

βœ… Docker = production readiness from day one

FastAPI gives you the speed of development β€” but your structure is what keeps your project alive long-term.


πŸš€ What's Next?

If you enjoyed this setup, in the next post I'll show you how to:

  • Add JWT Authentication
  • Connect to a real PostgreSQL Database
  • Write tests to make your app production-grade

Stay tuned! 🎯

Neon image

Build better on Postgres with AI-Assisted Development Practices

Compare top AI coding tools like Cursor and Windsurf with Neon's database integration. Generate synthetic data and manage databases with natural language.

Read more β†’

Top comments (0)

Quickstart image

Django MongoDB Backend Quickstart! A Step-by-Step Tutorial

Get up and running with the new Django MongoDB Backend Python library! This tutorial covers creating a Django application, connecting it to MongoDB Atlas, performing CRUD operations, and configuring the Django admin for MongoDB.

Watch full video β†’

πŸ‘‹ Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay