Skip to content

CORS MiddlewareΒΆ

Cross-Origin Resource Sharing (CORS) controls which external domains can make requests to your API. Cello's CORS middleware is implemented in Rust and handles preflight OPTIONS requests automatically.

Quick StartΒΆ

from cello import App

app = App()

# Allow all origins (development)
app.enable_cors()

# Allow specific origins (production)
app.enable_cors(origins=["https://example.com", "https://app.example.com"])

How CORS WorksΒΆ

When a browser makes a cross-origin request, it follows this flow:

Browser                         Cello Server
  β”‚                                  β”‚
  │── Preflight OPTIONS ──────────→  β”‚
  β”‚                                  β”‚ ← Rust CORS middleware checks origin
  │←── 200 + CORS headers ────────  β”‚
  β”‚                                  β”‚
  │── Actual GET/POST request ────→  β”‚
  β”‚                                  β”‚ ← CORS headers added to response
  │←── Response + CORS headers ───  β”‚

Preflight Requests

Browsers send a preflight OPTIONS request for non-simple requests (those with custom headers, non-standard methods, or JSON content type). Cello handles these automatically in Rust without reaching your Python handler.


ConfigurationΒΆ

Default ConfigurationΒΆ

# Allows all origins, all standard methods, and common headers
app.enable_cors()

Default behavior:

Setting Default
Origins ["*"] (all origins)
Methods GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD
Headers Content-Type, Authorization, Accept, Origin, X-Requested-With
Credentials false
Max Age 86400 seconds (24 hours)

Restricted OriginsΒΆ

# Only allow specific domains
app.enable_cors(origins=[
    "https://example.com",
    "https://app.example.com",
    "https://admin.example.com"
])

Wildcard and Credentials

When origins is set to ["*"], browsers will not send cookies or authorization headers. If your API requires credentials, you must list specific origins.


Preflight HandlingΒΆ

Cello automatically responds to OPTIONS preflight requests in Rust. The response includes:

  • Access-Control-Allow-Origin -- the matched origin or *
  • Access-Control-Allow-Methods -- allowed HTTP methods
  • Access-Control-Allow-Headers -- allowed request headers
  • Access-Control-Max-Age -- how long browsers should cache the preflight result
OPTIONS /api/users HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

Common PatternsΒΆ

API with Frontend SPAΒΆ

app = App()

# Allow the SPA origin
app.enable_cors(origins=["https://myapp.example.com"])

@app.get("/api/data")
def get_data(request):
    return {"items": [1, 2, 3]}

Public APIΒΆ

app = App()

# Allow any origin to call the API
app.enable_cors()

@app.get("/api/public/status")
def status(request):
    return {"status": "ok"}

Multiple EnvironmentsΒΆ

import os

app = App()

if os.environ.get("ENV") == "production":
    app.enable_cors(origins=[
        "https://app.example.com",
        "https://admin.example.com"
    ])
else:
    # Development -- allow everything
    app.enable_cors()

Blueprint-Level CORSΒΆ

Apply CORS to specific route groups using blueprints:

from cello import Blueprint

# Public API -- open CORS
public = Blueprint("/api/public")

# Internal API -- no CORS (same-origin only)
internal = Blueprint("/api/internal")

app.register_blueprint(public)
app.register_blueprint(internal)

# CORS only applies to public routes when configured at the blueprint level

PerformanceΒΆ

CORS processing in Cello is extremely efficient:

Operation Time
Origin check ~10ns
Preflight response ~50ns
Header injection ~20ns

Preflight responses are handled entirely in Rust and never reach your Python handlers.


Next StepsΒΆ