I'm trying to serve React SPA and a few API endpoints from FastAPI. React has its own routing, so in order to split responsibilities I use 2 FastAPI apps - one comes with all authorization bells and whistles and is mounted to the API route, and the second has one job - all requests that don't begin with /api
should return the SPA's index.html
. Sounds simple, right? Well, I either miss something really basic, or it's not that simple after all.
So this is the API app mounting (no questions here, works fine):
api_app = secure_authenticated_app() # returns a tweaked FastAPI app
# main app
app = FastAPI()
app.mount("/api", api_app, name="api")
But then, the party starts.
client_folder_path = pathlib.Path(__file__).parent.absolute().joinpath("build")
app.mount("", StaticFiles(directory=client_folder_path, html=True), name="client")
@app.get("/{full_path:path}")
async def serve_client():
with open(client_folder_path.joinpath("index.html")) as fh:
data = fh.read()
return Response(content=data, media_type="text/html")
My logic here following is: mount the client build folder to the root path so it could easily find all its assets, serve the index.html
when getting to the root path, and if you encounter some route that you don't know - no worries, just serve index.html
.
In reality it serves the html from the root path, other frontend assets are served too, but the /{full_path:path}
, which I keep seeing as a starlette solution for 'wildcard route', doesn't get hit - routes like /whtever
return 404.
I tried moving them around - with no luck, each time one of the features (either serving html from root, serving it from any other path or both) won't work. For the one coming from Node, this behavior is really surprising; is there a simple beautiful solution without writing full-blown helper classes?
As explained in this answer, as well as here and here, the order in which the endpoints are defined matters, as, in FastAPI, endpoints are evaluated in order. Please have a look at those references for more details.
Hence, while mounting api_app
to /api
, and then the StaticFiles
instance/app to /
(i.e., root path) would work fine, adding an endpoint that captures arbitrary paths, after mounting the StaticFiles
app to /
, would not work. The reason is simply bacause that endpoint would never be reached/called, as any request that starts with /
path (e.g., /whatever
) would be handled by the StaticFiles
app—"Mounting" means adding a complete "independent" application in a specific path, that then takes care of handling all the sub-paths.
Thus, if you still would like having the StaticFiles
app mounted to /
(i.e., root path), a solution would be to have the api_app
mounted first, followed by the StaticFiles
app, and use a custom exception handler, as demonstrated in this answer, in order to handle 404 Not Found
exceptions/errors and return your custom response—in your case, return a FileResponse
/TemplateResponse
of index.html
. Related answers about custom exception handling that might prove helpful to you can be found here, as well as here and here.