Handlers

Handlers are where you implement the business logic for your APIs, events, and cron jobs.

Handler Naming Convention

Handler files must be named exactly as the API/Event/Cron name in the schema:

  • API Healthsrc/handlers/api/Health.ts or Health.py
  • Event UserCreated → Handler defined in event schema
  • Cron DailyCleanupsrc/handlers/cron/DailyCleanup.ts or DailyCleanup.py

Handler Signatures

API Handlers

API 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

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

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()})

Request Object Structure

API Request Properties

The request object (*Request) contains:

  • Path Parameters: Extracted from route patterns (e.g., :id in /users/:id)

    • Accessible as properties: req.id, req.userId, etc.
  • Query Parameters: URL query string parameters

    • Python: req.query_params (Dict[str, str])
    • TypeScript: req.queryParams (Record of string to string)
  • Body: Request body (if specified in schema)

    • Python: req.body (typed object)
    • TypeScript: 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 }; }

State Object

The State object provides access to runtime features:

Logging

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")

Event Triggering

Manual Event Triggering

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() })

Auto-Triggered Events

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 }; }

Generated Types

All types are auto-generated in src/generated/ when you run rohas codegen. Do not edit these files manually.

The generated types include:

  • Request/Response types for APIs
  • Event payload types
  • State and Logger classes
  • Handler function signatures