5

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:
3
  • How are you configuring the API endpoint in your react app? Usually you'll want to configure the other API URL base when building, so that your app in production requests content from https://<prodhost>/api - <prodhost> seems to be localhost:3000 here (and http, not https probably) Commented Jul 14, 2022 at 21:59
  • @MatsLindh if I set axios.defaults.baseURL = 'http://localhost:3000/api/'; and then query axios.get('/fleets/') I still see the react app first query http://127.0.0.1:8000/fleets and get 307, temporary redirect to http://127.0.0.1:8000/api/fleets/ which returns 404 not found. Commented Jul 15, 2022 at 12:49
  • @NatarajMedayhal, doesn't the upstream block provide the port? Commented Jul 15, 2022 at 15:07

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

4 Comments

I have added the code to recreate a bare-bones version of the issue if helpful. When I switch fetch('http://localhost:3000/api/day?day_num=3) to fetch('http:localhost:8080/api/day?day_num=3') as you suggested, I still get net::ERR_CONNECTION_REFUSED.
You've missed // in the second fetch
Can you create a repo with the code you've pasted here?
The second fetch missing // was just a typo in the comment. Here's repo: github.com/carter-page/demo-docker-nginx-fastapi-react.git
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.