[{"data":1,"prerenderedAt":308},["ShallowReactive",2],{"topic-fastapi-routing":3},{"framework":4,"topic":15,"subtopics":24},{"id":5,"description":6,"extension":7,"icon":8,"meta":9,"name":10,"order":11,"slug":8,"stem":12,"tier":13,"__hash__":14},"frameworks\u002Fframeworks\u002Ffastapi.yml","FastAPI interview questions on async routing, Pydantic validation, dependency injection, OAuth2 security, database integration and deployment — the go-to Python framework for production APIs.","yml","fastapi",{},"FastAPI",6,"frameworks\u002Ffastapi",1,"lgr_X74wBdBYovbrlazGWPWqqi-YwNEUq44l1BmtgyE",{"id":16,"description":17,"extension":7,"frameworkSlug":8,"meta":18,"name":19,"order":20,"slug":21,"stem":22,"__hash__":23},"topics\u002Ftopics\u002Ffastapi-routing.yml","Path parameters, query strings, request bodies, response models and APIRouter — how FastAPI maps URLs to handlers and shapes their inputs and outputs.",{},"Routing & Parameters",2,"routing","topics\u002Ffastapi-routing","Q6gEKizgj7nZmKSg-lTB55By4eTBV1robiiWRTfhCt4",[25,105,173,240],{"id":26,"title":27,"body":28,"description":32,"difficulty":34,"extension":35,"framework":10,"frameworkSlug":8,"meta":36,"navigation":37,"order":13,"path":38,"questions":39,"questionsCount":97,"related":98,"seo":99,"seoDescription":100,"stem":101,"subtopic":102,"topic":19,"topicSlug":21,"updated":103,"__hash__":104},"qa\u002Ffastapi\u002Frouting\u002Fpath-query-params.md","Path Query Params",{"type":29,"value":30,"toc":31},"minimark",[],{"title":32,"searchDepth":20,"depth":20,"links":33},"",[],"easy","md",{},true,"\u002Ffastapi\u002Frouting\u002Fpath-query-params",[40,44,48,52,56,61,65,69,73,77,81,85,89,93],{"id":41,"difficulty":34,"q":42,"a":43},"path-param-basics","How do you define and read a path parameter in FastAPI?","Declare the parameter name inside curly braces in the path string and add a\nmatching argument to the handler function. FastAPI parses and type-coerces it\nautomatically.\n\n```python\n@app.get(\"\u002Fitems\u002F{item_id}\")\nasync def get_item(item_id: int):\n    return {\"item_id\": item_id}\n# GET \u002Fitems\u002F42 → {\"item_id\": 42} (integer, not string)\n```\n\nIf the value can't be coerced to the declared type, FastAPI returns 422.\n\nRule of thumb: always type-annotate path parameters so FastAPI validates the URL\nsegment before the handler runs.\n",{"id":45,"difficulty":34,"q":46,"a":47},"query-param-basics","How do you define a required query parameter in FastAPI?","Declare a function parameter with a type annotation and **no default value**.\nFastAPI treats any simple type not in the path template as a query parameter.\n\n```python\n@app.get(\"\u002Fitems\")\nasync def search_items(q: str):\n    return {\"q\": q}\n# GET \u002Fitems?q=laptop → {\"q\": \"laptop\"}\n# GET \u002Fitems        → 422 (required param missing)\n```\n\nRule of thumb: no default → required query param; add `= None` or `= value`\nto make it optional.\n",{"id":49,"difficulty":34,"q":50,"a":51},"query-param-default","How do you give a query parameter a default value?","Assign a default in the function signature. FastAPI uses it when the client\nomits the parameter.\n\n```python\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    page: int = 1,\n    size: int = 20,\n    active: bool = True,\n):\n    return {\"page\": page, \"size\": size, \"active\": active}\n# GET \u002Fitems → {\"page\": 1, \"size\": 20, \"active\": true}\n# GET \u002Fitems?page=3 → {\"page\": 3, \"size\": 20, \"active\": true}\n```\n\nRule of thumb: choose defaults that represent the most common use case so\nclients don't need to pass boilerplate on every request.\n",{"id":53,"difficulty":34,"q":54,"a":55},"bool-query-param","How does FastAPI parse boolean query parameters?","FastAPI accepts a flexible range of truthy\u002Ffalsy string values and converts them\nto Python `bool`:\n\n- Truthy: `\"1\"`, `\"true\"`, `\"on\"`, `\"yes\"` (case-insensitive)\n- Falsy: `\"0\"`, `\"false\"`, `\"off\"`, `\"no\"` (case-insensitive)\n\n```python\n@app.get(\"\u002Fitems\")\nasync def list_items(active: bool = True):\n    ...\n# GET \u002Fitems?active=false → active = False\n# GET \u002Fitems?active=0    → active = False\n# GET \u002Fitems?active=yes  → active = True\n```\n\nAny other value triggers a 422 validation error.\n\nRule of thumb: use `bool` for feature flags\u002Ffilters in query params — FastAPI's\nflexible string-to-bool coercion covers all common client conventions.\n",{"id":57,"difficulty":58,"q":59,"a":60},"path-query-same-name","medium","What happens if a path parameter and a query parameter have the same name?","FastAPI gives priority to the **path parameter**. A parameter name that appears\nin the route template `\u002F{name}` is always a path parameter; you cannot also read\nit from the query string with the same name in the same handler.\n\n```python\n@app.get(\"\u002Fitems\u002F{id}\")\nasync def get_item(id: int, q: str | None = None):\n    # id → from path, q → from query\n    ...\n```\n\nIf you genuinely need both a path and query parameter with the same name, use an\nalias: `Query(alias=\"id\")` — though this is a design smell.\n\nRule of thumb: keep path and query parameter names distinct; collision means\nyou should rethink the route design.\n",{"id":62,"difficulty":58,"q":63,"a":64},"path-helper","What is `Path()` and when should you use it instead of a bare type annotation?","`Path()` is FastAPI's parameter helper for adding **metadata and validation\nconstraints** to path parameters while keeping the type annotation clean.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Path\n\n@app.get(\"\u002Fitems\u002F{item_id}\")\nasync def get_item(\n    item_id: Annotated[int, Path(title=\"Item ID\", ge=1, le=999_999)],\n):\n    return {\"id\": item_id}\n```\n\nConstraints (`ge`, `le`, `gt`, `lt`) are enforced at validation time and\nreflected in the OpenAPI schema. You can also pass `description`, `example`, and\n`deprecated`.\n\nRule of thumb: use `Path()` whenever a path parameter has a meaningful range,\ndescription, or example worth documenting in the schema.\n",{"id":66,"difficulty":58,"q":67,"a":68},"query-helper","What extra control does `Query()` give you over a plain query parameter annotation?","`Query()` adds string constraints (`min_length`, `max_length`, `pattern`),\nnumeric bounds, metadata (`title`, `description`, `example`), and the ability\nto collect multi-value parameters.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fsearch\")\nasync def search(\n    q: Annotated[str, Query(\n        min_length=3,\n        max_length=100,\n        description=\"Search term\",\n        example=\"fastapi\",\n    )],\n    sort: Annotated[str, Query(pattern=r\"^(asc|desc)$\")] = \"asc\",\n):\n    ...\n```\n\nRule of thumb: add `Query()` the moment you need any constraint or documentation\non a query parameter — it's zero runtime cost and improves the schema immediately.\n",{"id":70,"difficulty":58,"q":71,"a":72},"multi-value-query","How do you accept a list of values for the same query parameter key?","Annotate with `list[T]` and wrap with `Query()`:\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fitems\")\nasync def filter_items(\n    tags: Annotated[list[str], Query()] = [],\n):\n    return {\"tags\": tags}\n# GET \u002Fitems?tags=python&tags=web → {\"tags\": [\"python\", \"web\"]}\n```\n\nWithout `Query()`, `list[str]` would be interpreted as a JSON body parameter.\nAn empty list default (`= []`) means the param is optional; use `= Query(min_length=1)`\non the list if at least one tag is required.\n\nRule of thumb: `list[T]` + `Query()` = multi-value param; the client repeats\nthe key multiple times in the query string.\n",{"id":74,"difficulty":58,"q":75,"a":76},"alias-parameter","How do you use a different query string key than the Python parameter name?","Pass `alias=` to `Query()` or `Path()`. FastAPI reads the value from the alias\nkey in the request but binds it to the Python name in the handler.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    item_query: Annotated[str | None, Query(alias=\"item-query\")] = None,\n):\n    return {\"query\": item_query}\n# GET \u002Fitems?item-query=foo → item_query = \"foo\"\n```\n\nThis is useful when the URL convention requires hyphens (which are invalid\nPython identifiers) or when you're preserving backward-compatible parameter names.\n\nRule of thumb: use `alias` to bridge the gap between URL naming conventions\n(hyphens) and Python naming conventions (underscores).\n",{"id":78,"difficulty":34,"q":79,"a":80},"deprecated-param","How do you mark a query parameter as deprecated in the OpenAPI schema?","Pass `deprecated=True` to `Query()` or `Path()`. The parameter still works\nat runtime — it's marked deprecated in the generated schema only.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    q: Annotated[str | None, Query()] = None,\n    search: Annotated[str | None, Query(deprecated=True)] = None,  # old alias\n):\n    effective_q = q or search\n    ...\n```\n\nSwagger UI renders deprecated parameters with a strikethrough and a warning badge.\n\nRule of thumb: mark old parameter names deprecated rather than removing them\nimmediately — gives clients a migration window while keeping the schema honest.\n",{"id":82,"difficulty":58,"q":83,"a":84},"numeric-constraints","What numeric constraints can you apply to path and query parameters in FastAPI?","| Constraint | Meaning |\n|------------|---------|\n| `ge=n` | greater than or equal (`>=`) |\n| `gt=n` | strictly greater than (`>`) |\n| `le=n` | less than or equal (`\u003C=`) |\n| `lt=n` | strictly less than (`\u003C`) |\n| `multiple_of=n` | value must be a multiple of n |\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query, Path\n\n@app.get(\"\u002Fitems\u002F{item_id}\")\nasync def get_item(\n    item_id: Annotated[int, Path(ge=1)],           # ID must be positive\n    page: Annotated[int, Query(ge=1, le=100)] = 1, # page 1-100\n    price_min: Annotated[float, Query(gt=0)] = 0.0,\n):\n    ...\n```\n\nConstraints are reflected in the OpenAPI JSON Schema properties so client\nvalidators can enforce them before the request is sent.\n\nRule of thumb: apply `ge=1` to all ID path parameters — negative or zero IDs\nare almost always bugs.\n",{"id":86,"difficulty":58,"q":87,"a":88},"string-constraints","What string constraints are available for query parameters?","| Constraint | Meaning |\n|------------|---------|\n| `min_length=n` | minimum string length |\n| `max_length=n` | maximum string length |\n| `pattern=r\"...\"` | regex the value must match |\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fusers\")\nasync def search_users(\n    username: Annotated[str, Query(min_length=3, max_length=50, pattern=r\"^\\w+$\")],\n):\n    ...\n# GET \u002Fusers?username=al → 422 (min_length=3)\n# GET \u002Fusers?username=al!ce → 422 (pattern)\n```\n\nRule of thumb: always set `max_length` on free-text query params to prevent\naccidental DoS from clients sending huge query strings.\n",{"id":90,"difficulty":34,"q":91,"a":92},"exclude-from-schema","How do you hide a query parameter from the OpenAPI schema?","Pass `include_in_schema=False` to `Query()`. The parameter still works at runtime\nbut won't appear in `\u002Fopenapi.json` or the docs.\n\n```python\nfrom typing import Annotated\nfrom fastapi import Query\n\n@app.get(\"\u002Fitems\")\nasync def list_items(\n    q: str | None = None,\n    _internal_trace_id: Annotated[str | None, Query(include_in_schema=False)] = None,\n):\n    ...\n```\n\nRule of thumb: use `include_in_schema=False` for internal\u002Finfra params\n(tracing IDs, A\u002FB test flags) that aren't part of the public API contract.\n",{"id":94,"difficulty":58,"q":95,"a":96},"path-vs-query-design","When should a resource identifier be a path parameter vs a query parameter?","**Path parameters** are for identifying a specific resource:\n```python\nGET \u002Fusers\u002F{user_id}       # identifies a specific user\nGET \u002Forders\u002F{order_id}\u002Fitems  # items within a specific order\n```\n\n**Query parameters** are for filtering, sorting, searching or pagination of a collection:\n```python\nGET \u002Fusers?role=admin&page=2   # filter users by role, paginate\nGET \u002Forders?status=pending     # filter orders\n```\n\nMixing them up leads to ugly URLs like `\u002Fusers?id=42` (should be `\u002Fusers\u002F42`)\nor `\u002Fusers\u002Fadmin` for a filter (should be `\u002Fusers?role=admin`).\n\nRule of thumb: if removing the identifier would leave a meaningless URL\n(`\u002Fusers\u002F` is just a list), it belongs in the path; if it's a filter, it's a query param.\n",14,null,{"description":32},"FastAPI path and query parameter interview questions — type coercion, optional params, validation constraints, multi-value query params and Path\u002FQuery helpers.","fastapi\u002Frouting\u002Fpath-query-params","Path & Query Parameters","2026-06-20","X4UtReULzyfYdlFbaM_Q6J6VrwdK5UfMWRcLWh4kW0I",{"id":106,"title":107,"body":108,"description":32,"difficulty":58,"extension":35,"framework":10,"frameworkSlug":8,"meta":112,"navigation":37,"order":20,"path":113,"questions":114,"questionsCount":168,"related":98,"seo":169,"seoDescription":170,"stem":171,"subtopic":107,"topic":19,"topicSlug":21,"updated":103,"__hash__":172},"qa\u002Ffastapi\u002Frouting\u002Frequest-body.md","Request Body",{"type":29,"value":109,"toc":110},[],{"title":32,"searchDepth":20,"depth":20,"links":111},[],{},"\u002Ffastapi\u002Frouting\u002Frequest-body",[115,119,123,127,131,135,139,143,147,151,155,159,164],{"id":116,"difficulty":34,"q":117,"a":118},"json-body-basics","How do you declare a JSON request body in FastAPI?","Declare a function parameter typed as a Pydantic `BaseModel` subclass. FastAPI\nreads the request body as JSON, parses it, and validates it against the model.\n\n```python\nfrom pydantic import BaseModel\nfrom fastapi import FastAPI\n\nclass Item(BaseModel):\n    name: str\n    price: float\n    in_stock: bool = True\n\napp = FastAPI()\n\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item):\n    return item\n# POST \u002Fitems  body: {\"name\": \"Widget\", \"price\": 9.99}\n# → Item(name='Widget', price=9.99, in_stock=True)\n```\n\nFastAPI returns 422 if the body is missing, malformed JSON, or fails validation.\n\nRule of thumb: one Pydantic model = one request body; the model name appears in\nthe OpenAPI schema as the expected input shape.\n",{"id":120,"difficulty":34,"q":121,"a":122},"optional-body","How do you make the request body optional in FastAPI?","Annotate the body parameter with `T | None = None`:\n\n```python\nclass Item(BaseModel):\n    name: str\n    price: float\n\n@app.patch(\"\u002Fitems\u002F{id}\")\nasync def update_item(id: int, item: Item | None = None):\n    if item is None:\n        return {\"updated\": False}\n    return {\"updated\": True, \"item\": item}\n```\n\nA `None` default makes the body optional; FastAPI won't error if the client\nsends an empty body. For partial updates prefer a model where all fields are\n`Optional` rather than making the whole body optional.\n\nRule of thumb: for PATCH endpoints, make individual fields optional inside the\nmodel rather than making the entire body optional.\n",{"id":124,"difficulty":58,"q":125,"a":126},"nested-models","How does FastAPI handle nested Pydantic models in the request body?","Nest a `BaseModel` inside another `BaseModel`. FastAPI validates the full\nnested structure and generates the OpenAPI schema with `$ref` references.\n\n```python\nclass Address(BaseModel):\n    street: str\n    city: str\n    postcode: str\n\nclass User(BaseModel):\n    name: str\n    email: str\n    address: Address       # nested model\n\n@app.post(\"\u002Fusers\")\nasync def create_user(user: User):\n    return user\n# body: {\"name\": \"Alice\", \"email\": \"a@b.com\",\n#         \"address\": {\"street\": \"1 Main St\", \"city\": \"London\", \"postcode\": \"SW1A\"}}\n```\n\nArbitrarily deep nesting is supported; Pydantic validates all levels.\n\nRule of thumb: prefer deep models over flat ones with `address_street`,\n`address_city` prefixes — nested models are cleaner and reusable across endpoints.\n",{"id":128,"difficulty":58,"q":129,"a":130},"multiple-body-params","How do you declare multiple Pydantic models as separate body parameters?","When you declare two Pydantic model parameters, FastAPI expects the body to be a\n**JSON object keyed by parameter name**:\n\n```python\nclass Item(BaseModel):\n    name: str\n    price: float\n\nclass Supplier(BaseModel):\n    name: str\n    contact: str\n\n@app.post(\"\u002Fitems\")\nasync def create_item(item: Item, supplier: Supplier):\n    return {\"item\": item, \"supplier\": supplier}\n# body: {\"item\": {\"name\": \"Widget\", \"price\": 9.99},\n#         \"supplier\": {\"name\": \"Acme\", \"contact\": \"info@acme.com\"}}\n```\n\nRule of thumb: multiple body models wrap themselves under their parameter names\nautomatically; use this to bundle related but distinct payloads in one request.\n",{"id":132,"difficulty":58,"q":133,"a":134},"body-singular","How do you mix a Pydantic body model with a singular body value?","Use `Body(embed=True)` on the scalar value. This forces FastAPI to wrap it under\nits parameter name in the expected JSON object.\n\n```python\nfrom fastapi import Body\nfrom typing import Annotated\n\nclass Item(BaseModel):\n    name: str\n\n@app.put(\"\u002Fitems\u002F{id}\")\nasync def update_item(\n    id: int,\n    item: Item,\n    importance: Annotated[int, Body(ge=1, le=5, embed=True)],\n):\n    return {\"item\": item, \"importance\": importance}\n# body: {\"item\": {\"name\": \"Widget\"}, \"importance\": 3}\n```\n\nWithout `embed=True`, FastAPI would expect `importance` at the top level of the\nJSON, which conflicts with `item`'s keys.\n\nRule of thumb: any singular `Body()` value alongside a Pydantic model needs\n`embed=True` so FastAPI knows to namespace it.\n",{"id":136,"difficulty":58,"q":137,"a":138},"body-field-extra","What happens when the client sends extra fields not defined in the Pydantic model?","By default (Pydantic v2), extra fields are **ignored** — they're stripped silently\nand don't reach the handler.\n\n```python\nclass Item(BaseModel):\n    name: str\n    price: float\n# body: {\"name\": \"Widget\", \"price\": 9.99, \"secret\": \"ignored\"}\n# → Item(name='Widget', price=9.99)  — secret dropped\n```\n\nYou can change this behaviour via `model_config`:\n\n```python\nfrom pydantic import BaseModel, ConfigDict\n\nclass StrictItem(BaseModel):\n    model_config = ConfigDict(extra=\"forbid\")  # 422 if extra fields sent\n    name: str\n    price: float\n```\n\nOptions: `\"ignore\"` (default), `\"allow\"` (keep in `model.model_extra`),\n`\"forbid\"` (raise validation error).\n\nRule of thumb: use `extra=\"forbid\"` for request bodies to catch client typos\nearly; use `extra=\"ignore\"` for internal models where schema drift is acceptable.\n",{"id":140,"difficulty":58,"q":141,"a":142},"list-body","How do you accept a JSON array (list) as the root of the request body?","Type-annotate the body parameter as `list[MyModel]`:\n\n```python\n@app.post(\"\u002Fitems\u002Fbulk\")\nasync def bulk_create(items: list[Item]):\n    return {\"count\": len(items)}\n# body: [{\"name\": \"A\", \"price\": 1.0}, {\"name\": \"B\", \"price\": 2.0}]\n```\n\nFastAPI validates each element against `Item`. For a top-level list, there is\nno \"outer key\" — the raw JSON array is the entire body.\n\nRule of thumb: use bulk endpoints for batch operations; add a reasonable size\nlimit (`len(items) \u003C= 100`) inside the handler to prevent abuse.\n",{"id":144,"difficulty":58,"q":145,"a":146},"form-data-body","How do you accept HTML form data instead of JSON in FastAPI?","Use `Form()` annotation and install `python-multipart`:\n\n```python\nfrom fastapi import Form\nfrom typing import Annotated\n\n@app.post(\"\u002Flogin\")\nasync def login(\n    username: Annotated[str, Form()],\n    password: Annotated[str, Form()],\n):\n    return {\"user\": username}\n# Content-Type: application\u002Fx-www-form-urlencoded\n# body: username=alice&password=secret\n```\n\nYou **cannot mix** `Form()` and a Pydantic JSON body in the same handler — HTTP\nonly allows one `Content-Type` per request.\n\nRule of thumb: use `Form()` for OAuth2 password flows and HTML `\u003Cform>` submissions;\nuse JSON body everywhere else.\n",{"id":148,"difficulty":58,"q":149,"a":150},"file-upload-body","How do you accept a file alongside form fields in FastAPI?","Use `UploadFile` for the file and `Form()` for accompanying fields. Both are\nmultipart\u002Fform-data.\n\n```python\nfrom fastapi import UploadFile, Form\nfrom typing import Annotated\n\n@app.post(\"\u002Fupload\")\nasync def upload(\n    file: UploadFile,\n    description: Annotated[str, Form()],\n):\n    content = await file.read()\n    return {\n        \"filename\": file.filename,\n        \"size\": len(content),\n        \"description\": description,\n    }\n```\n\n`UploadFile` wraps a `SpooledTemporaryFile`; use `await file.read()` for small\nfiles, iterate in chunks for large ones.\n\nRule of thumb: always `await file.seek(0)` before re-reading a file that was\nalready partially read elsewhere in the handler.\n",{"id":152,"difficulty":34,"q":153,"a":154},"body-validation-error-format","What does a FastAPI body validation error response look like?","HTTP **422 Unprocessable Entity** with a JSON body listing each failing field:\n\n```json\n{\n  \"detail\": [\n    {\n      \"type\": \"missing\",\n      \"loc\": [\"body\", \"price\"],\n      \"msg\": \"Field required\",\n      \"input\": {\"name\": \"Widget\"},\n      \"url\": \"https:\u002F\u002Ferrors.pydantic.dev\u002F2.0\u002Fv\u002Fmissing\"\n    },\n    {\n      \"type\": \"float_parsing\",\n      \"loc\": [\"body\", \"price\"],\n      \"msg\": \"Input should be a valid number\",\n      \"input\": \"not-a-number\"\n    }\n  ]\n}\n```\n\n`loc` is a breadcrumb path: `[\"body\", \"field_name\"]` for top-level fields,\n`[\"body\", \"nested\", \"field\"]` for nested models.\n\nRule of thumb: parse `detail[*].loc` in client code to map validation errors\nback to specific form fields for UX display.\n",{"id":156,"difficulty":34,"q":157,"a":158},"content-type-requirement","What Content-Type header does FastAPI expect for JSON body requests?","FastAPI expects `Content-Type: application\u002Fjson` for Pydantic body parameters.\nIf the client sends a different or missing `Content-Type`, FastAPI attempts to\nparse the body as JSON anyway — but tools like Swagger UI always send the header.\n\n```http\nPOST \u002Fitems HTTP\u002F1.1\nContent-Type: application\u002Fjson\n\n{\"name\": \"Widget\", \"price\": 9.99}\n```\n\nFor form data: `application\u002Fx-www-form-urlencoded` or `multipart\u002Fform-data`.\nFor raw file streams: `application\u002Foctet-stream` with `Request` body directly.\n\nRule of thumb: always set `Content-Type: application\u002Fjson` explicitly in REST\nclients — don't rely on default behaviour.\n",{"id":160,"difficulty":161,"q":162,"a":163},"raw-request-body","hard","How do you read the raw request body bytes in FastAPI without Pydantic parsing?","Inject the `Request` object and call `await request.body()`:\n\n```python\nfrom fastapi import Request\n\n@app.post(\"\u002Fwebhook\")\nasync def webhook(request: Request):\n    raw = await request.body()       # bytes\n    payload = json.loads(raw)\n    signature = request.headers.get(\"X-Signature\")\n    verify_signature(raw, signature) # HMAC check on raw bytes\n    return {\"ok\": True}\n```\n\nYou cannot mix `await request.body()` with a Pydantic body parameter in the same\nhandler — FastAPI reads the body stream once; `body()` consumes it before Pydantic can.\n\nRule of thumb: use raw body access for webhooks that need HMAC verification\non the exact bytes; use Pydantic models for everything else.\n",{"id":165,"difficulty":161,"q":166,"a":167},"body-max-size","How do you enforce a maximum request body size in FastAPI?","FastAPI has no built-in body size limit. Enforce it in middleware:\n\n```python\nfrom fastapi import Request\nfrom fastapi.responses import JSONResponse\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\nMAX_BODY = 1 * 1024 * 1024  # 1 MB\n\nclass LimitBodyMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request: Request, call_next):\n        if request.headers.get(\"content-length\"):\n            if int(request.headers[\"content-length\"]) > MAX_BODY:\n                return JSONResponse({\"detail\": \"Body too large\"}, status_code=413)\n        return await call_next(request)\n\napp.add_middleware(LimitBodyMiddleware)\n```\n\nFor production, configure the limit at the reverse proxy (Nginx `client_max_body_size`\nor Uvicorn `--limit-concurrency`) rather than in application code.\n\nRule of thumb: enforce body size at the reverse proxy level for efficiency;\nadd middleware as a defence-in-depth layer inside the app.\n",13,{"description":32},"FastAPI request body interview questions — Pydantic models as bodies, Body(), nested models, multiple bodies, form data, file uploads and embed.","fastapi\u002Frouting\u002Frequest-body","Ju078x3mX7g8VqxLdOPN9YTmVhYdnoMSpKenH_qpqkU",{"id":174,"title":175,"body":176,"description":32,"difficulty":58,"extension":35,"framework":10,"frameworkSlug":8,"meta":180,"navigation":37,"order":181,"path":182,"questions":183,"questionsCount":168,"related":98,"seo":236,"seoDescription":237,"stem":238,"subtopic":175,"topic":19,"topicSlug":21,"updated":103,"__hash__":239},"qa\u002Ffastapi\u002Frouting\u002Fresponse-models.md","Response Models",{"type":29,"value":177,"toc":178},[],{"title":32,"searchDepth":20,"depth":20,"links":179},[],{},3,"\u002Ffastapi\u002Frouting\u002Fresponse-models",[184,188,192,196,200,204,208,212,216,220,224,228,232],{"id":185,"difficulty":34,"q":186,"a":187},"response-model-basics","What does `response_model` do in a FastAPI route decorator?","`response_model` tells FastAPI which Pydantic model to use for **validating and\nfiltering** the handler's return value before serialising it to JSON.\n\n```python\nclass UserIn(BaseModel):\n    name: str\n    password: str          # sensitive — never leak this\n\nclass UserOut(BaseModel):\n    id: int\n    name: str              # no password field\n\n@app.post(\"\u002Fusers\", response_model=UserOut)\nasync def create_user(user: UserIn) -> UserOut:\n    db_user = await db.create(user)\n    return db_user         # password field is stripped by response_model\n```\n\nFastAPI:\n1. Validates the return value against `UserOut`.\n2. Strips fields not in `UserOut` (e.g., `password`).\n3. Documents `UserOut` as the response schema in OpenAPI.\n\nRule of thumb: always set `response_model` on any endpoint that returns ORM\nobjects or models with sensitive fields.\n",{"id":189,"difficulty":58,"q":190,"a":191},"response-model-vs-return-annotation","What is the difference between `response_model=` and a return type annotation?","Both tell FastAPI the response shape, but `response_model=` **overrides** the\nreturn annotation for serialisation and filtering:\n\n```python\n# return annotation only — used for schema AND serialisation\n@app.get(\"\u002Fusers\u002F{id}\")\nasync def get_user(id: int) -> UserOut:\n    return await db.get(User, id)\n\n# response_model wins — annotation is for type checkers only\n@app.get(\"\u002Fusers\u002F{id}\", response_model=UserOut)\nasync def get_user(id: int) -> User:   # mypy sees User; FastAPI uses UserOut\n    return await db.get(User, id)\n```\n\nUse the return annotation when they're the same type (clean, Pythonic).\nUse `response_model=` when the serialised type differs from what the handler\nactually returns (ORM model → Pydantic output DTO).\n\nRule of thumb: prefer the return annotation; add `response_model=` only when\nyou need to filter or transform the handler's return value.\n",{"id":193,"difficulty":34,"q":194,"a":195},"exclude-none","How do you omit `None` fields from a FastAPI JSON response?","Set `response_model_exclude_none=True` in the route decorator:\n\n```python\nclass Report(BaseModel):\n    total: int\n    average: float | None = None\n    notes: str | None = None\n\n@app.get(\"\u002Freport\", response_model=Report, response_model_exclude_none=True)\nasync def get_report():\n    return {\"total\": 100}\n    # average and notes are None → omitted: {\"total\": 100}\n```\n\nRule of thumb: use `exclude_none=True` for sparse responses where `None`\nmeans \"not applicable\" — keeps the JSON payload small and clean.\n",{"id":197,"difficulty":58,"q":198,"a":199},"exclude-unset","What is `response_model_exclude_unset` and when is it useful?","`response_model_exclude_unset=True` strips fields that were **not explicitly\nset** in the return value — it distinguishes between \"not set\" and \"set to None\".\n\n```python\nclass Item(BaseModel):\n    name: str\n    description: str | None = None\n    price: float | None = None\n\n@app.patch(\"\u002Fitems\u002F{id}\", response_model=Item, response_model_exclude_unset=True)\nasync def patch_item(id: int, patch: Item):\n    existing = await db.get(id)\n    updated_data = patch.model_dump(exclude_unset=True)\n    existing.update(updated_data)\n    return existing\n```\n\nThis is critical for PATCH semantics: you only return (and store) fields the\nclient actually sent, not defaults.\n\nRule of thumb: use `exclude_unset=True` on PATCH endpoints — it lets clients\nsend partial updates without overwriting fields they didn't mention.\n",{"id":201,"difficulty":58,"q":202,"a":203},"response-model-exclude","How do you always exclude specific fields from the response without a separate output model?","Use `response_model_exclude={\"field1\", \"field2\"}` in the route decorator:\n\n```python\nclass User(BaseModel):\n    id: int\n    name: str\n    password_hash: str\n    internal_notes: str | None = None\n\n@app.get(\n    \"\u002Fusers\u002F{id}\",\n    response_model=User,\n    response_model_exclude={\"password_hash\", \"internal_notes\"},\n)\nasync def get_user(id: int):\n    return await db.get(id)\n```\n\nThis avoids creating a separate `UserOut` model for simple exclusion cases.\nFor complex filtering (renames, computed fields), a dedicated output model is cleaner.\n\nRule of thumb: use `response_model_exclude` for 1-2 fields; create a dedicated\noutput model when you exclude three or more fields or need renames\u002Faliases.\n",{"id":205,"difficulty":58,"q":206,"a":207},"json-response","When should you return `JSONResponse` directly instead of a dict or Pydantic model?","Return `JSONResponse` when you need to:\n- Set a **non-default status code** dynamically.\n- Set **custom response headers**.\n- Bypass `response_model` filtering (e.g., a pre-serialised payload).\n\n```python\nfrom fastapi.responses import JSONResponse\n\n@app.get(\"\u002Fitems\u002F{id}\")\nasync def get_item(id: int):\n    item = await db.get(id)\n    if not item:\n        return JSONResponse(\n            status_code=404,\n            content={\"detail\": \"Not found\"},\n            headers={\"X-Request-ID\": \"abc123\"},\n        )\n    return item   # normal path uses response_model\n```\n\n`JSONResponse` bypasses Pydantic serialisation — pass JSON-safe Python primitives\nor use `jsonable_encoder()` first.\n\nRule of thumb: return dicts\u002Fmodels 95% of the time; reach for `JSONResponse`\nonly when you need direct control over status code or headers.\n",{"id":209,"difficulty":58,"q":210,"a":211},"response-classes","What response classes does FastAPI\u002FStarlette provide and when do you use each?","| Class | Use case |\n|-------|----------|\n| `JSONResponse` (default) | JSON API responses |\n| `HTMLResponse` | Rendered HTML pages |\n| `PlainTextResponse` | Plain text, logs |\n| `RedirectResponse` | 301\u002F302\u002F307 redirects |\n| `FileResponse` | Send a file with proper headers |\n| `StreamingResponse` | Large responses, real-time data |\n| `ORJSONResponse` | Faster JSON via `orjson` |\n| `UJSONResponse` | Faster JSON via `ujson` |\n\n```python\nfrom fastapi.responses import FileResponse, StreamingResponse\n\n@app.get(\"\u002Fdownload\")\nasync def download():\n    return FileResponse(\"report.pdf\", filename=\"report.pdf\")\n\n@app.get(\"\u002Fstream\")\nasync def stream():\n    async def generate():\n        for chunk in large_data():\n            yield chunk\n    return StreamingResponse(generate(), media_type=\"text\u002Fplain\")\n```\n\nRule of thumb: set `default_response_class=ORJSONResponse` on the `FastAPI()`\ninstance to get faster serialisation across all endpoints with no other changes.\n",{"id":213,"difficulty":58,"q":214,"a":215},"orjson-response","How do you use `ORJSONResponse` for faster JSON serialisation in FastAPI?","`orjson` is a Rust-backed JSON library that is 5-10× faster than the stdlib\n`json` module. Install it with `pip install orjson`, then:\n\n```python\nfrom fastapi import FastAPI\nfrom fastapi.responses import ORJSONResponse\n\n# Make it the default for all routes\napp = FastAPI(default_response_class=ORJSONResponse)\n\n@app.get(\"\u002Fitems\")\nasync def list_items():\n    return [{\"id\": 1}, {\"id\": 2}]\n```\n\nOr per-route:\n```python\n@app.get(\"\u002Fitems\", response_class=ORJSONResponse)\nasync def list_items():\n    ...\n```\n\n`orjson` natively handles `datetime`, `UUID`, `bytes` and NumPy arrays without\n`jsonable_encoder`.\n\nRule of thumb: switch to `ORJSONResponse` globally for any throughput-sensitive\nAPI — it's a one-line change with measurable latency improvement.\n",{"id":217,"difficulty":161,"q":218,"a":219},"streaming-response","How do you stream a large response in FastAPI without loading it all into memory?","Return a `StreamingResponse` with an async (or sync) generator:\n\n```python\nfrom fastapi.responses import StreamingResponse\nimport asyncio\n\nasync def generate_csv():\n    yield \"id,name\\n\"\n    async for row in db.stream_all_users():\n        yield f\"{row.id},{row.name}\\n\"\n\n@app.get(\"\u002Fexport\u002Fusers.csv\")\nasync def export_users():\n    return StreamingResponse(\n        generate_csv(),\n        media_type=\"text\u002Fcsv\",\n        headers={\"Content-Disposition\": \"attachment; filename=users.csv\"},\n    )\n```\n\nThe client receives bytes as they arrive; the server never holds the full dataset\nin memory.\n\nRule of thumb: use `StreamingResponse` for exports, large file downloads, and\nserver-sent events — never load a 1M-row dataset into a list before sending.\n",{"id":221,"difficulty":34,"q":222,"a":223},"redirect-response","How do you issue a redirect from a FastAPI route?","Return a `RedirectResponse`:\n\n```python\nfrom fastapi.responses import RedirectResponse\nfrom fastapi import status\n\n@app.get(\"\u002Fold-path\")\nasync def old_path():\n    return RedirectResponse(\n        url=\"\u002Fnew-path\",\n        status_code=status.HTTP_301_MOVED_PERMANENTLY,\n    )\n\n# Temporary redirect (default 307)\n@app.get(\"\u002Flogin\")\nasync def login_redirect():\n    return RedirectResponse(url=\"\u002Fauth\u002Flogin\")\n```\n\nDefault status code for `RedirectResponse` is **307 Temporary Redirect**, which\npreserves the HTTP method. Use 301 for permanent SEO-friendly redirects, 302 for\ntemporary, 303 to force GET on redirect.\n\nRule of thumb: use 307 to preserve POST method on redirect; use 303 to convert\na POST response to a GET (Post\u002FRedirect\u002FGet pattern).\n",{"id":225,"difficulty":58,"q":226,"a":227},"response-model-include","How do you include only specific fields in the response (whitelist instead of blacklist)?","Use `response_model_include={\"field1\", \"field2\"}`:\n\n```python\nclass User(BaseModel):\n    id: int\n    name: str\n    email: str\n    phone: str | None = None\n    internal_id: str\n\n@app.get(\n    \"\u002Fusers\u002F{id}\u002Fpublic\",\n    response_model=User,\n    response_model_include={\"id\", \"name\"},   # only these two fields\n)\nasync def get_user_public(id: int):\n    return await db.get(id)\n# response: {\"id\": 42, \"name\": \"Alice\"}\n```\n\nRule of thumb: prefer `response_model_exclude` for \"strip secrets\" use cases and\na dedicated output model for complex projections — `response_model_include` with\nmany field names becomes hard to maintain.\n",{"id":229,"difficulty":58,"q":230,"a":231},"custom-response-headers","How do you add custom headers to a FastAPI response?","Inject the `Response` object into the handler and set headers on it:\n\n```python\nfrom fastapi import Response\n\n@app.get(\"\u002Fitems\")\nasync def list_items(response: Response):\n    items = await db.all()\n    response.headers[\"X-Total-Count\"] = str(len(items))\n    response.headers[\"Cache-Control\"] = \"max-age=60\"\n    return items   # normal JSON response with extra headers\n```\n\nYou can also set headers via `JSONResponse(headers={...})` or middleware.\nInjecting `Response` is the cleanest option when the route already returns a\nplain dict\u002Fmodel.\n\nRule of thumb: inject `Response` for per-request headers (pagination counts,\nrate-limit info); use middleware for headers that apply to all responses (CORS, CSP).\n",{"id":233,"difficulty":58,"q":234,"a":235},"background-tasks-response","How does a route return a response immediately while also scheduling a background task?","Inject `BackgroundTasks` and add tasks before returning:\n\n```python\nfrom fastapi import BackgroundTasks\n\ndef notify_admin(item_id: int):\n    send_email(\"admin@example.com\", f\"New item {item_id} created\")\n\n@app.post(\"\u002Fitems\", status_code=201)\nasync def create_item(item: Item, background_tasks: BackgroundTasks):\n    new_item = await db.create(item)\n    background_tasks.add_task(notify_admin, new_item.id)\n    return new_item   # response is sent immediately; email sent after\n```\n\nFastAPI sends the HTTP response, then runs the background task(s) sequentially.\nThe client gets its 201 Created without waiting for the email.\n\nRule of thumb: use `BackgroundTasks` for fast, non-retryable work; use a task\nqueue (Celery, ARQ) for anything that must succeed or be retried.\n",{"description":32},"FastAPI response model interview questions — response_model, filtering fields, exclude_none, exclude_unset, JSONResponse, custom response classes and status codes.","fastapi\u002Frouting\u002Fresponse-models","czdTRQmnhNWC4pVOTDpgTUqZ_vSQlGS0ToZb22Gtox4",{"id":241,"title":242,"body":243,"description":32,"difficulty":58,"extension":35,"framework":10,"frameworkSlug":8,"meta":247,"navigation":37,"order":248,"path":249,"questions":250,"questionsCount":168,"related":98,"seo":303,"seoDescription":304,"stem":305,"subtopic":306,"topic":19,"topicSlug":21,"updated":103,"__hash__":307},"qa\u002Ffastapi\u002Frouting\u002Frouters.md","Routers",{"type":29,"value":244,"toc":245},[],{"title":32,"searchDepth":20,"depth":20,"links":246},[],{},4,"\u002Ffastapi\u002Frouting\u002Frouters",[251,255,259,263,267,271,275,279,283,287,291,295,299],{"id":252,"difficulty":34,"q":253,"a":254},"apirouter-basics","What is `APIRouter` and why use it instead of the `app` object directly?","`APIRouter` is a mini-app that collects route definitions and is then mounted\nonto the main `FastAPI` instance. It lets you split routes across files without\na circular import on the `app` object.\n\n```python\n# routers\u002Fitems.py\nfrom fastapi import APIRouter\nrouter = APIRouter()\n\n@router.get(\"\u002F\")\nasync def list_items(): ...\n\n@router.post(\"\u002F\")\nasync def create_item(): ...\n```\n\n```python\n# main.py\nfrom fastapi import FastAPI\nfrom routers import items\n\napp = FastAPI()\napp.include_router(items.router, prefix=\"\u002Fitems\", tags=[\"items\"])\n```\n\nRule of thumb: one router per resource\u002Fdomain; import and mount them all in\n`main.py` — this is the FastAPI equivalent of Flask Blueprints.\n",{"id":256,"difficulty":34,"q":257,"a":258},"prefix-tags","What do `prefix` and `tags` do in `include_router`?","`prefix` prepends a path segment to every route in the router.\n`tags` groups every route under a documentation category in Swagger UI.\n\n```python\napp.include_router(\n    users_router,\n    prefix=\"\u002Fusers\",      # \u002Flist → \u002Fusers\u002Flist\n    tags=[\"users\"],        # grouped under \"users\" in docs\n)\napp.include_router(\n    orders_router,\n    prefix=\"\u002Forders\",\n    tags=[\"orders\"],\n)\n```\n\nYou can also set `prefix` and `tags` on the `APIRouter` constructor — useful\nwhen the router \"owns\" its prefix. Setting them in `include_router` is cleaner\nfor versioned mounts.\n\nRule of thumb: set `prefix` and `tags` on `APIRouter()` if the router always\nbelongs to one resource; set them in `include_router` if you mount the same\nrouter at multiple prefixes (e.g., versioning).\n",{"id":260,"difficulty":58,"q":261,"a":262},"router-dependencies","How do you apply a dependency to every route in a router?","Pass `dependencies=[Depends(fn)]` to the `APIRouter` constructor or to\n`include_router`:\n\n```python\nfrom fastapi import APIRouter, Depends\nfrom .auth import verify_api_key\n\nrouter = APIRouter(\n    prefix=\"\u002Fadmin\",\n    tags=[\"admin\"],\n    dependencies=[Depends(verify_api_key)],\n)\n\n@router.get(\"\u002Fstats\")\nasync def stats(): ...  # verify_api_key runs automatically\n```\n\nDependencies at the router level run before route-level dependencies. Both sets\nstack — they don't replace each other.\n\nRule of thumb: put auth\u002Frate-limit dependencies on the router so new routes\ncan't accidentally bypass them.\n",{"id":264,"difficulty":58,"q":265,"a":266},"nested-routers","How do you nest one `APIRouter` inside another?","Call `parent_router.include_router(child_router, prefix=...)`:\n\n```python\n# routers\u002Forders\u002Fitems.py\nitems_router = APIRouter()\n\n@items_router.get(\"\u002F\")\nasync def list_order_items(): ...\n\n# routers\u002Forders\u002F__init__.py\norders_router = APIRouter(prefix=\"\u002Forders\")\norders_router.include_router(items_router, prefix=\"\u002F{order_id}\u002Fitems\")\n\n# main.py\napp.include_router(orders_router)\n# → GET \u002Forders\u002F{order_id}\u002Fitems\u002F\n```\n\nNesting is unlimited; prefixes concatenate.\n\nRule of thumb: nest routers to mirror resource hierarchy in the URL — it keeps\npath prefixes DRY and makes the app structure easy to navigate.\n",{"id":268,"difficulty":58,"q":269,"a":270},"router-response-model","Can you set a default `response_model` or `status_code` at the router level?","Not directly on `APIRouter`, but you can set `responses` (error response docs)\nand `dependencies` at the router level. Default `response_model` and `status_code`\nmust still be set per-route.\n\nA common pattern is to use `route_class` to inject default behaviour:\n\n```python\nfrom fastapi.routing import APIRoute\n\nclass LoggedRoute(APIRoute):\n    def get_route_handler(self):\n        original = super().get_route_handler()\n        async def custom(request):\n            log_request(request)\n            return await original(request)\n        return custom\n\nrouter = APIRouter(route_class=LoggedRoute)\n```\n\nRule of thumb: use `route_class` for cross-cutting concerns (logging, timing);\nset `response_model` and `status_code` per route for explicitness.\n",{"id":272,"difficulty":58,"q":273,"a":274},"include-router-responses","How do you add common error responses to every route in a router?","Pass `responses` to `include_router`. These are merged with per-route responses\nin the OpenAPI schema.\n\n```python\napp.include_router(\n    admin_router,\n    prefix=\"\u002Fadmin\",\n    responses={\n        401: {\"description\": \"Not authenticated\"},\n        403: {\"description\": \"Forbidden\"},\n    },\n)\n```\n\nAll routes in `admin_router` now document 401 and 403 in the schema without\nrepeating it on every decorator.\n\nRule of thumb: declare common error responses (401, 403, 429) at the router\nlevel; declare business-logic errors (404, 409) at the individual route level.\n",{"id":276,"difficulty":161,"q":277,"a":278},"mount-sub-application","What is the difference between `app.include_router()` and `app.mount()`?","`include_router` integrates routes into the **same FastAPI app** — they share\nmiddleware, exception handlers, dependency injection and the OpenAPI schema.\n\n`mount` attaches a **separate ASGI application** at a path prefix. The mounted\napp has its own middleware and schema; requests to its prefix are fully delegated.\n\n```python\n# include_router — routes join the parent app\napp.include_router(users_router, prefix=\"\u002Fusers\")\n\n# mount — separate sub-app, own \u002Fdocs\nv2_app = FastAPI()\napp.mount(\"\u002Fv2\", v2_app)\n```\n\n`mount` is appropriate for: serving static files (`StaticFiles`), mounting a\nseparate versioned API that has diverged significantly, or embedding non-FastAPI\nASGI apps.\n\nRule of thumb: use `include_router` for 95% of cases; use `mount` when you\ngenuinely need a separate ASGI application with independent middleware.\n",{"id":280,"difficulty":58,"q":281,"a":282},"large-app-structure","What is the recommended file structure for a large FastAPI application?","```\napp\u002F\n├── main.py            # FastAPI() instance, include all routers\n├── dependencies.py    # shared Depends() functions\n├── models\u002F\n│   ├── user.py        # Pydantic + ORM models\n│   └── order.py\n├── routers\u002F\n│   ├── users.py       # APIRouter for \u002Fusers\n│   └── orders.py      # APIRouter for \u002Forders\n├── services\u002F\n│   ├── user_service.py\n│   └── order_service.py\n└── db\u002F\n    ├── session.py     # engine + get_db dependency\n    └── models.py      # SQLAlchemy ORM models\n```\n\n```python\n# main.py\nfrom fastapi import FastAPI\nfrom app.routers import users, orders\n\napp = FastAPI()\napp.include_router(users.router, prefix=\"\u002Fusers\", tags=[\"users\"])\napp.include_router(orders.router, prefix=\"\u002Forders\", tags=[\"orders\"])\n```\n\nRule of thumb: keep route handlers thin — they call service functions, not\nbusiness logic directly; separate ORM models from Pydantic schemas.\n",{"id":284,"difficulty":161,"q":285,"a":286},"router-lifespan","Can `APIRouter` have its own lifespan events?","No — `APIRouter` has no built-in lifespan support. Lifespan events belong to\nthe `FastAPI` app. For modular startup\u002Fshutdown, compose context managers inside\nthe single app lifespan:\n\n```python\nfrom contextlib import asynccontextmanager\n\n@asynccontextmanager\nasync def db_lifespan(app):\n    await db.connect()\n    yield\n    await db.disconnect()\n\n@asynccontextmanager\nasync def cache_lifespan(app):\n    await cache.connect()\n    yield\n    await cache.disconnect()\n\n@asynccontextmanager\nasync def app_lifespan(app):\n    async with db_lifespan(app):\n        async with cache_lifespan(app):\n            yield\n\napp = FastAPI(lifespan=app_lifespan)\n```\n\nRule of thumb: compose multiple async context managers inside the app's lifespan\nfunction rather than trying to distribute startup across routers.\n",{"id":288,"difficulty":34,"q":289,"a":290},"router-override-response-class","How do you set a default response class for all routes in a router?","Pass `default_response_class` to the `APIRouter`:\n\n```python\nfrom fastapi import APIRouter\nfrom fastapi.responses import ORJSONResponse\n\nrouter = APIRouter(default_response_class=ORJSONResponse)\n\n@router.get(\"\u002Fitems\")\nasync def list_items():\n    return [{\"id\": 1}]   # serialised via orjson automatically\n```\n\nOr set it on the `FastAPI()` instance to affect all routes:\n```python\napp = FastAPI(default_response_class=ORJSONResponse)\n```\n\nRule of thumb: set `ORJSONResponse` globally on the app for consistent fast\nserialisation; override per-route only for special cases like `HTMLResponse`.\n",{"id":292,"difficulty":58,"q":293,"a":294},"testing-router-independently","How do you test an `APIRouter` independently without instantiating the full `FastAPI` app?","Wrap the router in a minimal `FastAPI` app in the test file:\n\n```python\nfrom fastapi import FastAPI\nfrom fastapi.testclient import TestClient\nfrom app.routers.users import router\n\ntest_app = FastAPI()\ntest_app.include_router(router, prefix=\"\u002Fusers\")\n\nclient = TestClient(test_app)\n\ndef test_list_users():\n    resp = client.get(\"\u002Fusers\u002F\")\n    assert resp.status_code == 200\n```\n\nOverride dependencies on `test_app` with `test_app.dependency_overrides` to\ninject fakes without touching the real app object.\n\nRule of thumb: always test routers through a minimal `FastAPI` wrapper — it\nexercises the full request\u002Fresponse pipeline including middleware and DI.\n",{"id":296,"difficulty":58,"q":297,"a":298},"conditional-router","How do you conditionally include a router (e.g., only in development)?","Use a simple `if` guard before `include_router`:\n\n```python\nimport os\nfrom fastapi import FastAPI\nfrom app.routers import debug_tools\n\napp = FastAPI()\n\nif os.getenv(\"ENV\") == \"development\":\n    app.include_router(debug_tools.router, prefix=\"\u002Fdebug\")\n```\n\nThis is evaluated at import time, so the routes are never registered in\nproduction even if someone guesses the URL.\n\nRule of thumb: gate debug\u002Fadmin routers behind an env check at startup rather\nthan `include_in_schema=False` — environment-gated routes truly don't exist in\nproduction, not just hidden from docs.\n",{"id":300,"difficulty":34,"q":301,"a":302},"api-prefix-global","How do you add a global `\u002Fapi` prefix to all FastAPI routes?","Two approaches:\n\n**Option A** — prefix every `include_router` call:\n```python\napp.include_router(users_router, prefix=\"\u002Fapi\u002Fv1\u002Fusers\")\n```\n\n**Option B** — mount the FastAPI app on a prefix using Starlette's root_path:\n```python\napp = FastAPI(root_path=\"\u002Fapi\u002Fv1\")\n```\n`root_path` adjusts the OpenAPI `servers` base URL but doesn't actually prefix routes\nin the ASGI routing — use a reverse proxy rewrite for that.\n\nThe cleanest approach is to create a single top-level router:\n```python\napi_router = APIRouter(prefix=\"\u002Fapi\u002Fv1\")\napi_router.include_router(users_router, prefix=\"\u002Fusers\")\napp.include_router(api_router)\n```\n\nRule of thumb: collect all routers into one `api_router` with the version prefix,\nthen mount it once — adding a new version means creating a second top-level router.\n",{"description":32},"FastAPI APIRouter interview questions — include_router, prefix, tags, dependencies, nested routers, versioning and large app structure.","fastapi\u002Frouting\u002Frouters","Routers & Structure","Ti4CtE-G9LsSIpv_43X1Im6g2JPBHAPMeRwQBI0s5bo",1782244096196]