Description
How can I serve static files (html, js) easily? Even though I don't need to inject python data, I attempted to do it with jinja and encountered issues.
Additional context
I tried something like:
@router.get("/webui/{file_name}", name="webui.show")
async def webui_show(file_name: str):
template = jinja2.get_template(file_name)
content = template.render()
return HTMLResponse(content=content, status_code=200)
Where jinja2 was
loader = FileSystemLoader(web_dir("dist"))
# no escaping while deubing
jinja2 = Environment(loader=loader, autoescape=False)
But I received an error message "jinja2.exceptions.TemplateSyntaxError: Expected an expression, got 'end of print statement'" when returning JavaScript files. What's sanctioned way to implement simple static file serving in fastapi? If so, it'd be nice to have some documentation around it.
FastAPI being based on Starlette you may use it for that purpose: https://www.starlette.io/staticfiles/
@3lpsy if you have a directory with static files, you can do as @euri10 suggests.
If you want to return some specific files (not necessarily from a fixed static directory), you can use a FileResponse
: https://www.starlette.io/responses/#fileresponse
Thanks for the responses @euri10 and @tiangolo. I was attaching the StaticFiles instance to the FastAPI() app incorrectly because I thought StaticFiles was a route but it's more of an ASGI app. For anyone else trying to do the same, you can do something like:
api = FastAPI(title=config.API_PROJECT_NAME, openapi_url="/api/v1/openapi.json")
api.mount("/static", StaticFiles(directory="static"))
Great!
I still have to document that properly :smiley:
Probably I'm just too stupid but is there a way to make this work with the root path?
It works with /static but as soon as I try to use / as path for the static content it doesn't work anymore. I would like to use / for the static content and then something like /api as base for all the rest stuff.
@tarioch StaticFiles
takes over the whole path (and sub-paths) assigned to it. If you pass the root, it would only serve static files, and no path operation/endpoint would work.
Thanks for the info, I now figured a way that seems to do what I want. I do the following. Have all the static files except the "root" (index.html) in /static subfolder and then do
app.mount("/static", StaticFiles(directory=pkg_resources.resource_filename(__name__, 'static')), name="static")
app.include_router(
apiRouter,
prefix="/api",
)
@app.get("/api/.*", status_code=404, include_in_schema=False)
def invalid_api():
return None
@app.get("/.*", include_in_schema=False)
def root():
return HTMLResponse(pkg_resources.resource_string(__name__, 'static/index.html'))
if __name__ == "__main__":
run()
With that order I get
@tarioch cool. Although I don't think "/.*"
would work, there's no logic to convert the .*
into a wildcard.
But you probably can use Starlette path
route convertors: https://www.starlette.io/routing/
Nevertheless, having a single-page app, I would suggest you separate your frontend from your backend, and then use a proxy/load-balancer on top, like Traefik, to separate the paths. The project generators include all that: https://fastapi.tiangolo.com/project-generation/
actually as far as I can tell the /.* works perfectly for me (I can for example do something like /foobar and it shows the index.html.
I actually don't have much static content, just some js and css and maybe a handful of pictures. I'm running everything in a kubernetes cluster so already have all the loadbalancer infrastructure in place and I prefer the simplicity of not having to deal with 2 different containers for deploying my app.
Ok, cool!
Is it possible to update the documentation to provide a minimal yet self-contained example? That would be very helpful.
Isn't it already well documented here?
https://fastapi.tiangolo.com/tutorial/static-files/
Thanks @alexmitelman , yeah, I think those are the docs for this :memo: :rocket:
I think we could close this issue now, right @3lpsy ?
Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.
I could solve this by combining these issues.
The index.html used to serve a single page app at / is served from public/index.html and other static files from /public.
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
# Use this to serve a public/index.html
from starlette.responses import FileResponse
app = FastAPI()
app.mount("/public", StaticFiles(directory="public"), name="public")
@app.get("/")
async def read_index():
return FileResponse('public/index.html')
You can use static/ or statics etc instead.
<!-- /public/index.html -->
<!DOCTYPE html>
<html>
<head>
<!-- Temporary Favicon for this website from GitHub -->
<link
rel=icon
href="https://avatars0.githubusercontent.com/u/32325099?s=460&v=4"
/>
<link rel="stylesheet" href="/public/css/test.css" />
<title>If you see the favicon, you made it.</title>
</head>
<body>
<h1 class="red" >red</h1>
<div id="app"></div>
<script src="index.js"></script>
</body>
</html>
/* public/css/test.css */
.red {
color: red;
}
Visit, / from your app and you will see the favicon and title with the message with css.
I'd like to propose a slightly modified solution from @steadylearner 's solution.
In existing solution, it requires that the html refers to include files (JS, CSS) with /public/
prefix.
If you have a frontend developer working on just the static pages, they may just use simple HTTP server, serving from /public
path, and this will break the page. Because it cannot find /public/*
.
I propose using redirect to point user to /public/index.html
instead.
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
# Use this to serve a public/index.html
from starlette.responses import FileResponse
from starlette.responses import RedirectResponse # add this
app = FastAPI()
app.mount("/public", StaticFiles(directory="public"), name="public")
@app.get("/")
async def read_index():
# return FileResponse('public/index.html') # remove this
return RedirectResponse(url="/public/index.html") # change to this
<!-- /public/index.html -->
<!DOCTYPE html>
<html>
<head>
<!-- Temporary Favicon for this website from GitHub -->
<link
rel=icon
href="https://avatars0.githubusercontent.com/u/32325099?s=460&v=4"
/>
<!--link rel="stylesheet" href="/public/css/test.css" /--> <!-- remove this -->
<link rel="stylesheet" href="css/test.css" /> <!-- change to this -->
<title>If you see the favicon, you made it.</title>
</head>
<body>
<h1 class="red" >red</h1>
<div id="app"></div>
<script src="index.js"></script>
</body>
</html>
I was unable to get the /.*
path working but managed to use a catch all path parameter instead. Eg:
@app.get("/api/{catchall:path}", status_code=404, include_in_schema=False)
def api_not_found():
raise HTTPException(status_code=404)
@app.get("/{catchall:path}", response_class=HTMLResponse)
def indexrequest: Request):
return templates.TemplateResponse("index.html", {"request": request})
@andyjones11 I'm trying to host an API and a react app in my fastapi implementation but it just isn't working. Can you share a more complete gist of your solution?
Most helpful comment
Is it possible to update the documentation to provide a minimal yet self-contained example? That would be very helpful.