21

I am trying to serve static files that I have in a package_docs directory. When I open in the browzer:

http://127.0.0.1:8001/packages/docs/index.html , the page is running.

But I want to open the page: http://127.0.0.1:8001/packages/docs/

without the source file. And the output is 404 Not Found

app.mount("/packages/docs", 
    StaticFiles(directory=pkg_resources.resource_filename(__name__, 'package_docs')
    ), 
    name="package_docs")

@app.get("/packages/docs/.*", include_in_schema=False)
def root():
    return HTMLResponse(pkg_resources.resource_string(__name__, "package_docs/index.html"))


app.include_router(static.router)
app.include_router(jamcam.router, prefix="/api/v1/cams", tags=["jamcam"])

How can I change my code? Any advice will be helpful. Thank you in advance.

Christy Nakou
  • 393
  • 1
  • 2
  • 14
  • answer posted by @justin-malloy seems to be the proper one, you just have to include html=True in the StaticFiles() call. – Nikhil VJ Oct 16 '21 at 02:39
  • Please have a look at [this answer](https://stackoverflow.com/a/70662813/17865804), as well as this [detailed answer](https://stackoverflow.com/a/73113792/17865804). – Chris Jan 08 '23 at 08:23

5 Answers5

30

There's a html option in Starlette that can be used within FastAPI. Starlette Documentation

This would let you have something such as:

app.mount("/site", StaticFiles(directory="site", html = True), name="site")

Which would parse /site to /site/index.html, /site/foo/ to /site/foo/index.html, etc.

The other answers can help you redirect if you want to change the folder names in a way not handled by using "directory = /foo", but this is the simplest option if you simply want to load the associated .html files.

Justin Malloy
  • 429
  • 5
  • 9
  • Thanks! This should be the chosen answer, and FastAPI really ought to include this in their documentation. I was putting html=True outside of that StaticFiles() function and getting error. – Nikhil VJ Oct 16 '21 at 01:47
12

You need to use FastAPI's TemplateResponse (actually Starlette's):

from fastapi import Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

app.mount("/static", StaticFiles(directory="static"), name="static")

templates = Jinja2Templates(directory="package_docs")

@app.get("/items/{id}")
async def example(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

Request as part of the key-value pairs in the context for Jinja2. So, you also have to declare it as query parameter. and you have to specify a html file you want to render it with Jinja ("your.html", {"request": request})

Also to return a HTMLResponse directly you can use HTMLResponse from fastapi.responses

from fastapi.responses import HTMLResponse

@app.get("/items/", response_class=HTMLResponse)
async def read_items():
    return """
    <html>
        <head>
            <title></title>
        </head>
        <body>
        </body>
    </html>
    """

You can read more about custom response from FastAPI Custom Responses

Yagiz Degirmenci
  • 16,595
  • 7
  • 65
  • 85
3

If app.mount is not an option, you can always manually read the file and respond with its content...

For example, if your static files are inside /site then:

from os.path import isfile
from fastapi import Response
from mimetypes import guess_type


@app.get("/site/{filename}")
async def get_site(filename):
    filename = './site/' + filename

    if not isfile(filename):
        return Response(status_code=404)

    with open(filename) as f:
        content = f.read()

    content_type, _ = guess_type(filename)
    return Response(content, media_type=content_type)


@app.get("/site/")
async def get_site_default_filename():
    return await get_site('index.html')
  • Thank you. I really needed this since there's a bug with fastapi docker. ModuleNotFoundError: No module named 'aiofiles' – EminezArtus Nov 23 '22 at 04:21
1

From the docs

The first "/static" refers to the sub-path this "sub-application" will be "mounted" on. So, any path that starts with "/static" will be handled by it.

This means that you mount your directory at http://127.0.0.1:8001/packages/docs/ , but you then need to either specify a file in the URL, or add a handler as you did. The problem though, is that since you mounted the path first, it will not take into consideration the following paths that include part of the path.

A possibility is to first specify the path for http://127.0.0.1:8001/packages/docs/ so that it is handled by fastapi and then to mount the folder, serving static files.

Also, I would redirect users asking for http://127.0.0.1:8001/packages/docs/ to http://127.0.0.1:8001/packages/docs/index.html

lsabi
  • 3,641
  • 1
  • 14
  • 26
1

▶️ 1. You don't need to create route to serve/render homepage/static-folder explicitly. When you mark a directory as static it will automatically get the first argument as route of the app.mount() in this case it's app.mount("/"). So, when you enter the base url like http://127.0.0.1:8000/ you will get the static files.

▶️ 2. app instance of the FastAPI() class. Yes, you can have as many instance as you want in a single FASTAPI app.

▶️ 3. api_app instance of the FastAPI() for other api related task.

The reason 2 & 3 need because if you want to stick with just app instance, when user req for the url http://127.0.0.1:8000/, user will get the the static file, then when user will req for http://127.0.0.1:8000/hello then the server will try to find hello inside static-folder, but there is no hello inside static-folder!, eventually it will be a not-found type response. That's why it's need to be created another instance of FastAPI api_app, and registered with prefix /api(▶️ -> 5), so every req comes from http://127.0.0.1:8000/api/xx, decorator @api_app (▶️ -> 4) will be fired!

instance declaration and mount() method calling order is important.

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from starlette.templating import Jinja2Templates

templates = Jinja2Templates(directory='homepage-app')

# for ui
#  -> 2
app = FastAPI(title="homepage-app")

# for api
#  -> 3
api_app = FastAPI(title="api-app")

# for api and route that starts with "/api" and not static
#  -> 5
app.mount("/api", api_app)

# for static files and route that starts with "/"
#  -> 1
app.mount("/", StaticFiles(directory="static-folder", html=True), name="static-folder") 

# for your other api routes you can use `api_app` instance of the FastAPI
#  -> 4
@api_app.get("/hello")
async def say_hello():
    print("hello")
    return {"message": "Hello World"}
Sabbir Sobhani
  • 389
  • 8
  • 15