skip to Main Content

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.

enter image description here

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 to localhost:3000/fleets/ which gives nginx 404
  • localhost:3000/api/docs gives Failed to load API definition error.
  • localhost:8000/docs works.
  • localhost:8000/fleets/ works.
  • localhost:8000/fleets gets redirected to localhost:8000/fleets/ and works.
  • React app pings http://127.0.0.1:8000/fleets and gets redirected to http://127.0.0.1:8000/fleets/ which works.

When running with --root-path /api:

  • localhost:3000/api/fleets/ works
  • localhost:3000/api/fleets gets redirected to localhost:3000/api/fleets/ and works
  • localhost:3000/api/docs works
  • localhost:8000/docs gives Failed to Load API definition error.
  • http://localhost:8000/fleets/ works.
  • http://localhost:8000/fleets gets redirected to localhost: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


  1. 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

    Should my react app be querying at localhost:3000/api/ or
    localhost:8000 and how do I make this adjustment?

    Nether. Your right address to make queries is http://localhost:8080/api/ (you proxy all your request through Nginx), this instruction location /api/ means that all request to localhost:8080/api/ will be proxied to localhost: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.

    Login or Signup to reply.
  2. 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

    npx react-scripts build
    

    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:

    • You can choose to use implement a solution like this where you can build a custom SSR solution.
    • or you can choose to implement a SSR solution with NextJS where you can import your CRA components. These components will still work, and then you can choose to use SSR. This SSR solution will solve your API needs. Even GatsbyJS with which you can build a similar solution.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search