web-controllers-and-routing
Skill1 starsChanged 3 months agoMITView on GitHub
Use when creating HTTP endpoints, controllers, middleware, or web routes in the Lexigram framework
In this repository17 other files it gives its agents.
- ai-subsystem-quickstartSkill
- auth-and-securitySkill
- caching-patternsSkill
- cli-code-generationSkill
- cli-config-and-inspectSkill
- cli-database-operationsSkill
- cli-project-scaffoldingSkill
- configuration-managementSkill
- creating-providers-and-modulesSkill
- database-repository-patternSkill
- events-and-messagingSkill
- lexigram-uiSkill
- multimedia-generationSkill
- real-time-webSkill
- resilience-patternsSkill
- testing-with-lexigramSkill
- using-result-and-error-codesSkill
The file
Files
Web Controllers and Routing
Overview
Lexigram-web provides an async-first web layer on Starlette. Controllers group related routes via decorators, with full DI injection and middleware guards.
Controller Pattern
from lexigram.web import Controller, get, post, put, delete
from starlette.requests import Request # framework reuses Starlette's Request
class UserController(Controller):
def __init__(self, repo: UserRepositoryProtocol):
self.repo = repo
@get("/users")
async def list_all(self, request: Request):
result = await self.repo.find_all()
return {"users": [u.to_dict() for u in result]}
@get("/users/{user_id}")
async def get_one(self, request: Request, user_id: str):
result = await self.repo.find(user_id)
return result.match(
ok=lambda u: {"user": u.to_dict()},
err=lambda e: ({"error": str(e)}, 404),
)
@post("/users")
async def create(self, request: Request):
data = await request.json()
result = await self.repo.create(data)
return result.match(
ok=lambda u: ({"user": u.to_dict()}, 201),
err=lambda e: ({"error": str(e)}, 400),
)
@delete("/users/{user_id}")
async def delete(self, user_id: str):
await self.repo.delete(user_id)
return {"ok": True}Guards and Middleware
from lexigram.web.security import use_guards, AuthGuard
from lexigram.contracts.web.guard import GuardProtocol
class AdminGuard(GuardProtocol):
async def can_activate(self, context) -> bool:
user = getattr(context.state, "user", None)
return user is not None and "admin" in user.roles
@use_guards(AuthGuard, AdminGuard)
@get("/admin/dashboard")
async def dashboard(self, request: Request):
return {"data": "sensitive"}Module Registration
from lexigram.web import WebModule
class AppModule(Module):
@classmethod
def configure(cls) -> DynamicModule:
return WebModule.configure(
controllers=[UserController],
host="0.0.0.0",
port=8000,
)Controllers can also return Result[T, E] directly — Err(NotFoundError) maps to 404, ValidationError to 422, ConflictError to 409, custom codes via @error_status(ErrorClass, status) (lexigram.web.error_status). Manual (body, status_code) tuples are just the explicit alternative.
Quick Reference
| Decorator | Method | Path Example |
@get(path) | GET | @get("/users/{id}") |
@post(path) | POST | @post("/users") |
@put(path) | PUT | @put("/users/{id}") |
@delete(path) | DELETE | @delete("/users/{id}") |
@patch(path) | PATCH | @patch("/users/{id}") |
@use_guards(*guards) | any | @use_guards(AuthGuard) |
Content Security Policy
CSP flows through SecurityConfig.csp: CSPConfig (a directives dict) into SecurityHeadersMiddleware(app, config=..., enabled=...):
from lexigram.web.security import SecurityHeadersMiddleware, SecurityConfig, CSPConfig
from lexigram.web import WebProvider, WebConfig
sec_config = SecurityConfig(
csp=CSPConfig(
enabled=True,
directives={
"default-src": {"'self'"},
"script-src": {"'self'", "https://unpkg.com"},
"style-src": {"'self'", "https://cdn.tailwindcss.com"},
"img-src": {"'self'", "data:"},
"connect-src": {"'self'", "ws://localhost:8000"},
},
)
)
class AppModule(Module):
@classmethod
def configure(cls) -> DynamicModule:
return DynamicModule(
module=cls,
providers=[
WebProvider(
controllers=[UserController],
middleware=[SecurityHeadersMiddleware],
web_config=WebConfig(security=sec_config),
),
],
)Server binding (host/port) lives in WebConfig(server=ServerConfig(...)) — WebProvider itself takes no host/port kwargs; pass web_config= or use WebModule.configure(host=..., port=...).
Common Mistakes
- Blocking I/O in async handlers — use
awaiton all calls - Returning exceptions directly — return
(body, status_code)tuple - Forgetting to register controllers in
WebModule.configure() - Business logic in controllers — delegate to services
- Missing
CSPPolicyon HTMX/SSE endpoints — inline event handlers blocked by default - Overly permissive
script-src: 'unsafe-inline'— defeats CSP purpose
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

