How-To Guides
Create a Controller with DI
Section titled “Create a Controller with DI”from lexigram.di import singletonfrom lexigram.web import Controller, get
@singletonclass UserService: async def list(self) -> list[dict]: return [{"id": 1, "name": "Ada"}]
class UserController(Controller): prefix = "/users"
def __init__(self, users: UserService) -> None: self.users = users
@get("/") async def list(self) -> list[dict]: return await self.users.list()Add CORS Middleware
Section titled “Add CORS Middleware”Configure CORS via application.yaml:
web: cors: allowed_origins: - "https://myapp.com" - "https://admin.myapp.com" allow_methods: - GET - POST - PUT - DELETE allow_credentials: trueOr override in code:
from lexigram.web.config import CORSConfigfrom lexigram.web import WebProvider
provider = WebProvider()provider.web_config.cors = CORSConfig( allowed_origins=["https://myapp.com"],)app.add_provider(provider)Add Input Sanitization Middleware
Section titled “Add Input Sanitization Middleware”from lexigram.web.middleware.sanitization import InputSanitizationMiddleware
app.add_provider(WebProvider(middleware=[InputSanitizationMiddleware]))Use WebModule.stub() in Tests
Section titled “Use WebModule.stub() in Tests”import pytestfrom lexigram import Applicationfrom lexigram.web import WebModulefrom lexigram.testing import WebTestBed
@pytest.mark.asyncioasync def test_health(): async with Application.boot( name="test", modules=[WebModule.stub()], ) as app: client = WebTestBed(app) response = await client.get("/health") assert response.status_code == 200Return Result Types from Handlers
Section titled “Return Result Types from Handlers”from lexigram.result import Result, Ok, Errfrom lexigram.web import Controller, getfrom lexigram.web.exceptions import NotFoundError
class UserController(Controller): prefix = "/users"
@get("/{user_id}") async def get(self, user_id: str) -> Result[dict, str]: if user_id == "0": return Err("User not found") return Ok({"id": user_id, "name": "Ada"})The ResultResponseMapper converts:
Ok(value)→ 200 JSONErr(str)→ 400Err(NotFoundError)→ 404Err(ValidationError)→ 422
Add Rate Limiting
Section titled “Add Rate Limiting”Configure in application.yaml:
web: rate_limit: enabled: true default_limit: 100 default_window: 60 rules: "/api/auth/login": requests: 10 window: 60 "/api/health": requests: 1000 window: 60Or use the @throttle decorator on individual handlers:
from lexigram.web import throttle, get
class UserController(Controller): @get("/login") @throttle(max_requests=10, window_seconds=60) async def login(self) -> dict: return {"status": "ok"}Serve Static Files
Section titled “Serve Static Files”web: static: enabled: true directory: ./public prefix: /staticAdd Custom Exception Handler
Section titled “Add Custom Exception Handler”from starlette.responses import JSONResponse
async def my_handler(request, exc): return JSONResponse({"error": str(exc)}, status_code=400)
app.add_provider(WebProvider(exception_handlers={ValueError: my_handler}))Add WebSocket Endpoint
Section titled “Add WebSocket Endpoint”from lexigram.web import Controller, websocketfrom lexigram.web.transport.websockets import WebSocket
class WsController(Controller): @websocket("/ws") async def handler(self, ws: WebSocket) -> None: await ws.accept() async for message in ws: await ws.send_text(f"Echo: {message}")Requires the websocket extra: uv add "lexigram-web[websocket]"