CORS and Frontend Integration

Introduction

Single-page apps (React, Vue, Svelte) often run on localhost:5173 while Flask API runs on localhost:5000. Browsers block cross-origin requests unless the API sends CORS headers. This chapter configures Flask-CORS, structures /api/v1/ routes, and notes OpenAPI documentation tools for API teams.

Prerequisites

Same-Origin vs Cross-Origin

SetupOriginCORS needed?
Flask serves HTML + API on same host/portSameNo
Vite dev server + Flask APIDifferent portYes
Production SPA on CDN, API on api.example.comDifferent subdomainYes

Browser sends preflight OPTIONS for some cross-origin requests before POST with JSON.

Install Flask-CORS

bash
pip install flask-cors

Global config in factory:

Code explanation:

  • resources limits CORS to API paths—not every route needs open origins
  • allow_headers must include Authorization for JWT

Production:

python
origins = os.environ.get("CORS_ORIGINS", "").split(",")
CORS(app, resources={r"/api/*": {"origins": origins}})

Never use origins: "*" with credentials: true.

Per-Route cross_origin

python
from flask_cors import cross_origin
 
@api_bp.route("/public/stats", methods=["GET"])
@cross_origin(origins="*")
def public_stats():
    return {"visits": 1000}

Use sparingly for truly public read-only endpoints.

Frontend fetch Example

Same pattern for Vite + Vue—see Vue Fetching API and CORS. Concept with React-style fetch:

javascript
const response = await fetch("http://127.0.0.1:5000/api/v1/posts", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${token}`,
  },
});
const data = await response.json();

Dev proxy alternative—no CORS in dev:

vite.config.js:

javascript
export default {
  server: {
    proxy: {
      "/api": "http://127.0.0.1:5000",
    },
  },
};

Frontend calls /api/v1/posts on same origin; Vite forwards to Flask.

Tip

Proxy in Dev, CORS in Staging

Many teams proxy locally and configure real CORS only where origins differ in deployed environments.

API Version Prefix

app/api/__init__.py:

python
from flask import Blueprint
 
api_v1_bp = Blueprint("api_v1", __name__, url_prefix="/api/v1")

Register in factory:

python
from app.api.routes import api_v1_bp
app.register_blueprint(api_v1_bp)

Versioning strategies:

StrategyExample
URL path/api/v1/users
HeaderAccept: application/vnd.myapp.v1+json
Query/api/users?version=1

URL prefix is simplest for tutorials and REST project.

Unified JSON Errors

python
@api_v1_bp.errorhandler(400)
def bad_request(e):
    return jsonify({"code": 400, "message": "bad request", "data": None}), 400

SPAs parse consistent shapes—see Responses and JSON API.

Cookies vs JWT for SPAs

AuthCORS notes
JWT in Authorization headerCommon for SPAs; set CORS allow_headers
Session cookieRequires supports_credentials=True and explicit origins—no wildcard
python
CORS(app, supports_credentials=True, origins=["https://app.example.com"])

Frontend:

javascript
fetch(url, { credentials: "include" });

OpenAPI Documentation (Concept)

Tools generate Swagger UI from Flask routes:

LibraryNotes
flask-smorestMarshmallow schemas + OpenAPI 3
flasggerDocstrings to Swagger
APIFairySimilar lightweight option

Spring teams compare with SpringDoc on Spring Boot track—same OpenAPI standard, different integration.

Minimal flasgger idea:

python
def list_posts():
    """List posts
    ---
    responses:
      200:
        description: OK
    """
    return jsonify([])

Not required for small projects—curl and pytest suffice early on.

Preflight Debugging

Browser Network tab shows:

text
OPTIONS /api/v1/posts → 200 with Access-Control-Allow-*
POST /api/v1/posts

If OPTIONS fails:

  • Flask route missing OPTIONS in methods (Flask-CORS usually handles)
  • Nginx blocking OPTIONS—allow in proxy config

FAQ

CORS error but Postman works?

CORS is browser-only—Postman ignores it; fix response headers for browsers.

Credentials not sent?

credentials: "include" on fetch and matching server supports_credentials.

Wildcard with Authorization?

Use explicit origin list when sending tokens.

Duplicate CORS headers?

Nginx and Flask both add headers—disable one layer.

WebSocket CORS?

Different mechanism—use flask-socketio CORS settings separately.

Mobile apps?

Native apps are not browsers—CORS does not apply; still use HTTPS and token auth.