Docker + Flask + React + Axios + http-proxy-middleware failing with "Error occured while trying to proxy to..."

1.3k Views Asked by At

I have an issue with http-proxy-middleware.

My setup is I have my flask API and my React frontend both in docker containers, run from a docker-compose file. All works well. Flask is on localhost:5000 and react on localhost:3000

My Flask API is listening on a route localhost:5000/recent_customers for a POST request.

Using Postman, I can send some info and get a nicely formed JSON response. It's working well.

The problem is, when I try and use the http-proxy-middleware to call say http://localhost:3000/api/recent_customers which SHOULD route to http://localhost:5000/recent_customers I get the following error:

Error occured while trying to proxy to: localhost:3000/api/recent_customers

With React, I've setup http-proxy-middleware and have the setupProxy.js as such:

  const { createProxyMiddleware } = require("http-proxy-middleware");

  module.exports = function (app) {
    app.use(
      "/api",
      createProxyMiddleware({
        target: "http://localhost:5000",
        changeOrigin: true,
      })
    );
  };

I've setup my Axios as such:

  import axios from "axios";

  const instance = axios.create({
    baseURL: "/api",
  });

  instance.defaults.headers.common["Authorization"] = "AUTH TOKEN FROM INSTANCE";

  // instance.interceptors.request...

  export default instance;

I import the axios into my React component and this is a cropped version of it for clarity:

 import React, { useEffect, useState } from "react";

    import RecentCustomersList from "../../components/Home/RecentCustomerList/RecentCustomerList";

    import axios from "../../axios";

    export default function Home(props) {
    
    // total number of active customers in the system
    const [totalActiveCustomers, setTotalActiveCustomers] = useState(
        "calculating."
    );

    // list of a few customers that have been added recently
    const [recentCustomers, setRecentCustomers] = useState([]);

    
    // This is currently taking temp JSON data.  We will take this from our API with axios in time
    const recentCustomersHandler = () => {
        axios.post("/recent_customers").then((response) => {
        const customers = response.data;
        setRecentCustomers(customers);
        });
    };

    // load the axios API data to be sent to the recent customers
    useEffect(() => {
        recentCustomersHandler();
    }, []);

    // more here for prepping the display of data

    return (
        <div>
        <Grid container spacing={3}>
            {/* more layout here but trimmed for clarity */}
            <RecentCustomerListGridItem />
        </Grid>
        </div>
    );
    }

My React Dockerfile is here:

# Pull official node image
FROM node:14.15.1

# Expose ports.  3000 for web access.  35729 for hot reloading
EXPOSE 3000
EXPOSE 35729

# Set working directory in the docker container
WORKDIR /app

# Add /app/node_modules/.bin to environment variables
ENV PATH /app/node_modules/.bin:$PATH

# Copy package files and install app dependencies
COPY package.json ./app/package.json
COPY package-lock.json ./app/package.json
RUN npm install
RUN npm install react-scripts -g

# Add React app to working directory
ADD . /app

# Start the React app
CMD ["npm", "start"]

My docker-compose file is here:

  version: "3.7"

  services:
    # react
    client:
      container_name: react
      build:
        context: ./frontend
        dockerfile: Dockerfile
      tty: true
      ports:
        - "3000:3000"
      volumes:
        - ./frontend:/app
        - /app/node_modules
      networks:
        - frontend
      depends_on:
        - flask_api

    # flask
    flask_api:
      container_name: flask-api
      build:
        context: ./backend
        dockerfile: Dockerfile
      command: gunicorn --reload --bind 0.0.0.0:5000 flask_api.app:create_app()
      ports:
        - "5000:5000"
      volumes:
        - ./backend:/app #  mount the /backend folder to the docker's /app directory. This allows for hotloading in flask, so when I save locally, container updates
        - appdata:/var/www/
      networks:
        - frontend
        - backend

  networks:
    frontend:
      driver: bridge
    backend:
      driver: bridge

  volumes:
    appdata:
      driver: local

I wonder if anyone is able to spot an issue. If I am missing any info that might help, please do let me know and I'll udpate the question. Thank you.

UPDATE 1 : If I update my axios file to the following:

import axios from "axios";

const instance = axios.create({
  baseURL: "http://localhost:5000",
});

instance.defaults.headers.common["Authorization"] = "AUTH TOKEN FROM INSTANCE";

// instance.interceptors.request...

export default instance;

I still get the same error from Postman, however my console in react / the browser shows as this error:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5000/recent_customers. (Reason: CORS request did not succeed).

2

There are 2 best solutions below

0
On

I was finally able to get this working with a reverse proxy using nginx to host my app.

First you have to use a multistage build with Docker so you can pull in nginx to host your app. Here's a sample Docker config:

# build environment
FROM node:13.12.0-alpine as build
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json ./
COPY package-lock.json ./
RUN npm ci --silent
RUN npm install [email protected] -g --silent
COPY . ./
RUN npm run build

# production environment
FROM nginx:stable-alpine
COPY --from=build /app/build /usr/share/nginx/html
# new
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

There's a few key points in this config:

  1. In the first stage of the build, I install react-scripts so I can create a production build of my app within the context of Docker so we can use that output in the second stage. In the second stage, I create an nginx build that hosts my app.
  2. Create a config file for nginx - in my case I just called it nginx.conf

In nginx.conf - that's where you set up the main entry point for your app which serves up your react app. After that, you add a reverse proxy to point to your actual API. Here's my config:

server {

  listen 80;

  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
  }

  location /api {
    proxy_pass http://api.your-domain.com;
  }

  error_page   500 502 503 504  /50x.html;

  location = /50x.html {
    root   /usr/share/nginx/html;
  }

}

location / is the main entry point for your app - that's where you specify that everything routes to index.html.

The juicy part is location /api - that's the reverse proxy you need to allow your app to communicate via middleware proxy back to your API. In your React app, everything goes to /api/someEndpoint - I created an api.ts file to ensure every call is prepended with /api. This is all working excellently for me, the app performs well and calls to my back end are very fast. Hopefully this helps :)

0
On

To keep it short and simple:

  • In development mode, with a proxy set up, that's an express app that will handle the API call. This app is running in your client container.
  • Your backend is running in another container: flask_api.

For those containers to be able to communicate with each other, you need to use their name. To access flask_api from client you can do: http://flask_api:5000.

So the error comes from you have configured the proxy to target localhost:5000 instead of flask_api:5000.

Good luck !