CORS and Middleware in FastAPI

Learn how to configure CORS and middleware in FastAPI step by step. This lesson covers the core concepts, hands-on setup, troubleshooting, and what to explore next in the FastAPI Backend Development track.

Focus: adding cors and middleware configuration

Sponsored

Your FastAPI API is working locally, but the moment you point a React or Vue frontend to it, the browser slams the brakes. A dark red CORS error appears in the console, and your requests silently fail — even though your endpoints are perfectly fine. This lesson solves that pain and the related headache of global request handling by teaching you how to add CORS and middleware configuration in FastAPI, step by step, so you can connect frontends, log requests, compress responses, and even create your own middleware hooks.

The problem this lesson solves

Imagine finishing a beautiful FastAPI backend and then wiring up a SPA running on http://localhost:3000. Your API is on http://localhost:8000. You try to fetch data, but the browser refuses to show it. That’s Cross-Origin Resource Sharing (CORS) throwing a security fit. Browsers enforce the same-origin policy by default, blocking requests from a different origin — meaning different scheme, host, or port. Without explicit permission from your server, any frontend on another port is out of luck.

But CORS isn’t the only hidden issue. You also need to handle tasks that happen for every request — logging, timing, gzip compression, custom headers — and manually adding code to every endpoint is a maintenance nightmare. That’s where middleware enters the picture. This lesson shows you how to fix browser CORS errors and add reusable request/response processing in FastAPI, so your API is both open to legitimate clients and cleanly instrumented.

Core concept / mental model

Think of your FastAPI app as a building with one main entrance. Middleware is the security checkpoint and reception desk just inside that entrance. Every request — no matter which endpoint it’s headed to — must pass through this checkpoint first. The middleware can log it, modify it, reject it, or let it through. After the endpoint runs, the response comes back through the same checkpoint for optional post-processing.

CORS is a specific kind of HTTP header negotiation, not a separate server. When a browser makes a cross-origin request, it first sends a preflight OPTIONS request asking, "Hey, am I allowed to talk to this server?" The server must reply with headers like Access-Control-Allow-Origin. FastAPI implements CORS as a middleware that automatically adds these headers and handles preflight requests for you.

Pro tip — Visualize the request path: Browser → CORS middleware → your custom middleware (e.g., logging) → endpoint → response → back through middleware → browser. Middleware order matters because each layer wraps the next.

How it works step by step

  1. Install FastAPI with the standard extras (if you haven’t already): bash pip install "fastapi[all]" The all extra includes uvicorn for running the server and python-multipart for forms, which you might need later.

  2. Import the CORS middleware in your main app file: python from fastapi.middleware.cors import CORSMiddleware

  3. Define a list of allowed origins — the frontend URLs you trust, e.g., ["http://localhost:3000"].

  4. Add the middleware to your app instance using .add_middleware(): python app.add_middleware( CORSMiddleware, allow_origins=allowed_origins, allow_credentials=True, allow_methods=["*"], # all HTTP methods allow_headers=["*"], # all request headers )

  5. Understand the parameters: - allow_origins: list of exact origins (with scheme and port). - allow_credentials: set True if you send cookies or auth headers. - allow_methods: "*" allows all, or restrict to ["GET", "POST"]. - allow_headers: "*" allows all, or list specific headers like "Authorization".

  6. Create custom middleware using either a @app.middleware("http") decorator or a full BaseHTTPMiddleware class. The decorator version is cleaner for simple tasks like logging.

  7. Test with a real frontend or with curl to verify the headers are present (see hands-on below).

Hands-on walkthrough

Let’s build a minimal FastAPI app that enables CORS and adds a request‑logging middleware. Create a file named main.py:

from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
import time

app = FastAPI()

# 1. CORS configuration
allowed_origins = [
    "http://localhost:3000",    # React dev server
    "http://127.0.0.1:3000",    # alternative localhost
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=allowed_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 2. Custom middleware to log request duration
@app.middleware("http")
async def log_requests(request: Request, call_next):
    start = time.perf_counter()
    response = await call_next(request)   # pass request to the next layer
    duration = time.perf_counter() - start
    print(f"{request.method} {request.url.path} took {duration:.4f}s")
    return response

@app.get("/")
async def root():
    return {"message": "Hello World"}

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

Run the server:

uvicorn main:app --reload --port 8000

Now test the CORS headers with curl:

curl -i http://localhost:8000/data

You’ll see output like:

HTTP/1.1 200 OK
access-control-allow-origin: http://localhost:3000
access-control-allow-credentials: true
content-type: application/json
...

Pro tip — The access-control-allow-origin header appears even when the request is not from a browser, because FastAPI adds it unconditionally. That’s fine — the browser only enforces it when needed.

Now send a preflight request to simulate what a browser does:

curl -i -X OPTIONS http://localhost:8000/data \
  -H "Origin: http://localhost:3000" \
  -H "Access-Control-Request-Method: GET"

You should get a 200 OK with access-control-allow-* headers — CORS is properly configured.

Compare options / when to choose what

CORS comes in different flavors depending on your needs. Here’s a quick comparison:

Approach Use case Pros Cons
allow_origins=["*"] Public APIs with no auth Simplest Cannot combine with allow_credentials=True
Specific origins list Internal frontends or known domains Secure, works with auth Must update when deploying to new domains
Environment‑based origins Development vs. production No code changes for different envs Requires extra config management
BaseHTTPMiddleware Reusable, complex middleware logic Class‑based, state possible Slightly more boilerplate
@app.middleware("http") Simple logging or header injection Minimal, inline Harder to test in isolation

When to choose what: - For a dev frontend on localhost, hardcode a specific origin list. Never use "*" with credentials. - For production, load the allowed origins from an environment variable or a config file. - For custom middleware, start with the decorator for trivial tasks. If you need to manage state or want to reuse the middleware across projects, use a BaseHTTPMiddleware subclass.

Troubleshooting & edge cases

  • CORS error persists — Check that the Origin header in the request exactly matches an entry in allow_origins. http://localhost:3000 and http://127.0.0.1:3000 are different origins.
  • allow_origins=[""] doesn’t work with cookies — When allow_credentials=True, FastAPI requires a specific origin. Replace "" with the exact frontend origin.
  • Preflight returns 404 — Ensure your route is defined and that you didn’t accidentally replace FastAPI’s default OPTIONS handling. If you mounted a sub‑app, CORS middleware might be applied to the wrong app.
  • Middleware not executed — Middleware added after the app has started may not be picked up. Always call add_middleware before running the app.
  • Custom middleware blocks requests — If your middleware raises an exception before calling call_next, the endpoint never runs. Wrap the call in try/except to handle errors gracefully.
  • Double middleware logging — You might see duplicate logs if the same middleware is registered twice or if you have both a decorator and manual add_middleware. Check your imports and registration order.

What you learned & what's next

You now understand the core idea behind adding CORS and middleware configuration: you can control cross-origin access and intercept every request/response globally. You completed a practical exercise that sets up CORS headers, handles preflight requests, and adds a logging middleware. You also learned how to troubleshoot common pitfalls like origin mismatch and wildcards with credentials.

Next in the FastAPI Backend Development track, you’ll explore dependencies and dependency injection — a powerful way to share authentication, database sessions, and validation logic across many endpoints without repeating code. With your API now open to the frontend and covered by middleware, you’re ready to build more advanced features like user authentication and request-scoped resources.


Key takeaways from this lesson: - CORS is a browser security mechanism; FastAPI handles it via the CORSMiddleware. - Middleware runs for every request/response, making it ideal for logging, compression, and header injection. - Use specific allow_origins for production, never "*" when allow_credentials=True. - The @app.middleware("http") decorator is great for simple tasks; BaseHTTPMiddleware for complex, reusable logic. - Always register middleware before the app starts and test preflight requests with curl. - Middleware order matters — each layer wraps the next, so think about the execution sequence.

Practice recap

Create a new FastAPI app and add both CORS middleware (with two origins) and a custom middleware that adds a custom X-Process-Time header to every response, measuring the processing time. Then run uvicorn and use curl -i to verify both the CORS headers and your custom header appear. Test preflight with an OPTIONS request to confirm it returns 200.

Common mistakes

  • Using allow_origins=["*"] together with allow_credentials=True — FastAPI will throw an error because wildcard origins can't work with credentials. Use exact origins instead.
  • Forgetting to include the scheme and port in origins — localhost:3000 is not valid; you need http://localhost:3000.
  • Adding middleware after the app has already started (e.g., in a test or after the first request) — the middleware may not be applied, leading to confusion.
  • Assuming CORS errors are a server problem when the browser is caching a failed preflight response — clear browser cache or hard‑reload before debugging.
  • Restricting allow_methods to a list that doesn't include OPTIONS — though FastAPI adds it automatically, some reverse proxies may block it if not allowed.

Variations

  1. Use BaseHTTPMiddleware to create a reusable class-based middleware with custom initialization (e.g., for logging services or rate limiting).
  2. Load allowed origins from environment variables or a settings file using Pydantic settings, so you don't hard-code domains for different environments.
  3. Employ a third-party middleware like gzip-middleware for compression, or TrustedHostMiddleware from Starlette to validate the Host header against a fixed list.

Real-world use cases

  • Enabling a React or Vue frontend on a different port to call your FastAPI backend without CORS errors in development and production.
  • Adding structured request logging middleware to capture metrics (e.g., response time, status codes) for every API call in a microservices dashboard.
  • Implementing a security middleware that rejects requests from unauthorised origins or adds custom security headers (e.g., CSP) before the endpoint runs.

Key takeaways

  • CORS errors are browser-enforced; FastAPI uses CORSMiddleware to add the correct headers and handle preflight OPTIONS requests.
  • Middleware in FastAPI wraps every request/response, making it perfect for logging, compression, and custom header injection.
  • Always set allow_origins to the exact frontend origin list — never "*" when allow_credentials=True.
  • Order matters: middleware added earlier wraps later ones, so think about the execution sequence when combining CORS and custom middleware.
  • Use the @app.middleware("http") decorator for simple tasks, and BaseHTTPMiddleware for reusable, stateful logic.
  • Testing with curl with an Origin header lets you verify CORS headers without needing a full browser session.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.