Skip to content

Instantly share code, notes, and snippets.

@ultrafunkamsterdam
Last active November 21, 2024 17:37
Show Gist options
  • Save ultrafunkamsterdam/b1655b3f04893447c3802453e05ecb5e to your computer and use it in GitHub Desktop.
Save ultrafunkamsterdam/b1655b3f04893447c3802453e05ecb5e to your computer and use it in GitHub Desktop.
FastAPI support for React ( with working react-router )
"""
███████╗ █████╗ ███████╗████████╗ █████╗ ██████╗ ██╗
██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║
█████╗ ███████║███████╗ ██║ ███████║██████╔╝██║
██╔══╝ ██╔══██║╚════██║ ██║ ██╔══██║██╔═══╝ ██║
██║ ██║ ██║███████║ ██║ ██║ ██║██║ ██║
╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝
██████╗ ███████╗ █████╗ ██████╗████████╗
██╔══██╗ ██╔════╝ ██╔══██╗ ██╔════╝╚══██╔══╝
██████╔╝ █████╗ ███████║ ██║ ██║
██╔══██╗ ██╔══╝ ██╔══██║ ██║ ██║
██║ ██║ ███████╗ ██║ ██║ ╚██████╗ ██║
╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝
serve react app via fastapi, complete with working react-router
How to use ?
-------
it's actually very simple.
create your fastapi app like you'd normally do
but you have to use this as your VERY LAST mount to your main app
example
----
```
# regular fastapi setup
root = fastapi.FastAPI()
@root.get('/some/path')
...
...code...
...
@root.get('/some/other/path')
...
...code...
...
... some static files maybe ?
root.mount('/some/staticfiles')
...
...code...
...
# moment of truth
from fastapi_react import frontend
root.mount('/', frontend(build_dir='build')) # we are MOUNTING it!
...
...
uvicorn.run(root, port=8080) # maybe ?
```
"""
def frontend(build_dir="./build"):
"""
FASTAPI ROUTER FOR REACT FRONTEND
:param build_dir: the path to your build folder for react
we are assuming the "static" folder lives within your build folder
if not, change it some lines below
:return: fastapi.FastAPI
"""
import pathlib
import fastapi.exceptions
from fastapi import FastAPI, Request, Response
from fastapi.staticfiles import StaticFiles
build_dir = pathlib.Path(build_dir)
react = FastAPI(openapi_url="")
react.mount('/static', StaticFiles(directory=build_dir / "static"))
@react.get('/{path:path}')
async def handle_catch_all(request: Request, path):
if path and path != "/":
disk_path = build_dir / path
if disk_path.exists():
return Response(disk_path.read_bytes(), 200)
else:
if disk_path.is_file():
raise fastapi.exceptions.HTTPException(404)
return Response((build_dir / "index.html").read_bytes(), 200)
return react
@andreas-bergstrom
Copy link

Great but I had to add the correct mime for this to render in Safari:

def frontend(build_dir="./build"):
    """
     FASTAPI ROUTER FOR REACT FRONTEND
    :param build_dir: the path to your build folder for react
            we are assuming the "static" folder lives within your build folder
            if not, change it some lines below
    :return: fastapi.FastAPI
    """

    import pathlib
    import fastapi.exceptions
    from fastapi import FastAPI, Request, Response
    from fastapi.staticfiles import StaticFiles

    build_dir = pathlib.Path(build_dir)

    react = FastAPI(openapi_url="")
    react.mount('/static', StaticFiles(directory=build_dir / ""))

    @react.get('/{path:path}')
    async def handle_catch_all(request: Request, path):
        if path and path != "/":
            disk_path = build_dir / path
            if disk_path.exists():
                content = disk_path.read_bytes()
                content_type = "text/html"
                
                # Set correct MIME types for different file extensions
                if path.endswith(".js"):
                    content_type = "application/javascript"
                elif path.endswith(".css"):
                    content_type = "text/css"
                elif path.endswith(".svg"):
                    content_type = "image/svg+xml"
                
                return Response(content, 200, media_type=content_type)
            else:
                if disk_path.is_file():
                    raise fastapi.exceptions.HTTPException(404)

        return Response((build_dir / "index.html").read_bytes(), 200)

    return react```

@ultrafunkamsterdam
Copy link
Author

ultrafunkamsterdam commented Nov 21, 2024

@andreas-bergstrom

Then you leave out many other filetypes , like fonts, png, jpg etc).
Actually this gist outdated already.

Here's a better one, which also handles all file types.

from pathlib import Path

FRONTEND_DIST = Path( "to your dist folder")
router = APIRouter()

@router.get('/{path:path}')
async def frontend_handler(path: str):
    fp = FRONTEND_DIST  / path
    if not fp.exists():
        fp = FRONTEND_DIST / "index.html"
    return fastapi.responses.FileResponse(fp)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment