fastapi: Mounting StaticFiles with an APIRouter doesn't work

Describe the bug

Mounting StaticFiles with an APIRouter doesn’t work.

To Reproduce

from typing import Any
from fastapi import FastAPI, Request, APIRouter
from fastapi.staticfiles import StaticFiles
from fastapi.testclient import TestClient

router = APIRouter()

@router.get("/")
async def foo(request: Request) -> Any:
    # this raises starlette.routing.NoMatchFound
    return request.url_for("static", path="/bar")

app = FastAPI()

router.mount("/static", StaticFiles(directory="."), name="static")
# uncomment to fix
# app.mount("/static", StaticFiles(directory="."), name="static")

app.include_router(router)

client = TestClient(app)
client.get("/")
  • Execute the script, raises NoMatchFound
  • Uncomment line to mount with app instead
  • Executes as expected

Expected behavior

I can use APIRouter() as if it was a FastAPI() as noted in the docs.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 18
  • Comments: 16 (3 by maintainers)

Most upvoted comments

The underlying problems seems to be that the route does not get applied to the application. The cause is a very restrictive check:

https://github.com/tiangolo/fastapi/blob/f0388915a8b1cd9f3ae2259bace234ac6249c51a/fastapi/routing.py#L713-L721

This should be expanded to work with routing.BaseRoute

If there is a specific need to include static files under a router, could you outline why that feature would be more useful then the basic mounting on the app?

I have different modules with different static things in my app and wanted to separate everything. So that changing the prefix also changes the static prefix. And so they can live in another package not in the same repo.

Could you also point out the specific docs that were confusing so they can be clarified?

It’s here https://fastapi.tiangolo.com/tutorial/bigger-applications/#path-operations-with-apirouter “All the same options are supported.”, but I now see that this only refers to path operations.

(btw the search in the docs is broken, it just says “Initializing search” forever)

I tried the same way as lazka and failed.

It confuses me a lot that APIRouter has mount() which doesn’t work actually. image

Same here. I use @lazka’s solution for now, but it would be nice to have this more intuitive. Or at least throw an error if someone tries to mount StaticFiles to a APIRouter. It took some time to get to this page.

any update on this? Running into the same issue.

I just ran into the same issue. I agree that at the very least there should be an error (although I would definitely support mounting within routers).

@lazka Good to know that’s a solution. Would this introduce any additional latency (because of the duplicated “bookkeeping”, FastAPI/starlette does for each ASGI request? Or is that not something to even worry about?

Basically, I’m in a similar situation where I have organized my app into around 7 different APIRouter instances that each have around 3 routes defined and are all imported and included in the root router. While I assumed this was one of the intended purposes of APIRouter, if not perhaps we should update the bigger-applications docs page or at least mention when an APIRouter behaves differently than the FastAPI class.

@tiangolo we probably need your opinion here on design. Would it be appropriate for include_router to also mount any sub-applications that have been mounted to thatAPIRouter (with appropriate prefix)? Or should we explicitly disable the mount function on APIRouter and make a note in the docs about it?

@lazka there is an open issue about the search (#1448). It works on some platforms and not on others (not sure that’s been narrowed down yet). I have good luck on Chrome on macOS.