Stackvora.tech

FastAPI in 10 Minutes

FastAPI has emerged as a high-performance web framework for building APIs with Python, leveraging modern Python features like type hints to provide…

FastAPI has emerged as a high-performance web framework for building APIs with Python, leveraging modern Python features like type hints to provide automatic data validation, serialization, and interactive API documentation. This article details the essential steps to set up a FastAPI project, define endpoints, manage dependencies, and deploy a basic application, enabling developers to quickly build robust and efficient web services.

Setting Up Your Environment

Before diving into FastAPI development, ensure you have Python 3.7+ installed. We recommend creating a virtual environment to manage project dependencies effectively, preventing conflicts with other Python projects.


# Create a virtual environment
python3 -m venv fastapi-env

# Activate the virtual environment
# On macOS/Linux:
source fastapi-env/bin/activate
# On Windows:
.\fastapi-env\Scripts\activate

# Install FastAPI and an ASGI server (Uvicorn recommended)
pip install "fastapi[all]" uvicorn

The fastapi[all] installation pulls in common optional dependencies like Pydantic for data validation, Jinja2 for templating (though less common for pure APIs), and python-multipart for form data parsing. For most API-centric projects, pip install fastapi uvicorn is sufficient, adding specific dependencies as needed.

Your First FastAPI Application

The simplest FastAPI application defines a root endpoint that returns a JSON response. Save the following code as main.py:


from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def read_root():
    return {"message": "Hello, Stackvora!"}

To run this application, use Uvicorn. The --reload flag is useful for development, as it restarts the server automatically on code changes.


uvicorn main:app --reload

You can now access your API at http://127.0.0.1:8000/. FastAPI automatically provides interactive API documentation at http://127.0.0.1:8000/docs (Swagger UI) and http://127.0.0.1:8000/redoc (ReDoc).

Defining Endpoints and Path Parameters

FastAPI uses Python type hints to define the expected types of path and query parameters, which enables automatic data validation and serialization.

Path Parameters

Path parameters are defined using curly braces in the path string, similar to Flask or Django REST Framework. Type hints ensure correct data types.


from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id}

@app.get("/users/{user_name}")
async def read_user(user_name: str):
    return {"user_name": user_name}

Accessing /items/5 will return {"item_id": 5}. Attempting to access /items/abc will result in a 422 Unprocessable Entity error, demonstrating FastAPI's automatic validation.

Query Parameters

Query parameters are declared as function parameters that are not part of the path. They are optional by default, but can be made mandatory or given default values.


from fastapi import FastAPI
from typing import Optional

app = FastAPI()

@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10, q: Optional[str] = None):
    results = {"skip": skip, "limit": limit}
    if q:
        results.update({"q": q})
    return results
  • /items/: Returns {"skip": 0, "limit": 10}
  • /items/?skip=5&limit=20: Returns {"skip": 5, "limit": 20}
  • /items/?q=fastapi_query: Returns {"skip": 0, "limit": 10, "q": "fastapi_query"}

Request Body with Pydantic

For POST, PUT, and DELETE requests, data is typically sent in the request body. FastAPI uses Pydantic models to define the structure and validation rules for this data.


from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

@app.post("/items/")
async def create_item(item: Item):
    item_dict = item.dict()
    if item.tax:
        price_with_tax = item.price + item.tax
        item_dict.update({"price_with_tax": price_with_tax})
    return item_dict

@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    return {"item_id": item_id, **item.dict()}

When sending a POST request to /items/ with a JSON body like {"name": "Laptop", "price": 1200.0}, FastAPI will validate the data against the Item model, automatically handling serialization and deserialization. If the data does not conform to the model (e.g., missing name or price), FastAPI will return a 422 error with detailed validation messages.

Dependency Injection

FastAPI's dependency injection system simplifies sharing common logic, managing database connections, or handling authentication. Dependencies are callable functions (or classes) that FastAPI will execute and inject their return values into the path operation functions.


from fastapi import FastAPI, Depends, HTTPException, status

app = FastAPI()

async def get_current_user(token: str):
    # In a real app, this would validate a token (e.g., JWT)
    # and retrieve user information from a database.
    if token != "supersecrettoken":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication token",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return {"username": "admin_user", "roles": ["admin"]}

@app.get("/users/me")
async def read_users_me(current_user: dict = Depends(get_current_user)):
    return current_user

@app.get("/admin_data")
async def read_admin_data(current_user: dict = Depends(get_current_user)):
    if "admin" not in current_user.get("roles", []):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="User does not have sufficient permissions",
        )
    return {"data": "This is sensitive admin data."}

To access /users/me, you would send a request with a header like Authorization: Bearer supersecrettoken. FastAPI automatically detects the token parameter from the Authorization header (or query parameter if not specified explicitly) and passes it to get_current_user.

Deployment Considerations

For production deployments, running Uvicorn directly is generally suitable behind a reverse proxy like Nginx or Caddy. Uvicorn supports asynchronous workers and can be scaled horizontally. A common production command:


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

--host 0.0.0.0 makes the application accessible from external IPs, and --workers 4 specifies the number of Uvicorn worker processes. The optimal number of workers typically aligns with the number of CPU cores available on the server. For containerized deployments (e.g., Docker, Kubernetes), this command would be part of your container entrypoint.

Example Dockerfile


# Use an official Python runtime as a parent image
FROM python:3.9-slim-buster

# Set the working directory in the container
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY requirements.txt .
RUN pip install -r requirements.txt

# Copy the FastAPI application code
COPY . .

# Expose port 8000 (or your chosen port)
EXPOSE 8000

# Run the Uvicorn server when the container launches
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Your requirements.txt should contain fastapi and uvicorn.

Common Pitfalls

  • Missing Type Hints: FastAPI relies heavily on Python type hints for validation, serialization, and documentation. Forgetting them for path/query/body parameters will result in FastAPI treating them as plain Python objects without automatic validation or OpenAPI schema generation.
  • Synchronous I/O in Async Endpoints: While FastAPI supports synchronous path operations, using blocking I/O (e.g., requests.get() or traditional database drivers without an await) directly within an async def function can block the entire event loop, severely degrading performance. Always use await with asynchronous libraries or run blocking operations in a separate thread/process.
  • Incorrect Uvicorn Command: Ensure the Uvicorn command points to your FastAPI app correctly (module_name:app_object_name, e.g., main:app for an app object in main.py).
  • Forgetting uvicorn --reload: In development, not using --reload means you have to manually restart Uvicorn after every code change, slowing down your feedback loop.
  • Security Headers in Production: While uvicorn serves fine behind a reverse proxy, ensure your proxy is configured with appropriate security headers (e.g., HSTS, Content Security Policy) and handles SSL termination. FastAPI itself doesn't directly manage these as it's typically concerned with the application layer.

Back to the knowledge base · Ask the AI assistant