agentleFS
dbtinoy-dbtinoy-/lexigram-framework-skills/web-controllers-and-routing/SKILL.md

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

The file

Files
SKILL.md

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

DecoratorMethodPath 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 await on 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 CSPPolicy on HTMX/SSE endpoints — inline event handlers blocked by default
  • Overly permissive script-src: 'unsafe-inline' — defeats CSP purpose

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.