Question
Here is a simple static FastAPI app. With this setup even though the root path
is expected to return a FileResponse
of custom.html
, the app still returns
index.html
. How can I get the root path work and render custom.html
?
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
app = FastAPI()
app.mount(
"/",
StaticFiles(directory="static", html=True),
name="static",
)
@app.get("/")
async def index() -> FileResponse:
return FileResponse("custom.html", media_type="html")
Answer
As per Starlette documentation:
StaticFiles
Signature:
StaticFiles(directory=None, packages=None, check_dir=True)
html
- Run in HTML mode. Automatically loadsindex.html
for directories if such file exists.
In addtion, as shown from the code snippet you provided, you have mounted
StaticFiles
to the root directory (i.e., /
), instead of, for example,
/static
(or some other path name), as shown below:
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()
app.mount('/static', StaticFiles(directory='static'), name='static')
As per FastAPI [documentation](https://fastapi.tiangolo.com/tutorial/static- files/#what-is-mounting):
"Mounting" means adding a complete "independent" application in a specific path, that then takes care of handling all the sub-paths.
Hence, any path that starts with /
will be handled by that StaticFiles
application, and due to specifying html=True
in the arguments, index.html
will be automatically loaded; regardless of creating a separate endpoint
pointing to the root path /
and trying to return something else, as
demonstrated in the example given in your question.
Order Matters
If, for example, you moved app.mount("/",StaticFiles(...
line after
defining your @app.get("/")
endpoint, you would see that order
matters
and index.html
would not automatically be loaded anymore, as endpoints
are evaluated in order. Note that, in your case, you might get an Internal Server Error
, as your @app.get("/")
endpoint would be called and attempt to
find custom.html
, but if this file is not located under the root /
directory, but rather under /static
directory (as shown from your code), you
would then get a File does not exist
error, and hence, you should instead
return FileResponse('static/custom.html')
.
Even if you removed html=True
, but keep StaticFiles
mounted to the root
directory and defined before your /
endpoint, you would get a
{"detail":"Not Found"}
error response when attempting to access
http://localhost:8000/
. This is because the /
route would still be handled
by the StaticFiles
application (as mentioned earlier), and you should thus
need to specify the file that you would like to access (when html=True
is
not used), e.g., http://localhost:8000/index.html
. Even if you defined other
endpoints in your code (e.g., /register
, /login
, /hello
), as long as
StaticFiles
is mounted to the root directory (i.e., /
) and defined in your
code before all other endpoints, for instance:
app.mount('/', StaticFiles(directory='static'), name='static')
@app.post('/register')
async def register():
pass
@app.post('/login')
async def login():
pass
@app.get('/hello')
async def hello():
pass
every request to those routes would again be handled by the StaticFiles
application, and hence, would lead to an error response, such as
{"detail":"Not Found"}
(if you send a GET
request, such as when you type a
URL in the address bar of the browser and then hit the Enter
key, and the
given path does not match a file name in the static
web directory), or
{detail": "Method Not Allowed"}
(if you issue a POST
request through
Swagger UI or some other client platform/application). As described in
Starlette's documentation on
StaticFiles
(see StaticFiles
class
implementation
as well):
Static files will respond with
404 Not found
or405 Method not allowed
responses for requests which do not match. In HTML mode, if404.html
file exists, it will be shown as 404 response.
Hence, you should either mount the StaticFiles
instance to a
different/unique path, such as /static
(i.e., app.mount('/static', ...
, as
shown at the top of this answer), or, if you still want to mount the
StaticFiles
instance to /
path, define StaticFiles
after declaring
all your API endpoints, for example:
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
app = FastAPI()
@app.post('/register')
async def register():
pass
@app.post('/login')
async def login():
pass
@app.get('/hello')
async def hello():
pass
@app.get('/')
async def index():
return FileResponse('static/custom.html')
app.mount('/',StaticFiles(directory='static', html=True), name='static')
Note
Every time a webpage is loaded, the browser caches most content on the page,
in order to shorten laod times (the next time that user loads the page). Thus,
if you tried out the example provided earlier, i.e., where the StaticFiles
application is defined before every API endpoint, and then, using the same
browser session, you tried out the example above with the StaticFiles
application defined after all API endpoints, but the browser still
displays the content of static/index.html
file instead of
static/custom.html
—when accessing http://localhost:8000/
in your
browser—this is due to the browser loading the webpage from the cache. To
overcome this, you could either clear your browser's cache, or open the
webpage in an Incognito window (and close it when you are done with it), or
simply press Ctrl
+F5
, instead of just F5
, in your browser (using either
an Incognito or regular window), which would force the browser to retrieve the
webpage from the server instead of loading it from the cache.
You may also find this answer helpful, regarding the order of endpoints in FastAPI.
The html=True
option
Setting the html
argument of StaticFiles
instance to True
(i.e.,
html=True
) simply provides an easy way to serve a directory of web content
with just one line of code. If you only need to serve static files, such
as package docs directory, then this is the way to go. If, however, you need
to serve different HTML files that will get dynamically updated, as well as
you wish to create additional routes/endpoints, you should better have a look
at Templates
( not
[FileResponse
](https://fastapi.tiangolo.com/advanced/custom-
response/#fileresponse)), as well as mount your StaticFiles
instance to a
different path (e.g., /static
), rather than root path (and without using
html=True
).