I have react & FastAPI being served with nginx
using docker
. When I run docker-compose up
, everything works with the react app available at localhost:3000
and api available at localhost:8000
. For instance, the get request localhost:8000/fleets/
returns the desired result. I would like the api to be reached at localhost:3000/api/
instead though.
When I add --root-path /api
to the FastAPI Dockerfile
, the react app still loads at localhost:3000
and I am able to reach the api at localhost:3000/api/
, but my react app gets 404 errors when it tries to query for the api. When I go to the Network on tab in Chrome I see react queries for http://127.0.0.1:8000/api/fleets/
leading to a 404 error. The api still available at localhoost:8000/fleets/
though.
Should my react app be querying at localhost:3000/api/
or localhost:8000
and how do I make this adjustment?
Note I am using axios
but changing axios.defaults.baseURL
doesn’t appear to make an impact. I currently have axios.defaults.baseURL = '/';
in my react App.jsx
.
I have also removed "proxy": "http://127.0.0.1:3000"
from my react package.json
.
In FastAPI, I have origins = ["http://localhost:3000", "http://127.0.0.1:8000"]
for dealing with CORS.
FastAPI Dockerfile
:
FROM python:3.8-alpine
ENV PYTHONBUFFERED 1
WORKDIR /api
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
react Dockerfile
:
FROM node:15.13-alpine
WORKDIR /react
COPY . .
RUN yarn run build
nginx-setup.conf
:
upstream api {
server backend:8000;
}
server {
listen 8080;
location / {
root /var/www/react;
}
location /api/ {
proxy_pass http://api/;
proxy_set_header Host $http_host;
}
}
docker-compose.yml
:
version: '3'
services:
backend:
build:
context: ./api
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --root-path /api
ports:
- 8000:8000
frontend:
build:
context: ./react
volumes:
- react_build:/react/build
nginx:
image: nginx:latest
ports:
- 3000:8080
volumes:
- ./nginx/nginx-setup.conf:/etc/nginx/conf.d/default.conf:ro
- react_build:/var/www/react
depends_on:
- backend
- frontend
volumes:
react_build:
EDIT (for more details)
When running without the --root-path /api
:
localhost:3000/api/fleets/
does actually work.localhost:3000/api/fleets
gets redirected tolocalhost:3000/fleets/
which gives nginx 404localhost:3000/api/docs
givesFailed to load API definition
error.localhost:8000/docs
works.localhost:8000/fleets/
works.localhost:8000/fleets
gets redirected tolocalhost:8000/fleets/
and works.- React app pings
http://127.0.0.1:8000/fleets
and gets redirected tohttp://127.0.0.1:8000/fleets/
which works.
When running with --root-path /api
:
localhost:3000/api/fleets/
workslocalhost:3000/api/fleets
gets redirected tolocalhost:3000/api/fleets/
and workslocalhost:3000/api/docs
workslocalhost:8000/docs
gives Failed to Load API definition error.http://localhost:8000/fleets/
works.http://localhost:8000/fleets
gets redirected tolocalhost:8000/api/fleets/ and gives
FastAPI404
{"detail": "Not Found"}`
EDIT Here’s a bare-bones reproducible example:
.
├── backend
│ ├── app
│ │ └── main.py
│ ├── Dockerfile
│ └── requirements.txt
├── docker-compose.yml
├── frontend
│ ├── Dockerfile
│ ├── package.json
│ ├── src
│ │ ├── App.css
│ │ ├── App.jsx
│ │ ├── App.test.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── logo.svg
│ │ ├── reportWebVitals.js
│ │ └── setupTests.js
│ └── yarn.lock
├── nginx
└── nginx-setup.conf
./backend/app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = [
"http://localhost:3000"
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/day", tags=["Dates"])
async def get_day_of_week(day_num: int = 0):
"""
Get the current day of week
"""
days = ['Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat']
return days[day_num]
./backend/Dockerfile
FROM python:3.8-alpine
ENV PYTHONBUFFERED 1
WORKDIR /alpine
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
./backend/requirements.txt
anyio==3.6.1
click==8.1.3
colorama==0.4.5
fastapi==0.78.0
h11==0.13.0
idna==3.3
pydantic==1.9.1
sniffio==1.2.0
starlette==0.19.1
typing_extensions==4.3.0
uvicorn==0.18.2
./frontend/src/App.jsx
import React, { useState, useEffect } from 'react';
import './App.css';
function App() {
const [error, setError] = useState(null);
const [day, setDay] = useState('Sun');
useEffect(() => {
fetch('http://localhost:3000/api/day?day_num=3')
.then((res) => setDay(res.data))
.catch((err) => setError(err.message))
}, []);
return (
<>
<>Hello</>
<>{day}{error}</>
</>
);
}
export default App;
./frontend/Dockerfile
FROM node:15.13-alpine
WORKDIR /frontend
COPY . .
RUN yarn run build
./frontend/.dockerignore
#node_modules
build
./frontend/package.json
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"axios": "^0.27.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"web-vitals": "^1.1.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
./nginx/nginx-setup.conf
upstream api {
server backend:8000;
}
server {
listen 8080;
location / {
root /var/www/react;
}
location /api/ {
proxy_pass http://api/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
}
}
./docker-compose.yml
version: '3'
services:
backend:
build:
context: ./backend
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --root-path /api
ports:
- 8000:8000
frontend:
build:
context: ./frontend
volumes:
- react_build:/frontend/build
nginx:
image: nginx:latest
ports:
- 3000:8080
volumes:
- ./nginx/nginx-setup.conf:/etc/nginx/conf.d/default.conf:ro
- react_build:/var/www/react
depends_on:
- backend
- frontend
volumes:
react_build:
2
Answers
I think you’re mixing things up, how usually this is done:
You have your backend app (FastAPI) running on some port, let it be:
localhost:8000
You have your frontend app (React) running on some port, let it be:
localhost:3000
Also, you use Nginx, so the route is: frontend -> nginx -> backend
Nether. Your right address to make queries is
http://localhost:8080/api/
(you proxy all your request through Nginx), this instructionlocation /api/
means that all request tolocalhost:8080/api/
will be proxied tolocalhost:8000
, for production you’d specify port 80 and configure SSL.I don’t see the whole picture because of the absence of code, but I guess
--root-path
is not what you want. Also carefully check your ports in docker-compose, your app (frontend) must use only 8080 port, do not expose 8000.A possible approach to the problem!
Disclaimer I
I don’t know how much of server side rendering you need so I shall assume that you need the least. You can learn about Server side rendering and Static Site Generation at those links I’ve mentioned.
In a nutshell, server side rendering is basically a web page being rendered every time an user loads it. Where as SSG is an approach through which you usually pre-build pages and keep it ready.
Disclaimer II:
Your page doesn’t change with your SSG content
Now coming to addressing the problem, you can choose to build your react app using the build script. The command for that is
Now once done, you can use this NGINX documentation to serve the built static file. You need to configure your nginx and fastapi accordingly!
Solution to SSR’s
This is a tricky part, now you can choose to do one of these two options: