Skip to main content

CORS & REST APIs

When your frontend (running on one domain) needs to call your API (running on another domain), you encounter CORS. Understanding CORS and REST conventions is essential for building web applications that actually work in a browser.

What is CORS?

Cross-Origin Resource Sharing (CORS) is a browser security mechanism. When JavaScript on https://myapp.com tries to fetch data from https://api.myapp.com, the browser blocks the request unless the API explicitly allows it.

The Same-Origin Policy

By default, browsers only allow requests to the same origin (protocol + domain + port). A request from https://myapp.com to https://api.myapp.com is cross-origin because the domains differ.

How CORS Works

  1. The browser sends a preflight request (OPTIONS) before the actual request
  2. The server responds with CORS headers indicating what is allowed
  3. If the headers permit the request, the browser sends the actual request
  4. If not, the browser blocks it and you see a CORS error in the console
code
Browser API Server
| |
| OPTIONS /data (preflight) |
| Origin: https://myapp.com |
| Access-Control-Request-Method: POST |
| ------------------------------------>|
| |
| 204 No Content |
| Access-Control-Allow-Origin: |
| https://myapp.com |
| Access-Control-Allow-Methods: |
| GET, POST, PUT, DELETE |
| Access-Control-Allow-Headers: |
| Content-Type, Authorization |
| <------------------------------------|
| |
| POST /data (actual request) |
| ------------------------------------>|
| 200 OK |
| <------------------------------------|

Configuring CORS in FastAPI

python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# Development: allow all origins
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allow all domains
allow_credentials=True,
allow_methods=["*"], # Allow all HTTP methods
allow_headers=["*"], # Allow all headers
)

# Production: allow specific origins only
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://myapp.com",
"https://www.myapp.com",
],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
Never use allow_origins=["*"] in production

Wildcard origins with allow_credentials=True is a security risk. Always specify exact origins in production. If you need credentials (cookies, auth headers), the browser requires specific origins — it will not accept *.

REST API Conventions

REST (Representational State Transfer) is a set of conventions for designing APIs. Following REST makes your API intuitive and predictable.

Resource-Based URLs

text
GET /api/v1/users → List all users
GET /api/v1/users/42 → Get user 42
POST /api/v1/users → Create a new user
PUT /api/v1/users/42 → Replace user 42 entirely
PATCH /api/v1/users/42 → Update user 42 partially
DELETE /api/v1/users/42 → Delete user 42

GET /api/v1/users/42/orders → List orders for user 42
POST /api/v1/users/42/orders → Create order for user 42

HTTP Status Codes

Use the correct status code to communicate the result:

CodeMeaningWhen to Use
200OKSuccessful GET, PUT, PATCH
201CreatedSuccessful POST (resource created)
204No ContentSuccessful DELETE
400Bad RequestInvalid input, missing fields
401UnauthorizedMissing or invalid authentication
403ForbiddenAuthenticated but not authorized
404Not FoundResource does not exist
409ConflictDuplicate resource
422Unprocessable EntityValidation error
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server failure

Pagination Pattern

python
from fastapi import FastAPI, Query
from typing import Optional

app = FastAPI()

@app.get("/api/v1/items")
def list_items(
page: int = Query(1, ge=1),
per_page: int = Query(20, ge=1, le=100),
search: Optional[str] = None,
):
# Calculate offset
offset = (page - 1) * per_page

# Query database
items = get_items(offset=offset, limit=per_page, search=search)
total = count_items(search=search)

return {
"items": items,
"pagination": {
"page": page,
"per_page": per_page,
"total": total,
"total_pages": (total + per_page - 1) // per_page,
}
}
API versioning

Prefix your API paths with a version number (/api/v1/). When you need to make breaking changes, create /api/v2/ while keeping v1 running. This prevents breaking existing clients.