Installing FastAPI and Running Your First Application

Getting a FastAPI application running takes under five minutes โ€” install two packages, write five lines of Python, and start the server. The simplicity of the first hello world is intentional: FastAPI front-loads all the complexity into type annotations and Pydantic models rather than framework-specific configuration files. This lesson walks through installation, the minimal app, the recommended project structure for a real application, and the development workflow that makes building FastAPI applications efficient.

Installation

# Create a virtual environment (recommended)
python3 -m venv venv
source venv/bin/activate       # Linux/macOS
# venv\Scripts\activate        # Windows

# Install FastAPI and Uvicorn
pip install fastapi uvicorn[standard]

# uvicorn[standard] includes:
# - websockets (for WebSocket support)
# - httptools (faster HTTP parsing)
# - watchfiles (for --reload in development)
# - python-dotenv (for .env files)

# Verify
python -c "import fastapi; print(fastapi.__version__)"
python -c "import uvicorn; print(uvicorn.__version__)"
Note: uvicorn[standard] installs Uvicorn with all optional C-accelerated dependencies. The plain pip install uvicorn installs a pure-Python version that works but is slower. For production, always use the standard install. Uvicorn is the ASGI server โ€” FastAPI defines your application, Uvicorn handles the actual TCP connections, HTTP parsing, and passes requests to FastAPI’s ASGI interface.
Tip: Use uvicorn main:app --reload during development โ€” the --reload flag watches your Python files for changes and automatically restarts the server. This means you save a file and the change is reflected immediately without manually restarting. Do NOT use --reload in production โ€” it adds overhead and is designed only for development.
Warning: When running with multiple Uvicorn workers in production (--workers 4), each worker is a separate process with its own memory space. Any in-memory state (global variables, dicts) is NOT shared between workers. If you store data in a global dict, each worker has its own copy. Use a database or Redis for any state that must be shared across workers or requests.

The Minimal Application

# main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def root():
    return {"message": "Hello, World!"}

@app.get("/health")
def health_check():
    return {"status": "ok"}
# Run the application
uvicorn main:app --reload

# Output:
# INFO:     Will watch for changes in these directories: ['/path/to/project']
# INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
# INFO:     Started reloader process [12345]

# Test it
curl http://localhost:8000/
# {"message":"Hello, World!"}

# Visit docs
# http://localhost:8000/docs   โ†’ Swagger UI
# http://localhost:8000/redoc  โ†’ ReDoc
blog-api/
โ”œโ”€โ”€ app/
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ main.py           # FastAPI app instance, router includes, lifespan
โ”‚   โ”œโ”€โ”€ config.py         # pydantic-settings Settings class
โ”‚   โ”œโ”€โ”€ database.py       # SQLAlchemy engine, SessionLocal, Base
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ models/           # SQLAlchemy ORM models (one file per resource)
โ”‚   โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”‚   โ”œโ”€โ”€ user.py
โ”‚   โ”‚   โ””โ”€โ”€ post.py
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ schemas/          # Pydantic request/response models
โ”‚   โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”‚   โ”œโ”€โ”€ user.py       # UserCreate, UserUpdate, UserResponse
โ”‚   โ”‚   โ””โ”€โ”€ post.py       # PostCreate, PostUpdate, PostResponse
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ routers/          # Route handlers grouped by resource
โ”‚   โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”‚   โ”œโ”€โ”€ users.py      # /users endpoints
โ”‚   โ”‚   โ””โ”€โ”€ posts.py      # /posts endpoints
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ dependencies.py   # Shared FastAPI dependencies (get_db, get_current_user)
โ”‚
โ”œโ”€โ”€ alembic/              # Database migrations
โ”‚   โ”œโ”€โ”€ env.py
โ”‚   โ””โ”€โ”€ versions/
โ”‚
โ”œโ”€โ”€ tests/
โ”‚   โ”œโ”€โ”€ conftest.py       # pytest fixtures
โ”‚   โ””โ”€โ”€ test_posts.py
โ”‚
โ”œโ”€โ”€ alembic.ini
โ”œโ”€โ”€ requirements.txt
โ””โ”€โ”€ .env

Application Factory Pattern

# app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.routers import posts, users, auth
from app.config import settings

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: connect to DB pool, warm caches, etc.
    print("Application starting...")
    yield
    # Shutdown: close DB connections, flush queues
    print("Application shutting down...")

def create_app() -> FastAPI:
    app = FastAPI(
        title       = settings.app_name,
        version     = "1.0.0",
        lifespan    = lifespan,
        docs_url    = "/docs" if settings.environment != "production" else None,
    )

    # CORS middleware
    app.add_middleware(
        CORSMiddleware,
        allow_origins     = settings.allowed_origins,
        allow_credentials = True,
        allow_methods     = ["*"],
        allow_headers     = ["*"],
    )

    # Include routers
    app.include_router(auth.router,  prefix="/auth",  tags=["Auth"])
    app.include_router(users.router, prefix="/users", tags=["Users"])
    app.include_router(posts.router, prefix="/posts", tags=["Posts"])

    return app

app = create_app()
# Run: uvicorn app.main:app --reload

Common Mistakes

Mistake 1 โ€” Defining routes directly on app in large applications

โŒ Wrong โ€” all routes in main.py becomes unmanageable:

@app.get("/posts")
@app.post("/posts")
@app.get("/users")
# ... 50 more routes in main.py

โœ… Correct โ€” use APIRouter in separate modules, include in main:

from fastapi import APIRouter
router = APIRouter()
@router.get("/")
def list_posts(): ...
# In main.py: app.include_router(router, prefix="/posts")

Mistake 2 โ€” Using –reload in production

โŒ Wrong:

uvicorn app.main:app --reload --workers 4   # --reload incompatible with --workers!

โœ… Correct production command:

uvicorn app.main:app --workers 4 --host 0.0.0.0 --port 8000

Mistake 3 โ€” Not using a virtual environment

โŒ Wrong โ€” installing globally creates version conflicts across projects.

โœ… Correct โ€” always use python3 -m venv venv per project.

Quick Reference

Task Command / Code
Install pip install fastapi uvicorn[standard]
Run dev uvicorn main:app --reload
Run prod uvicorn app.main:app --workers 4 --host 0.0.0.0
Create app app = FastAPI(title="...", version="...")
Include router app.include_router(router, prefix="/posts")
Swagger docs http://localhost:8000/docs
OpenAPI schema http://localhost:8000/openapi.json

🧠 Test Yourself

You run uvicorn main:app --reload --workers 4 in development. What problem does this cause?