Christy Nakou
Christy Nakou

Reputation: 423

How to serve static files in FastAPI

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.

Upvotes: 25

Views: 56796

Answers (5)

Sabbir Sobhani
Sabbir Sobhani

Reputation: 1314

▶️ 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"}

Upvotes: 7

Yagiz Degirmenci
Yagiz Degirmenci

Reputation: 20826

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

Upvotes: 12

Nitsan BenHanoch
Nitsan BenHanoch

Reputation: 709

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')

Upvotes: 3

Justin Malloy
Justin Malloy

Reputation: 489

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.

Upvotes: 34

lsabi
lsabi

Reputation: 4496

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

Upvotes: 1

Related Questions