ESC

Type to search...

CORS

CORS is an HTTP concern — EVOID handles it in the adapter layer. You have two approaches: middleware in the adapter, or a processor that adds CORS headers.

Approach 1: Middleware in the Adapter

The simplest approach — add CORS middleware to your Starlette app:

from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.routing import Route

from evoid.core import Intent, execute


async def handle(request):
    intent = Intent(
        name=f"{request.method}:{request.url.path}",
        level=Level.STANDARD,
        metadata={"method": request.method, "path": request.url.path},
    )
    result = await execute(intent)
    if result.success:
        from starlette.responses import JSONResponse
        return JSONResponse(result.value)
    from starlette.responses import JSONResponse
    return JSONResponse({"error": str(result.error)}, status_code=500)


app = Starlette(
    routes=[Route("/{path:path}", handle, methods=["GET", "POST", "PUT", "DELETE"])],
    middleware=[
        Middleware(
            CORSMiddleware,
            allow_origins=["*"],
            allow_methods=["*"],
            allow_headers=["*"],
        )
    ],
)

Approach 2: Processor-Based CORS

For dynamic CORS policies (e.g., allow different origins per route), use a processor:

from evoid.core import Context


ALLOWED_ORIGINS = {
    "GET:/users": ["https://app.example.com"],
    "POST:/users": ["https://admin.example.com"],
    "*": ["https://any.example.com"],
}


async def cors_processor(ctx: Context) -> dict:
    method = ctx.metadata.get("method", "")
    path = ctx.metadata.get("path", "")
    headers = ctx.metadata.get("headers", {})
    origin = headers.get("origin", "")

    # Find matching CORS policy
    allowed = ALLOWED_ORIGINS.get(f"{method}:{path}")
    if allowed is None:
        allowed = ALLOWED_ORIGINS.get("*", [])

    if origin in allowed:
        ctx.state["cors_headers"] = {
            "Access-Control-Allow-Origin": origin,
            "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
            "Access-Control-Allow-Headers": "Content-Type, Authorization",
        }
    else:
        ctx.state["cors_headers"] = {}

    return {"cors_configured": True}

Attaching CORS Headers to Responses

If using the processor approach, you need an after processor to attach headers:

from evoid.core import Context
from evoid.web.route import Service, get, before, after

app = Service("api")

@app.get("/users")
async def get_users() -> dict:
    return {"users": []}

before("GET:/users", "cors_processor")
after("GET:/users", "attach_cors_headers")
from starlette.responses import JSONResponse


async def attach_cors_headers(ctx: Context) -> dict:
    cors = ctx.state.get("cors_headers", {})
    # Store headers for the adapter to include in response
    ctx.state["response_headers"] = cors
    return ctx.state.get("last_result", {})

@controller Style

from evoid.web.controller import Service, Controller, GET

app = Service("api")

@Controller("/data")
class DataController:
    @GET("/")
    async def get_data(self) -> dict:
        return {"data": [1, 2, 3]}

from evoid.web.controller import before
before("GET:/data/", "cors_processor")

Native IOP Style

from evoid import Intent, Level, add_intent

GET_DATA = Intent(
    name="get:data",
    level=Level.STANDARD,
)


async def handle_get_data(intent: Intent) -> dict:
    return {"data": [1, 2, 3]}


add_intent(GET_DATA, handle_get_data)

Comparison

ApproachProsCons
MiddlewareSimple, handles preflight, battle-testedFixed policy, runs on every request
ProcessorDynamic per-route, integrates with pipelineMore complex, manual preflight handling