Reputation: 835
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.
GET
request at the domain root endpoint?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?Upvotes: 23
Views: 25084
Reputation: 14994
No need for Jinja
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
app = FastAPI()
app.mount("/assets", StaticFiles(directory="frontend/dist/assets"), name="assets")
@app.get('/api/health')
async def health():
return { 'status': 'healthy' }
# Route to serve React index.html (for client-side routing)
@app.get("/{catchall:path}")
async def serve_react_app(catchall: str):
return FileResponse("frontend/dist/index.html")
This is how you can host your SPA app on Heroku (FastAPI + React)
Upvotes: 2
Reputation: 1433
I'm adding this answer purely to provide a complete working example of serving a React App from FastAPI. The credit goes to @csum though. Their answer is what led me to this solution so if you find this answer helpful you should give this answer an upvote as well.
If you don't care for an explanation and just want the code checkout this repo.
Here is my directory structure for my app for your reference. Whatever your directory structure is doesn't really matter. The important piece is knowing the path to the build
folder generated by npm run build
.
example-app/
├── api/ <- My FastAPI App
└── main.py
└── ui/ <- My React App
└── build/
└── static/
└── index.html/
└── ...
└── src/
└── package.json
└── tsconfig.json
└── ...
NOTE: depending on how you have configured your React app you may have dist
instead of build
.
In main.py
there are 3 important things for serving the react app.
static
folder found in build
to /static
This allows the browser to load all the css and js needed for the React SPA to render.index.html
as a template so it can be served up by FastAPI./
route handler. This allows you to have additional routes in your React app such as /dashboard
, /profile
etc.main.py
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.requests import Request
app = FastAPI()
# Sets the templates directory to the `build` folder from `npm run build`
# this is where you'll find the index.html file.
templates = Jinja2Templates(directory="../ui/build")
# Mounts the `static` folder within the `build` folder to the `/static` route.
app.mount('/static', StaticFiles(directory="../ui/build/static"), 'static')
# sets up a health check route. This is used later to show how you can hit
# the API and the React App url's
@app.get('/api/health')
async def health():
return { 'status': 'healthy' }
# Defines a route handler for `/*` essentially.
# NOTE: this needs to be the last route defined b/c it's a catch all route
@app.get("/{rest_of_path:path}")
async def react_app(req: Request, rest_of_path: str):
return templates.TemplateResponse('index.html', { 'request': req })
To get the app running follow these steps.
npm run build
from the ui folderuvicorn main:app --reload
from the api folderYou can verify the react app and the FastAPI app are both working by entering the following routes into your browser.
localhost:8000/api/health
returns { "status": "healthy" }
localhost:8000
returns your React App's homepagelocalhost:8000/test-react-route
returns whatever page is setup in React Router.Upvotes: 8
Reputation: 821
Dear hainabaraka
Your question resembles this one: How do I serve a React-built front-end on a FastAPI backend?. In that one, I contributed an answer, but I'd like to also refer you to Mike Chaliy's answer, which updates mine.
In any case, here are two thoughts on that solution (that I should really update) and the answers you get here:
nginx
approach: this removes responsibility from your code, meaning that it also removes control from you. DevOps love this kind of "you do your thing and let me worry about that" approach, but let's just say that this is a good solution... not for your case. You want to serve from your API.Jinja2
approach: I'm not sure this would work, and even if it did, it would be introdsucing a lot of CPU waste. Jinja2
is an excellent tool for Python based backend tremplate rendering. I used it extensively to generate HTML medical reports to be then PDF-rendered but in this case it always sounds like a "Yes, it works, but...". Rule of thumb for "but"-sentences: everything to the left of the "but" is irrelevant.FastAPI
RTFM approach: I am a HUGE fan of FastAPI and I'm (extremely slowly) trasnslating its documentation to European Portuguese, but the examples there are very very limited. Referring you to the FastAPI docs is assuming you're an idiot that never thought of that.The solution I present works and has been working for me for years now, both with Vue and React SPAs. The caveat is that you either serve your app from a special endpoint (I suggest /my-spa
, which is quite lame), or, if you want the app to come from /
, you have to do the
app.mount('/', SPAStaticFiles(directory='folder', html=True), name='whatever')
after all other endpoints, and you cannot obviously have other endpoints conflicting with either /
or any React route within it. This perhaps adds to why APIs are recommended to hang from an /api
endpoints, and possibly versioned, like /api/v1/...
. Please let me know if this works for you.
For that, there an excellent resource in Zalando RESTful API and Event Guidelines.
Upvotes: 3
Reputation: 41
I've also struggled with this and here's an approach that worked for me
import logging
from fastapi import FastAPI
from starlette.responses import RedirectResponse
from starlette.staticfiles import StaticFiles
app = FastAPI()
@app.get("/")
async def index():
return RedirectResponse(url="/index.html")
app.mount("/", StaticFiles(directory="backend/ui/"), name="ui")
Note: Please observe that
app.mount
is called after registering the/
route. From my experience if the mount is called before the registration then it will simply not redirect toindex.html
Note: I use
react-scripts
to generate the app.
Upvotes: 2
Reputation: 1992
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})
Upvotes: 15
Reputation: 2240
The "traditional" approach to running a web application is to have a server that serves your web application (i.e. your React app). Usually you'll hear about nginx being used as the web server being used for modern day single page applications. When you run npm run start
you start up a local server on your machine and it makes your app available at http://localhost:8080
(the port and hostname are of course configurable).
When it comes to your API, it should be it's own server available at a different endpoint/url, and then your web app will make API calls to that endpoint/url in order to fetch data.
The way you're describing things, it sounds like you're trying to use FastAPI to server render your web app, but I'm not sure how feasible that is, especially considering there is an entire framework solely dedicated to server rendering react applications.
Upvotes: 3