[{"data":1,"prerenderedAt":100},["ShallowReactive",2],{"qa-\u002Ffastapi\u002Frouting\u002Frouters":3},{"page":4,"siblings":87,"blog":78},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":77,"related":78,"seo":79,"seoDescription":80,"stem":81,"subtopic":82,"topic":83,"topicSlug":84,"updated":85,"__hash__":86},"qa\u002Ffastapi\u002Frouting\u002Frouters.md","Routers",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","FastAPI","fastapi",{},true,4,"\u002Ffastapi\u002Frouting\u002Frouters",[23,28,32,36,40,44,48,53,57,61,65,69,73],{"id":24,"difficulty":25,"q":26,"a":27},"apirouter-basics","easy","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":29,"difficulty":25,"q":30,"a":31},"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":33,"difficulty":14,"q":34,"a":35},"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":37,"difficulty":14,"q":38,"a":39},"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":41,"difficulty":14,"q":42,"a":43},"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":45,"difficulty":14,"q":46,"a":47},"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":49,"difficulty":50,"q":51,"a":52},"mount-sub-application","hard","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":54,"difficulty":14,"q":55,"a":56},"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":58,"difficulty":50,"q":59,"a":60},"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":62,"difficulty":25,"q":63,"a":64},"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":66,"difficulty":14,"q":67,"a":68},"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":70,"difficulty":14,"q":71,"a":72},"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":74,"difficulty":25,"q":75,"a":76},"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",13,null,{"description":11},"FastAPI APIRouter interview questions — include_router, prefix, tags, dependencies, nested routers, versioning and large app structure.","fastapi\u002Frouting\u002Frouters","Routers & Structure","Routing & Parameters","routing","2026-06-20","Ti4CtE-G9LsSIpv_43X1Im6g2JPBHAPMeRwQBI0s5bo",[88,92,95,99],{"subtopic":89,"path":90,"order":91},"Path & Query Parameters","\u002Ffastapi\u002Frouting\u002Fpath-query-params",1,{"subtopic":93,"path":94,"order":12},"Request Body","\u002Ffastapi\u002Frouting\u002Frequest-body",{"subtopic":96,"path":97,"order":98},"Response Models","\u002Ffastapi\u002Frouting\u002Fresponse-models",3,{"subtopic":82,"path":21,"order":20},1782244112766]