This is Chapter 1 of the FastAPI textbook. We start with what FastAPI actually is, what’s underneath, and the architectural choices that shape every subsequent topic. Knowing this layer makes the rest of the textbook feel obvious instead of magical.

What FastAPI is (and isn’t)

FastAPI is a Python web framework for building HTTP APIs with three load-bearing ideas:

  1. ASGI as the protocol layer — async-native servers and middleware.
  2. Starlette as the routing / request / response layer.
  3. Pydantic as the validation / serialization layer.

FastAPI itself is a thin (but very deliberate) layer on top that wires Starlette + Pydantic together with type-hint-driven dependency injection.

It is not:

  • An ORM (it leaves persistence to SQLAlchemy / others).
  • A template engine (Jinja is available; that’s Starlette’s territory).
  • A frontend framework.

It is small on purpose. The framework you understand fits in your head; FastAPI fits in your head.

ASGI in one paragraph

ASGI (Asynchronous Server Gateway Interface) is the spiritual successor to WSGI. Instead of one synchronous callable, an ASGI app is a coroutine that takes (scope, receive, send). scope describes the connection (HTTP, WebSocket, lifespan); receive and send are awaitables for messages. This protocol makes async-native HTTP, WebSocket, and lifespan events first-class.

async def app(scope, receive, send):
    assert scope["type"] == "http"
    await send({"type": "http.response.start", "status": 200, "headers": []})
    await send({"type": "http.response.body", "body": b"hello"})

Every FastAPI handler is eventually called through this protocol. Servers (Uvicorn, Hypercorn, Daphne, Granian) implement ASGI; FastAPI is the app.

Starlette underneath

When you import FastAPI, you import a subclass of Starlette’s Starlette. The Request, Response, WebSocket, BackgroundTasks, StreamingResponse, Mount, and most middleware come straight from Starlette. FastAPI’s job is to build on top:

  • Type-hint-driven parameter parsing (path, query, body, form, header, cookie, file, etc.).
  • Pydantic-based validation and serialization.
  • Dependency injection (Depends) that composes through the request lifecycle.
  • OpenAPI generation from the same type hints.

This means you can drop down to Starlette primitives anywhere. If FastAPI doesn’t have a feature, ask whether Starlette does.

The request lifecycle

For an HTTP request:

1. ASGI server receives bytes; constructs scope; calls FastAPI.
2. FastAPI's middleware stack runs (CORS, GZip, sessions, custom).
3. Routing matches the path; finds the endpoint.
4. Path operation prepares dependencies (Depends graph resolved).
5. Body / query / header parameters validated by Pydantic.
6. The handler coroutine runs.
7. Return value serialized to JSON (or other) by Pydantic.
8. Response middleware runs in reverse.
9. ASGI server writes bytes back.

Every chapter that follows hooks somewhere into this lifecycle.

Why type hints

@app.get("/users/{id}")
async def get_user(id: int, q: str | None = None) -> User:
    ...

The function signature is the API contract:

  • id: int — path param, validated as int.
  • q: str | None = None — optional query param.
  • -> User — response will be serialized via Pydantic.

The same signature drives:

  • Runtime validation.
  • IDE / type checker support.
  • OpenAPI schema (used by /docs, SDK generators, etc.).

This single source of truth is FastAPI’s signature design choice. We’ll lean on it heavily.

Dependency injection in one example

from fastapi import Depends

async def get_db():
    async with SessionLocal() as session:
        yield session

@app.get("/users")
async def list_users(db = Depends(get_db)):
    return await db.fetch_users()

Depends(get_db) is the framework’s way of saying “before calling this endpoint, resolve get_db and pass its yielded value.” Cleanup runs after.

DI is one of FastAPI’s biggest features. We get a chapter on it (Chapter 5).

What async means here

A FastAPI handler can be async def or def:

  • async def: runs in the event loop directly.
  • def: runs in a thread pool (Starlette wraps it via anyio).

Mixing is fine. For IO-bound endpoints (DB, HTTP, file): async def. For CPU-bound short ops or sync libraries: def is acceptable; for heavy CPU: offload to a worker.

The thread pool has finite size. If your def handlers block for long, throughput collapses. We cover this in Chapter 8 (Concurrency).

Servers

Notes
UvicornMost common; uvloop + httptools
HypercornTrio support; HTTP/2; HTTP/3 (QUIC)
GranianRust-based; very fast
DaphneOriginal ASGI; less common in 2026

For most production: Uvicorn. For HTTP/2: Hypercorn. For squeezing perf: Granian.

Process model

Single Uvicorn process = single Python process = one event loop. To use multiple cores:

uvicorn app:app --workers 4
# or run via Gunicorn with Uvicorn workers
gunicorn -k uvicorn.workers.UvicornWorker -w 4 app:app

Each worker has its own event loop, its own DB pool, its own state. Don’t share Python-level state across workers.

We’ll cover sharing via Redis / DB / external state in later chapters.

Project layout

myapp/
├── pyproject.toml
├── src/myapp/
│   ├── __init__.py
│   ├── main.py            # FastAPI app
│   ├── api/               # routers
│   │   ├── __init__.py
│   │   ├── users.py
│   │   └── posts.py
│   ├── deps.py            # shared Depends
│   ├── schemas.py         # Pydantic
│   ├── models.py          # SQLAlchemy
│   ├── db.py              # engine + session
│   ├── settings.py        # pydantic-settings
│   └── lifespan.py        # startup/shutdown
└── tests/

This is the layout the textbook will assume. It’s not the only layout, but it’s the one I see work consistently.

What you get for free

  • OpenAPI 3.1 at /openapi.json.
  • Swagger UI at /docs.
  • ReDoc at /redoc.
  • JSON request / response by default.
  • Validation errors as 422 with details.
  • Background tasks (Starlette).
  • WebSockets (Starlette).
  • Server-Sent Events (StreamingResponse).
  • HTTP Basic / OAuth2 helpers.

Every chapter will use one or more of these.

What you don’t get

  • A persistence layer.
  • A migrations tool.
  • An auth identity provider.
  • Built-in caching.
  • Email / SMS / push.
  • Background workers (different from BackgroundTasks).

These are choices you make. The textbook’s companion volumes (SQLAlchemy, Pydantic, Alembic) cover the persistence side; the FastAPI textbook covers integration patterns.

What this textbook covers

ChTopic
1This chapter: introduction + architecture
2Routing, path operations, and the request anatomy
3Pydantic models and request validation
4Response models and serialization
5Dependency injection in depth
6Authentication and authorization
7Async, concurrency, and the thread pool
8WebSockets, SSE, streaming
9Background tasks and integration with task queues
10Testing
11Observability, logging, tracing, metrics
12Deployment, scaling, production

Each chapter is its own deep post. Cross-references everywhere.

Read this next

If you want my FastAPI starter project (this layout, fully wired), it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .