FastAPI Core Essentials — A Concept-to-Code
Handbook
Format: Definition → Code Example (atomic, <20 lines) → Line-by-Line Walkthrough →
Interview Angle
Table of Contents
1. What Is FastAPI
2. Path Parameters
3. Path Parameter Validation ( Path() )
4. Query Parameters
5. Query Parameter Validation ( Query() )
6. Request Body & Pydantic Models
7. Field-Level Validation ( Field() )
8. Response Models
9. Status Codes
10. Error Handling ( HTTPException )
11. Custom Exception Handlers
12. Combining Path, Query, and Body Parameters
13. Headers and Cookies
14. Auto-Generated Documentation
15. Interview Q&A Quick Reference
1. What Is FastAPI
Definition: FastAPI is a Python web framework for building APIs, built on top of Starlette
(handling the ASGI web layer) and Pydantic (handling data validation and serialization via
Python type hints). It implements ASGI (Asynchronous Server Gateway Interface), giving it
native support for async request handling, and it auto-generates OpenAPI/JSON Schema
documentation directly from type-annotated code.
Code Example:
from fastapi import FastAPI
app = FastAPI()
@[Link]("/")
def read_root():
return {"message": "Hello, FastAPI"}
Line-by-Line Walkthrough:
1. from fastapi import FastAPI — imports the core application class.
2. app = FastAPI() — instantiates the ASGI application object; this is the object an ASGI
server (e.g., Uvicorn) runs.
3. @[Link]("/") — a decorator that registers an HTTP GET path operation for the route
"/" .
4. def read_root(): — the path operation function; its return value becomes the
response body.
5. return {"message": "Hello, FastAPI"} — FastAPI serializes the dict to JSON
automatically using jsonable_encoder .
Run with: uvicorn main:app --reload
Interview Angle: Be ready to explain WSGI vs ASGI — WSGI is synchronous and handles
one request per worker thread at a time; ASGI supports async/await and concurrent I/O-
bound requests on a single worker. FastAPI’s speed comes from Starlette (ASGI) + Pydantic
(compiled validation) + type-hint-driven design.
2. Path Parameters
Definition: A path parameter is a dynamic segment of the URL, declared using curly braces
in the route template, and captured as an argument to the path operation function.
Declaring a type hint on that argument makes FastAPI perform automatic type conversion
and validation.
Code Example:
from fastapi import FastAPI
app = FastAPI()
@[Link]("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}
Line-by-Line Walkthrough:
1. from fastapi import FastAPI — import.
2. app = FastAPI() — app instance.
3. @[Link]("/items/{item_id}") — {item_id} is a placeholder matched against the
URL segment.
4. def read_item(item_id: int): — FastAPI extracts the string from the URL and
converts it to int ; if conversion fails (e.g., /items/abc ), it returns HTTP 422
automatically.
5. return {"item_id": item_id} — returns the already-converted integer.
Interview Angle: Route ordering matters. A static route like /items/me must be declared
before a dynamic route like /items/{item_id} , or the dynamic route will match "me" as
item_id first and cause a type-conversion error.
3. Path Parameter Validation ( Path() )
Definition: Path() is a FastAPI function used to attach validation constraints (e.g., gt ,
le ) and OpenAPI metadata (title, description) to a path parameter, beyond the basic type
coercion that a plain type hint provides.
Code Example:
from fastapi import FastAPI, Path
app = FastAPI()
@[Link]("/items/{item_id}")
def read_item(item_id: int = Path(..., gt=0, le=1000)):
return {"item_id": item_id}
Line-by-Line Walkthrough:
1. from fastapi import FastAPI, Path — imports Path alongside FastAPI .
2. app = FastAPI() — app instance.
3. @[Link]("/items/{item_id}") — route template.
4. item_id: int = Path(..., gt=0, le=1000) — the ... (Ellipsis) marks the parameter
as required; gt=0 enforces “greater than 0”; le=1000 enforces “less than or equal to
1000”. Violations return HTTP 422 before the function body runs.
5. return {"item_id": item_id} — returns the validated value.
Interview Angle: Use Path() / Query() / Field() when you need constraints or
documentation metadata; a bare type hint alone only gives type coercion, not range/length
validation.
4. Query Parameters
Definition: A query parameter is a key-value pair appended to a URL after ? . Any function
parameter that is not part of the path template and has a simple type (not a Pydantic model)
is automatically interpreted by FastAPI as a query parameter.
Code Example:
from fastapi import FastAPI
app = FastAPI()
@[Link]("/items/")
def list_items(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
Line-by-Line Walkthrough:
1. from fastapi import FastAPI — import.
2. app = FastAPI() — app instance.
3. @[Link]("/items/") — route with no path parameters.
4. skip: int = 0, limit: int = 10 — both have default values, making them optional
query parameters, e.g. GET /items/?skip=5&limit=20 .
5. return {"skip": skip, "limit": limit} — echoes the parsed values.
Interview Angle: A query parameter with no default (e.g. q: str ) is required; one with a
default ( q: str = "x" ) or Optional[str] = None is optional. This is the opposite of
assuming all query params are optional by nature.
5. Query Parameter Validation ( Query() )
Definition: Query() provides validation constraints and metadata for query parameters —
string length bounds, regex patterns, numeric ranges — and explicit control over whether
the parameter is required.
Code Example:
from fastapi import FastAPI, Query
app = FastAPI()
@[Link]("/search/")
def search(q: str = Query(..., min_length=3, max_length=50)):
return {"q": q}
Line-by-Line Walkthrough:
1. from fastapi import FastAPI, Query — imports Query .
2. app = FastAPI() — app instance.
3. @[Link]("/search/") — route.
4. q: str = Query(..., min_length=3, max_length=50) — ... makes q required; the
request fails with 422 if q is shorter than 3 or longer than 50 characters.
5. return {"q": q} — returns the validated query string.
Interview Angle: In Pydantic v2 / recent FastAPI, Query(default=None) is the explicit form;
Query(None) positional-default form still works but explicit keyword form is preferred in
production code for clarity.
6. Request Body & Pydantic Models
Definition: A request body is the JSON payload sent with POST/PUT/PATCH requests.
FastAPI parses it into an instance of a [Link] subclass, which declares field
names and types and drives automatic validation, parsing, and serialization.
Code Example:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
in_stock: bool = True
@[Link]("/items/")
def create_item(item: Item):
return item
Line-by-Line Walkthrough:
1. from fastapi import FastAPI — import.
2. from pydantic import BaseModel — import the schema base class.
3. app = FastAPI() — app instance.
4. class Item(BaseModel): — declares a schema named Item .
5. name: str — required string field.
6. price: float — required float field.
7. in_stock: bool = True — optional field defaulting to True .
8. @[Link]("/items/") — registers a POST route.
9. def create_item(item: Item): — because item ’s type is a BaseModel subclass,
FastAPI reads the JSON body, validates it against Item , and instantiates it.
10. return item — FastAPI serializes the Pydantic instance back to JSON.
Interview Angle: BaseModel instances expose .model_dump() (Pydantic v2, formerly
.dict() ) for converting to a plain dict. Validation failures return HTTP 422 with a
structured detail array pinpointing the failing field and error type.
7. Field-Level Validation ( Field() )
Definition: Field() from Pydantic attaches validation constraints and metadata directly to
a model’s attributes — defaults, numeric bounds, string length — refining validation at the
schema level rather than the path-operation level.
Code Example:
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
price: float = Field(..., gt=0)
tax: float | None = Field(default=None, ge=0)
Line-by-Line Walkthrough:
1. from pydantic import BaseModel, Field — imports.
2. class Item(BaseModel): — schema declaration.
3. name: str = Field(..., min_length=1, max_length=100) — required, length between
1 and 100.
4. price: float = Field(..., gt=0) — required, must be strictly greater than 0.
5. tax: float | None = Field(default=None, ge=0) — optional; if provided, must be ≥ 0.
Interview Angle: Field() operates on model attributes (reusable across every endpoint
using that model); Query() / Path() operate on individual function parameters (specific to
one endpoint). Know which layer you’re validating at.
8. Response Models
Definition: The response_model parameter on a path operation decorator declares the
schema of the data actually sent to the client. FastAPI filters, validates, and serializes the
function’s return value through this schema regardless of what extra attributes the returned
object holds, and documents this schema in the OpenAPI spec.
Code Example:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
class UserOut(BaseModel):
username: str
@[Link]("/users/", response_model=UserOut)
def create_user(user: UserIn):
return user
Line-by-Line Walkthrough: 1-2. Imports. 3. app = FastAPI() — app instance. 4-6.
UserIn — input schema, includes password . 7-8. UserOut — output schema, excludes
password . 9. @[Link]("/users/", response_model=UserOut) — declares that responses
from this endpoint conform to UserOut . 10. def create_user(user: UserIn): — accepts
the full input, including the password. 11. return user — even though user has a
password attribute, response_model=UserOut strips it before the response is sent; the
client never sees the password field.
Interview Angle: This input/output schema separation is the standard pattern for
preventing sensitive-field leakage (passwords, internal IDs, hashed secrets) in API
responses — a common system-design and security interview question.
9. Status Codes
Definition: The status_code parameter on a path operation decorator sets the default
HTTP status code returned on success. FastAPI’s status module provides named
constants for standard HTTP status codes, avoiding magic numbers in code.
Code Example:
from fastapi import FastAPI, status
app = FastAPI()
@[Link]("/items/", status_code=status.HTTP_201_CREATED)
def create_item(name: str):
return {"name": name}
Line-by-Line Walkthrough:
1. from fastapi import FastAPI, status — imports the status constants module.
2. app = FastAPI() — app instance.
3. @[Link]("/items/", status_code=status.HTTP_201_CREATED) — overrides the
default 200 with 201 (Created), the semantically correct code for a successful resource-
creation POST.
4. def create_item(name: str): — name here is a required query parameter (no path
match, simple type, no default).
5. return {"name": name} — response body; status code is fixed at 201 regardless of the
return value.
Interview Angle: Know the core set: 200 OK, 201 Created, 204 No Content, 400 Bad
Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 422 Unprocessable Entity
(FastAPI’s default validation-error code), 500 Internal Server Error.
10. Error Handling ( HTTPException )
Definition: HTTPException is FastAPI’s mechanism for terminating a path operation with a
specific HTTP status code and an error detail payload. Raising it immediately halts function
execution and produces a structured JSON error response.
Code Example:
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"1": "Book"}
@[Link]("/items/{item_id}")
def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}
Line-by-Line Walkthrough:
1. from fastapi import FastAPI, HTTPException — imports.
2. app = FastAPI() — app instance.
3. items = {"1": "Book"} — an in-memory data store standing in for a database.
4. @[Link]("/items/{item_id}") — route.
5. def read_item(item_id: str): — function signature.
6. if item_id not in items: — lookup check.
7. raise HTTPException(status_code=404, detail="Item not found") — FastAPI catches
this exception type globally and converts it to {"detail": "Item not found"} with a
404 status.
8. return {"item": items[item_id]} — the success path, only reached if the item exists.
Interview Angle: HTTPException is for expected, per-request error conditions raised
deliberately in business logic. Contrast with Section 11’s custom exception handlers, used
for centralizing formatting of a custom exception type across the whole app.
11. Custom Exception Handlers
Definition: @app.exception_handler(ExceptionClass) registers a global handler function
that intercepts any instance of a specific exception type raised anywhere in the application,
decoupling business-logic exceptions from HTTP-response formatting.
Code Example:
from fastapi import FastAPI, Request
from [Link] import JSONResponse
class ItemNotFoundError(Exception):
pass
app = FastAPI()
@app.exception_handler(ItemNotFoundError)
def handler(request: Request, exc: ItemNotFoundError):
return JSONResponse(status_code=404, content={"error": "not found"})
Line-by-Line Walkthrough:
1. from fastapi import FastAPI, Request — imports, including Request for handler
signature typing.
2. from [Link] import JSONResponse — explicit JSON response class for
manual construction. 3-4. class ItemNotFoundError(Exception): pass — a custom,
domain-specific exception unrelated to FastAPI.
3. app = FastAPI() — app instance.
4. @app.exception_handler(ItemNotFoundError) — registers this function as the handler
for any raised ItemNotFoundError .
5. def handler(request: Request, exc: ItemNotFoundError): — receives the incoming
request and the raised exception instance.
6. return JSONResponse(status_code=404, content={"error": "not found"}) —
manually constructs the error response.
Interview Angle: This pattern lets business logic raise plain Python exceptions ( raise
ItemNotFoundError() ) without importing FastAPI at all, keeping the domain layer
framework-agnostic — a common clean-architecture talking point.
12. Combining Path, Query, and Body Parameters
Definition: FastAPI infers each parameter’s source by inspecting the function signature:
names matching the path template become path parameters; simple-typed parameters
without a Pydantic model become query parameters; parameters typed as a BaseModel
subclass are parsed from the request body. All three can coexist in a single function.
Code Example:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
@[Link]("/items/{item_id}")
def update_item(item_id: int, q: str | None = None, item: Item = None):
return {"item_id": item_id, "q": q, "item": item}
Line-by-Line Walkthrough: 1-2. Imports. 3. app = FastAPI() — app instance. 4-5. class
Item(BaseModel): name: str — request body schema. 6. @[Link]("/items/{item_id}")
— route with a path placeholder. 7. def update_item(item_id: int, q: str | None =
None, item: Item = None): — item_id matches the path template (path parameter); q is
a simple optional type not in the path (query parameter); item is typed as Item , a
BaseModel , so it is parsed from the JSON body. 8. return {"item_id": item_id, "q": q,
"item": item} — combines all three sources in one response.
Interview Angle: This signature-based source inference (no manual [Link] /
[Link] parsing) is a core differentiator from frameworks like Flask, and is a good
talking point on developer-experience and type safety.
13. Headers and Cookies
Definition: Header() and Cookie() are FastAPI parameter functions that extract values
from HTTP request headers and cookies respectively, applying the same type coercion and
validation machinery as Query() / Path() .
Code Example:
from fastapi import FastAPI, Header, Cookie
app = FastAPI()
@[Link]("/whoami")
def whoami(
user_agent: str | None = Header(default=None),
session_id: str | None = Cookie(default=None),
):
return {"user_agent": user_agent, "session_id": session_id}
Line-by-Line Walkthrough:
1. from fastapi import FastAPI, Header, Cookie — imports.
2. app = FastAPI() — app instance.
3. @[Link]("/whoami") — route. 4-7. user_agent: str | None = Header(default=None)
reads the User-Agent header (FastAPI auto-converts the Python snake_case parameter
name to the HTTP kebab-case header name); session_id: str | None =
Cookie(default=None) reads a cookie literally named session_id .
4. return {"user_agent": user_agent, "session_id": session_id} — returns both
extracted values.
Interview Angle: Know the underscore-to-hyphen header name conversion rule — it’s a
frequent source of confusion when a custom header isn’t being picked up as expected.
14. Auto-Generated Documentation
Definition: FastAPI automatically produces interactive API documentation conforming to
the OpenAPI specification, served by default at /docs (Swagger UI) and /redoc (ReDoc).
The schema is derived entirely from type hints, Pydantic models, and decorator metadata —
no separate documentation-writing step is required.
Code Example:
from fastapi import FastAPI
app = FastAPI(title="Inventory API", version="1.0.0")
Line-by-Line Walkthrough:
1. from fastapi import FastAPI — import.
2. app = FastAPI(title="Inventory API", version="1.0.0") — title and version are
metadata that appear at the top of the generated /docs page and in the raw OpenAPI
JSON at /[Link] .
Interview Angle: Be able to state that OpenAPI docs update automatically as code
changes, eliminating the classic problem of documentation drifting out of sync with
implementation.
15. Interview Q&A Quick Reference
Question Answer
WSGI is synchronous, one request per thread; ASGI supports
WSGI vs ASGI?
async/await for concurrent I/O-bound handling.
How does FastAPI decide
Path if name matches the route template; body if typed as a
if a parameter is path,
BaseModel ; otherwise query.
query, or body?
Default validation error 422 Unprocessable Entity .
status code?
Difference between Field() validates at the model/schema level (reusable);
Field() and Query() / Path() validate at the individual endpoint-parameter
Query() / Path() ? level.
Why use To filter/shape the outgoing response and prevent leaking fields
response_model ? (e.g., passwords) present on the internal object.
HTTPException vs HTTPException is raised inline for a specific request’s error; a
custom exception custom handler centralizes formatting for a custom exception
handler? type across the whole app.
Where do OpenAPI docs Auto-generated from type hints and Pydantic models — no
come from? manual doc-writing.
How is a required vs
Required: no default value. Optional: has a default value or
optional query parameter
Query(default=None) .
defined?
Each code example above is intentionally atomic (under 20 lines) and runnable on its own
with uvicorn main:app --reload once combined with the relevant imports.