FastAPI is a high-performance, async-first, type-hint safe web framework for building APIs quickly with Python.

It uses and features:

  • Pydantic for data validation, parsing, and Python 3.6+ type hints.
  • Starlette for HTTP requests, routing, middleware, and ASGI interface.
  • Swagger (OpenAPI) for auto-generated doumentation and schemas.
  • Developer ergonomics focusing on velocity and readability.

Setup and Environment

Virtual Environments

Python virtual environments isolate dependencies for individual projects, preventing conflicts.

python -m venv venv # create a virtual environment
source venv/bin/activate  # activate the virtual environment

Creating a FastAPI Application

Install FastAPI:

pip install fastapi uvicorn

Write your first FastAPI application:

from fastapi import FastAPI
app = FastAPI()
 
@app.get("/api-endpoint)
async def first_api():
	return {'message': 'Hello world!'}

Run the app with uvicorn:

uvicorn books:app --reload
  • ‘books’ is the name of the Python file, ‘app’ is the FastAPI instance.
  • --reload auto-restarts the server on file change.

HTTP Methods: GET, POST, PUT, DELETE

Define endpoints with HTTP method decorators:

  • @app.get("/books")
  • @app.post("/books")
  • @app.put("/books/{book_id}"
  • @app.delete(/books/{book_id}

Path Parameters

Path Parameters are request parameters that have been attached to the URL. You identify specific resources by utilizing a specific path.

  • Anything wrapped in {} in the path is a variable (a path parameter).
  • Specify the type of parameter (str, int, float, etc.) to get auto validation and docs.
  • It’s important to note that order matters, the /books/{book_id} endpoint would pattern match everything after /books and never call the mybook endpoint.
@app.get("books/{dynamic_param}")
async def read_all_books(dynamic_param):
	return {'dynamic_param': dynamic_param}
 
@app.get("books/{book_title}")
async def read_book(book_id: int):
	return get_book_by_id(book_id)

Query Parameters

Query parameters are values sent in the URL after a ”?”. e.g. GET /books/?skip=10&limit=5

  • Use Optional or default to None if the parameter isn’t required.
  • You can set default values using =.
  • /search/?keyword=magic → Books matching ‘magic’
  • /search/ → All books
@app.get("/search/")
async def search_books(keyword: Optional[str] = None):
	if keyword:
		return get_books_by_keyword(keyword)
	return get_all_books()

Pydantics: Data Modeling & Validation

Pydantics is a library used for data modeling and parsing.

FastAPI uses Pydantic under the hood for: request bodies (e.g., JSON data in POST or PUT), data validation, automatic error handling, generating Swagger docs.

You define a BaseModel to describe the input data. Then use that model as a param in your endpoint. The request will then be parsed into a Book object using Pydantic, automatically validating types and returning 422 Unprocessiable Entity if the data is invalid.

from pydantic import BaseModel
 
class Book(BaseModel):
	id: int
    title: str
    author: str
    
@app.post("/books/")
async def create_book(book: Book):
    return {"book": book}

Field Metadata

Use Field() to add validation, constraints, and defaults or extra metadata to model attributes in FastAPI.

from pydantic import BaseModel
 
class Book(BaseModel):
	# Value must be greater than 0.
	id: int = Field(gt=0)
	# Title and author must both be at least one character long.
    title: str = Field(min_length=1)
    author: str = Field(min_length=1)
    # Description must be between 3 and 120 characters.
    description: str = Field(min_length=3, max_length=120)
    # An optional rating, but must be between 1 and 5 inclusive if provided.
    rating: Optional[int] = Field(default=None, ge=1, le=5)

You can also add description and metadata for better OpenAPI docs:

title: str = Field(..., description="Book title", example="The Hobbit")

Custom Validation with @validator

You can use more complex validation using @validator:

@validator("title")
    def title_must_be_capitalized(cls, v):
        if not v.istitle():
            raise ValueError("Title must be capitalized")
        return v

Immutable (Frozen) Models

You can enforce immutability with frozen=True.

class Book(BaseModel):
    title: str
    class Config:
        frozen = True
 
book = Book(title="Dune")
book.title = "Dune Messiah"  # ❌ raises TypeError

Parameter Validation

Path() Validation

The below code ensures that book_id is greater than 0, and gives helpful description and example in Swagger docs.

from fastapi import Path
 
@app.get("/books/{book_id}")
async def read_book(
    book_id: int = Path(..., description="ID of book", gt=0, example=123)
):
    return get_book(book_id)

Query() Validation

The below code parses skip and limit as query parameters, validates types and ranges, enriches Swagger docs, and returns 422 error if validation fails.

from fastapi import Query
 
@app.get("/books/")
async def get_books(skip: int = Query(0, ge=0), limit: int = Query(10, le=100)):
    return {"skip": skip, "limit": limit}