reactjsfastapi

how to put backend and frontend together - returning react frontend from fastapi backend endpoint


Firstly, I just wanted to say that this is my first web application project. I've spent the past few days trying to find answers on how to essentially put the frontend and backend together. I have a lot of questions, but the main one I want answered is on how to return my frontend 'final product' from a backend endpoint.

This is what I understand (please correct me if I'm wrong):

Then comes the following problem:

When I want to test out my front end and see how it's coming along, I just run npm run start. I then go to the given url (usually http://localhost:8080/) and I have access to the frontend that I've developed. And when I want to deploy it, I run npm run build, which gives me a dist folder (bundled together and minified).

If I want to run and test my backend locally, as I am using FastAPI, I simply run uvicorn main:app --reload.

How to put the two together? More specifically, in my backend code, how do I return the product of my frontend work (i.e., the dist folder?). I've tried the following (simplified):

@app.get("/", response_class=HTMLResponse)
def root():
    return open("../frontend/dist/index.html", "r").read()

but, of course, this only gives me the static html without the React components.

I realize this post may be loaded with incorrect assumptions and poor practices (in which case, my apologies! and I would appreciate any corrections/suggestions.) However, if the following questions could be answered, I would greatly appreciate it. These are questions I have that will hopefully help me test my whole web application locally on my computer.

  1. How do I return the product of my frontend work for the GET request at the domain root endpoint?
  2. If there is a page A, page B, and page C for my web app, each with url www.example.com/A, www.example.com/B, and www.example.com/C do I have to create three separate React frontend projects? I.e., equivalent of having three dist folders? What is the standard way this is handled?

Solution

  • These are good questions and it is certainly possible. I will tell you what I do, with the caveat that there may be a better way...

    I'm using Vue instead of React, but its build process also sends static html, js and css to a dist/ directory, so the process should be about the same.

    First you can copy the dist/index.html file you mention into your FastAPI templates/ directory. You will use your FastAPI route to serve that file as a Template.

    Then copy your js and css into a static/ directory and make sure FastAPI knows about both static and templates.

    from fastapi import FastAPI, Request
    from fastapi.staticfiles import StaticFiles
    from fastapi.templating import Jinja2Templates
    
    app = FastAPI()
    
    app.mount("/static", StaticFiles(directory="static"), name="static")
    
    templates = Jinja2Templates(directory="templates")
    
    @app.get("/")
    async def serve_spa(request: Request):
        return templates.TemplateResponse("index.html", {"request": request})
    

    You may need to set something in React in order for your build to know that the js and css will live in a dir called static. For Vue, there is the assetsDir option within vue.config.js

    For your question about handling different paths, like example.com/a and example.com/b, it depends how you want to handle those requests. Are you wanting your single react app to handle all of those routes?

    If that is the case, you may want to see also: How to capture arbitrary paths at one route in FastAPI?

    One option is to copy the serve_spa() route above and handle your routes, like /a, /b, etc.

    Or use a catch-all route:

    @app.route("/{full_path:path}")
    async def catch_all(request: Request, full_path: str):
        print("full_path: "+full_path)
        return templates.TemplateResponse("index.html", {"request": request})