.
├─ flask_api
│ ├─ app
│ ├─ logs
│ ├─ venv
│ ├─ .env
│ ├─ .gitignore
│ ├─ config.py
│ ├─ README.md
│ ├─ requirements.txt
│ └─ run.py
└─ react_ui
├─ build
├─ node_modules
├─ public
├─ src
├─ .gitignore
├─ package-lock.json
├─ package.json
└─ README.md
Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources.
CORS is specifically a browser security mechanism—it is implemented and enforced by web browsers. If we have a Flask backend and a traditional client-side React frontend, since the fetch requests will be coming from the client-side (browser) going to the backend, we will need to set up CORS. If our frontend was instead something like Next.js 15 and all of our fetch requests were in server components and server actions then these would be server-to-server calls, and are therefor not subject to CORS.
Our approach to handling CORS will depend on how we intend on serving the frontend and backend:
- The frontend and backend will be served from the same domain and port by configuring Nginx as a reverse proxy (example below).
- The frontend and backend will be served separately (different domain and/or different ports)
When developing, the Flask backend will be running on one port (e.g. http://localhost:5000/) and the React backend will be running on another (e.g. http://localhost:3000/). Out-of-the-box, if you want to make an API call to the backend, you would have to use the full URL since they are not the same origin:
fetch('http://localhost:5000/api/color', options);
// or
axios.get('http://localhost:5000/api/color', config);Note that anyone can make a GET request without worrying about CORS. But, if the server is configured to send back some json data, we then need to consider the following:
- if the requester is from the same origin (same domain and port), we have no CORS issues
- if the requester is from a different origin (e.g. in our case, a different port), then the server needs to explicitly allow code from other origins to access the resource. This can be done quickly with the
'Access-Control-Allow-Origin'header using the*wildcard. This allows any origin to access the resource
# ...
response = jsonify(data)
response.headers['Access-Control-Allow-Origin'] = '*'
return responseAs soon as we try to do a POST though, we will run into the same problem again. The Flask backend needs to identify if origins other than its own are allowed to POST. This is easiest done using flask-cors and is described in the next section.
But for this example, we are assuming that in production we intend to serve the React frontend from the same domain and port as the Flask backend.
In the meantime, if using create-react-app we can configure a proxy in the package.json file of the React project. This allows the app to "pretend" it's making requests from the same port as the backend, thereby avoiding all CORS issues.
"proxy": "http://127.0.0.1:5000",This tells the React development server to proxy any unknown requests to your backend API server. Side note: for some reason you cannot use http://localhost:5000. Once we add this proxy pointing to our backend, we can change all our API calls to just the endpoint path:
fetch('/api/color', options);Vite's development server also provides a proxy configuration option similar to the one in Create React App. Vite handles proxying through its vite.config.js (or vite.config.ts) file.
With the proxy set, we no longer need the headers for any responses on the server, since it thinks the request is from the same origin:
# ...
response = jsonify(data)
# We only need this header for public APIs.
# response.headers['Access-Control-Allow-Origin'] = '*'
return responseThis proxy is only applicable to the React development server. When we deploy the frontend and backend together, we would configure nginx to serve the frontend, then act as a reverse-proxy for the backend API which will be running as a service. In other words, we don't need to change anything in our front or backend code when we deploy this.
nginx example:
server {
listen 80;
server_name yourdomain.com;
# Route API requests to Flask backend
location /api/ {
proxy_pass http://localhost:5000; # Flask running locally on port 5000
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Route all other requests to Next.js frontend
location / {
proxy_pass http://localhost:3000; # Next.js running locally on port 3000
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}Nginx only handles requests coming from the client-side (browsers). If I have a Flask backend and a Next.js frontend and all my fetch requests to the backend are happening in server components or server actions, then these all need to use the full internal URL of the backend (e.g., http://flask-service:5000 if using service discovery, or http://192.168.1.10:5000 if using an internal IP).
See: https://flask-cors.readthedocs.io/en/latest/
pip install flask-cors In the simplest case, you can initialize the Flask-Cors extension with default arguments. This will allow CORS for all domains on all routes.
# ...
from flask_cors import CORS
app = Flask(__name__)
# Allow all origins access to all resources:
CORS(app) That's it. Our front end can now access resources and POST to our backend APIs using the full URL:
fetch('http://localhost:5000/api/color', options)
// or
axios.post('http://localhost:5000/api/color', data, headers);Rather than allowing all origins access to everything, you will likely want to specify which origins have access to what resources:
# Allow some origins access to some resources:
cors = CORS(app, resources={r'/api/*': {'origins': ['http://localhost:3000']}})Any other routes in the Flask app that don't match the /api/* pattern cannot be accessed from different origins.
In lieu of initializing CORS(), you can use decorators to allow access directly on the endpoints:
# ...
from flask_cors import cross_origin
@app.route('/api/demo', methods=['GET'])
@cross_origin(origins=['http://localhost:3000'])
def api_demo():
return 'example'It is a very good idea to prefix API endpoints with /api so that:
- they do not get mixed with any possible routes used by the React side.
- they can be easily targeted with CORS rules as shown above
Flask-cors allows for total granular control beyond what is shown here, for example you can pass parameters saying which methods are allowed and more. See the API docs.