Web Routing in myfy
myfy provides FastAPI-like routing with full DI integration.
Route Decorators
pythonfrom myfy.web import route @route.get("/path") async def handler() -> dict: return {"message": "hello"} @route.post("/path", status_code=201) async def create() -> dict: return {"created": True} @route.put("/path/{id}") async def update(id: int) -> dict: return {"updated": id} @route.delete("/path/{id}", status_code=204) async def delete(id: int) -> None: pass @route.patch("/path/{id}") async def partial_update(id: int) -> dict: return {"patched": id}
Path Parameters
Extract from URL template using {param}:
python@route.get("/users/{user_id}/posts/{post_id}") async def get_post(user_id: int, post_id: int) -> dict: return {"user": user_id, "post": post_id}
Path parameters are:
- Automatically type-converted based on annotation
- Must match function parameter names exactly
- Must be valid Python identifiers
Query Parameters
Use Query for explicit query parameters:
pythonfrom myfy.web import Query @route.get("/search") async def search( q: str = Query(default=""), # With default value limit: int = Query(default=10), # Integer query param page: int = Query(alias="p"), # Aliased (?p=1 in URL) ) -> dict: return {"query": q, "limit": limit, "page": page}
Request Body
Use Pydantic models or dataclasses for request bodies:
pythonfrom pydantic import BaseModel class UserCreate(BaseModel): email: str name: str @route.post("/users", status_code=201) async def create_user(body: UserCreate, session: AsyncSession) -> dict: user = User(**body.model_dump()) session.add(user) await session.commit() return {"id": user.id}
Request bodies are automatically:
- Parsed from JSON
- Validated by Pydantic
- Type-checked at runtime
Parameter Classification
Parameters are classified in this order:
- Path parameters - Names matching
{param}in route path - Query parameters - Annotated with
Query(...) - Body parameter - Pydantic model, dataclass, or dict
- DI dependencies - Everything else (resolved from container)
python@route.post("/users/{user_id}/orders") async def create_order( user_id: int, # 1. Path param (matches {user_id}) limit: int = Query(default=10), # 2. Query param (explicit Query) body: OrderCreate, # 3. Request body (Pydantic model) session: AsyncSession, # 4. DI dependency settings: AppSettings, # 4. DI dependency ) -> dict: ...
Authentication
Use Authenticated for protected routes:
pythonfrom myfy.web import Authenticated, AuthModule from dataclasses import dataclass @dataclass class User(Authenticated): email: str # Register auth provider def my_auth(request: Request) -> User | None: token = request.headers.get("Authorization") if not token: return None # Results in 401 return User(id="123", email="user@example.com") app.add_module(AuthModule(authenticated_provider=my_auth)) # Protected route - returns 401 if not authenticated @route.get("/profile") async def profile(user: User) -> dict: return {"id": user.id, "email": user.email}
Error Handling
Quick Errors with abort()
pythonfrom myfy.web import abort @route.get("/users/{user_id}") async def get_user(user_id: int, session: AsyncSession) -> dict: user = await session.get(User, user_id) if not user: abort(404, "User not found") return {"user": user}
Typed Errors
pythonfrom myfy.web import errors raise errors.NotFound("User not found") raise errors.BadRequest("Invalid email", field="email") raise errors.Unauthorized("Invalid token") raise errors.Forbidden("Access denied") raise errors.Conflict("Email already exists")
Custom Exceptions
pythonfrom myfy.web.exceptions import WebError class RateLimitExceeded(WebError): status_code = 429 error_type = "rate_limit_exceeded"
Rate Limiting
pythonfrom myfy.web.ratelimit import RateLimitModule, rate_limit, RateLimitKey # Add module app.add_module(RateLimitModule()) # Rate limit by IP (default) @route.get("/api/data") @rate_limit(100) # 100 requests per minute per IP async def get_data() -> dict: ... # Rate limit by authenticated user @route.get("/api/profile") @rate_limit(50, key=RateLimitKey.USER) async def get_profile(user: User) -> dict: ...
Response Types
Routes can return:
python# Dict (serialized to JSON) @route.get("/json") async def json_response() -> dict: return {"key": "value"} # Pydantic model (serialized to JSON) @route.get("/model") async def model_response() -> UserResponse: return UserResponse(id=1, name="John") # None for 204 No Content @route.delete("/users/{id}", status_code=204) async def delete_user(id: int) -> None: ...
Best Practices
- Always use async - All handlers should be async functions
- Type all parameters - Use type hints for auto-classification
- Use Pydantic for bodies - Get free validation
- Return typed responses - Prefer Pydantic models over dicts
- Use appropriate status codes - 201 for creation, 204 for deletion
- Handle errors explicitly - Use abort() or typed errors
- Document with docstrings - Add OpenAPI-compatible docs

