Handlers are where you implement the business logic for your APIs, events, and cron jobs.
Handler files must be named exactly as the API/Event/Cron name in the schema:
Health → src/handlers/api/Health.ts or Health.pyUserCreated → Handler defined in event schemaDailyCleanup → src/handlers/cron/DailyCleanup.ts or DailyCleanup.pyAPI handlers receive a request object and optionally a State object:
from generated.api.GetUser import GetUserRequest, GetUserResponse
from generated.state import State
async def handle_get_user(req: GetUserRequest, state: State) -> GetUserResponse:
# Access path parameters
user_id = req.id # From path: /users/:id
# Access query parameters
include_posts = req.query_params.get("include_posts", "false")
# Access request body (if present)
if hasattr(req, 'body'):
body_data = req.body
# Use state for logging and events
state.logger.info(f"Fetching user {user_id}")
return GetUserResponse(data=user)Event handlers receive an event object and optionally a State object:
from generated.events.UserCreated import UserCreated
from generated.state import State
async def handle_user_created(event: UserCreated, state: State) -> None:
# Access event payload
user_id = event.payload.id
email = event.payload.email
# Log and trigger additional events
state.logger.info(f"Processing user created: {user_id}")
state.trigger_event("WelcomeEmailSent", {"email": email})Cron handlers receive a request object (usually empty) and optionally a State object:
from generated.state import State
async def handle_daily_cleanup(req, state: State) -> None:
state.logger.info("Starting daily cleanup")
# Cleanup logic
state.trigger_event("CleanupCompleted", {"timestamp": datetime.now().isoformat()})The request object (*Request) contains:
Path Parameters: Extracted from route patterns (e.g., :id in /users/:id)
req.id, req.userId, etc.Query Parameters: URL query string parameters
req.query_params (Dict[str, str])req.queryParams (Record of string to string)Body: Request body (if specified in schema)
req.body (typed object)req.body (typed object)Example with path and query parameters:
api GetUser {
method: GET
path: "/users/:id"
response: User
}import { GetUserRequest, GetUserResponse } from "../generated/api/GetUser"; export async function handler( req: GetUserRequest, state: State ): Promise<GetUserResponse> { // Path parameter from /users/:id const userId = req.id; // e.g., "123" from /users/123 // Query parameters from ?include_posts=true&format=json const includePosts = req.queryParams?.include_posts || "false"; const formatType = req.queryParams?.format || "json"; // Use parameters const user = fetchUser(userId, { includePosts: includePosts === "true" }); return { data: user }; }
The State object provides access to runtime features:
Use state.logger for structured logging:
from generated.state import State
async def handler(req, state: State):
# Log levels
state.logger.info("Information message")
state.logger.error("Error message")
state.logger.warning("Warning message")
state.logger.warn("Warning message (alias)")
state.logger.debug("Debug message")
state.logger.trace("Trace message")
# With additional fields
state.logger.info("User action", user_id=123, action="login")Use trigger_event() for events NOT defined in the schema's triggers list:
from generated.state import State
async def handler(req, state: State):
# Trigger an event manually
state.trigger_event("CustomEvent", {
"data": "value",
"timestamp": datetime.now().isoformat()
})Use set_payload() for events defined in the schema's triggers list:
api CreateUser {
method: POST
path: "/users"
body: CreateUserInput
response: User
triggers: [UserCreated, WelcomeEmailSent]
}from generated.api.GetUserPosts import GetUserPostsRequest, GetUserPostsResponse
from generated.state import State
async def handle_get_user_posts(
req: GetUserPostsRequest,
state: State
) -> GetUserPostsResponse:
# Path parameter: /users/:userId/posts
user_id = req.userId
# Query parameters: ?limit=10&offset=0&sort=created_at
limit = int(req.query_params.get("limit", "10"))
offset = int(req.query_params.get("offset", "0"))
sort_by = req.query_params.get("sort", "created_at")
# Logging
state.logger.info(f"Fetching posts for user {user_id}", {
"limit": limit,
"offset": offset
})
# Business logic
posts = fetch_user_posts(user_id, limit=limit, offset=offset, sort_by=sort_by)
# Trigger analytics event
state.trigger_event("PostsViewed", {
"user_id": user_id,
"count": len(posts)
})
return GetUserPostsResponse(data=posts)import { GetUserPostsRequest, GetUserPostsResponse } from "../generated/api/GetUserPosts"; import { State } from "../generated/state"; export async function handler( req: GetUserPostsRequest, state: State ): Promise<GetUserPostsResponse> { // Path parameter: /users/:userId/posts const userId = req.userId; // Query parameters: ?limit=10&offset=0&sort=created_at const limit = parseInt(req.queryParams?.limit || "10"); const offset = parseInt(req.queryParams?.offset || "0"); const sortBy = req.queryParams?.sort || "created_at"; // Logging state.logger.info(`Fetching posts for user ${userId}`, { limit, offset }); // Business logic const posts = fetchUserPosts(userId, { limit, offset, sortBy }); // Trigger analytics event state.triggerEvent("PostsViewed", { userId, count: posts.length }); return { data: posts }; }
All types are auto-generated in src/generated/ when you run rohas codegen. Do not edit these files manually.
The generated types include: