HTTP controllers
An HTTP controller organizes the logic of one or several endpoints. When the router finds a match and middleware allow execution to continue, Orionis constructs the controller through the container, invokes its action and processes the returned response.
Keeping actions in controllers lets route files concentrate on endpoint declarations while services handle business logic.
Resource and Invokable are two controller structures. --api is an independent option that changes response annotations and can be used with either structure.
Generating a controller
Section titled “Generating a controller”From the project root, with its Python environment active, use the direct command:
orionis make:http-controller ExampleControllerOr its alternative through the local reactor file:
python -B reactor make:http-controller ExampleControllerThe syntax is make:http-controller name [--invoke] [--api]:
| Argument or option | Required | Default | Effect |
|---|---|---|---|
name | Yes | No value | Name used to construct the file and class. |
--invoke | No | False | Generate only the __call__ action instead of the seven Resource actions. |
--api | No | False | Import JSONResponse and use it in return annotations and docstrings. |
-h or --help display help. The --invoke and --api options can be combined.
Location and name normalization
Section titled “Location and name normalization”The default destination is app/http/controllers, obtained from the app_http_controllers application path. Customizing that path changes the generator’s destination.
name | Generated class | Project-relative file |
|---|---|---|
ExampleController | ExampleController | app/http/controllers/examplecontroller.py |
UserController | UserController | app/http/controllers/usercontroller.py |
user_controller or user | UserController | app/http/controllers/user_controller.py |
admin/user | UserController | app/http/controllers/admin/user_controller.py |
The filename is lowercase and gains _controller when the suffix is missing. The class is formed from underscore-separated segments and gains Controller when missing. PascalCase words are not separated with underscores: UserController produces usercontroller.py, not user_controller.py.
Subdirectories with / or \ are supported, normalized to lowercase, as is a final extension such as .py. Directories are created automatically. Absolute paths, .. segments and names containing hyphens are rejected.
The generator does not overwrite existing files and provides no --force option. The following variants are alternatives: use different names to keep several, or edit the file already generated.
Resource Controllers
Section titled “Resource Controllers”A Resource Controller groups actions related to a resource. Use it when listing, forms, creation, updates and deletion belong to the same entity.
This is the default structure:
orionis make:http-controller UserControllerLocal alternative:
python -B reactor make:http-controller UserControllerGenerated code
Section titled “Generated code”The app/http/controllers/usercontroller.py file contains:
from orionis.http import HttpResponsefrom orionis.http.base import BaseController
class UserController(BaseController): """ Handle HTTP requests through asynchronous controller actions.
Resource actions are generated by default. Use the invokable option to generate only ``__call__`` and the API option to annotate JSON responses. """
async def index(self) -> HttpResponse: """Display a listing of the resource.
Returns ------- HttpResponse Response produced when this action is implemented. """
async def create(self) -> HttpResponse: """Show the form for creating a resource.
Returns ------- HttpResponse Response produced when this action is implemented. """
async def store(self) -> HttpResponse: """Store a newly created resource.
Returns ------- HttpResponse Response produced when this action is implemented. """
async def show(self) -> HttpResponse: """Display the specified resource.
Returns ------- HttpResponse Response produced when this action is implemented. """
async def edit(self) -> HttpResponse: """Show the form for editing the resource.
Returns ------- HttpResponse Response produced when this action is implemented. """
async def update(self) -> HttpResponse: """Update the specified resource.
Returns ------- HttpResponse Response produced when this action is implemented. """
async def destroy(self) -> HttpResponse: """Remove the specified resource.
Returns ------- HttpResponse Response produced when this action is implemented. """BaseController, imported from orionis.http.base, is an empty marker base class: it does not implement CRUD, validation, authorization or data access. HttpResponse is the framework’s type alias for Response.
All actions are asynchronous, receive only self and are annotated with HttpResponse. The generator does not automatically add Request, identifiers or dependencies.
| Action | Purpose described by the template |
|---|---|
index | List the resource. |
create | Show the creation form. |
store | Store a new resource. |
show | Show a particular resource. |
edit | Show the editing form. |
update | Update a particular resource. |
destroy | Remove a particular resource. |
These names organize responsibilities; their bodies do not perform those operations until implemented.
Implementing actions and registering routes
Section titled “Implementing actions and registering routes”To try two read actions, replace the example file with this minimal implementation:
from orionis.http import HttpResponse, responsefrom orionis.http.base import BaseController
class UserController(BaseController): async def index(self) -> HttpResponse: return response.json({"users": []})
async def show(self, user_id: int) -> HttpResponse: return response.json({"id": user_id})In the file registered as API, usually routes/api.py:
from app.http.controllers.usercontroller import UserControllerfrom orionis.support.facades import Route
Route.get("/users", [UserController, "index"])Route.get("/users/{user_id:int}", [UserController, "show"])GET /users returns {"users": []}; GET /users/42 returns {"id": 42}. The second action adds user_id to its signature to receive the route parameter.
The [Class, "action"] pair identifies the method without executing it. A tuple is also supported. Pass the class without instantiating it.
There is no automatic Resource route registration. The generator does not associate HTTP verbs with actions: this example chooses GET for both reads. Declare other routes explicitly with the HTTP methods appropriate for your implemented actions. See Routing.
Invokable Controllers
Section titled “Invokable Controllers”An Invokable Controller concentrates one operation in __call__. Use it for an endpoint with a specific responsibility, without grouping several actions in the class.
orionis make:http-controller UserController --invokeLocal alternative:
python -B reactor make:http-controller UserController --invokeGenerated code
Section titled “Generated code”In this variant, app/http/controllers/usercontroller.py contains only one action:
from orionis.http import HttpResponsefrom orionis.http.base import BaseController
class UserController(BaseController): """ Handle HTTP requests through asynchronous controller actions.
Resource actions are generated by default. Use the invokable option to generate only ``__call__`` and the API option to annotate JSON responses. """
async def __call__(self) -> HttpResponse: """Handle the request for this controller.
Returns ------- HttpResponse Response produced when this action is implemented. """__call__ is the method the router selects when registering an invokable class directly. The skeleton receives only self; route parameters and dependencies can be added as for any action.
Implementing and registering the single action
Section titled “Implementing and registering the single action”Replace the skeleton with an implementation that returns a response:
from orionis.http import HttpResponse, responsefrom orionis.http.base import BaseController
class UserController(BaseController): async def __call__(self) -> HttpResponse: return response.text("User endpoint ready")In the route file:
from app.http.controllers.usercontroller import UserControllerfrom orionis.support.facades import Route
Route.get("/user-summary", UserController)GET /user-summary executes __call__() and returns the text with status 200. [UserController, "__call__"] is also supported, although passing the class expresses the same intent more briefly. The router does not accept an instance or bound method as an action.
Controllers with —api
Section titled “Controllers with —api”--api replaces the HttpResponse import with JSONResponse, changes return annotations and the type mentioned in docstrings. It adds no response code, serialization, routes or persistence.
The structure still depends on --invoke: without that option, all seven Resource actions remain, including create and edit; with it, only __call__ is generated.
Resource Controller for API
Section titled “Resource Controller for API”orionis make:http-controller UserController --apiLocal alternative:
python -B reactor make:http-controller UserController --apiThe complete generated code is:
from orionis.http import JSONResponsefrom orionis.http.base import BaseController
class UserController(BaseController): """ Handle HTTP requests through asynchronous controller actions.
Resource actions are generated by default. Use the invokable option to generate only ``__call__`` and the API option to annotate JSON responses. """
async def index(self) -> JSONResponse: """Display a listing of the resource.
Returns ------- JSONResponse Response produced when this action is implemented. """
async def create(self) -> JSONResponse: """Show the form for creating a resource.
Returns ------- JSONResponse Response produced when this action is implemented. """
async def store(self) -> JSONResponse: """Store a newly created resource.
Returns ------- JSONResponse Response produced when this action is implemented. """
async def show(self) -> JSONResponse: """Display the specified resource.
Returns ------- JSONResponse Response produced when this action is implemented. """
async def edit(self) -> JSONResponse: """Show the form for editing the resource.
Returns ------- JSONResponse Response produced when this action is implemented. """
async def update(self) -> JSONResponse: """Update the specified resource.
Returns ------- JSONResponse Response produced when this action is implemented. """
async def destroy(self) -> JSONResponse: """Remove the specified resource.
Returns ------- JSONResponse Response produced when this action is implemented. """To implement read actions with explicit JSON responses, edit the file:
from orionis.http import JSONResponse, responsefrom orionis.http.base import BaseController
class UserController(BaseController): async def index(self) -> JSONResponse: return response.json({"users": []})
async def show(self, user_id: int) -> JSONResponse: return response.json({"id": user_id})Register them in routes/api.py using the same class and action pairs:
from app.http.controllers.usercontroller import UserControllerfrom orionis.support.facades import Route
Route.get("/users", [UserController, "index"])Route.get("/users/{user_id:int}", [UserController, "show"])Invokable Controller for API
Section titled “Invokable Controller for API”The options can be combined without restrictions between them:
orionis make:http-controller UserController --invoke --apiLocal alternative:
python -B reactor make:http-controller UserController --invoke --apiThe complete result is:
from orionis.http import JSONResponsefrom orionis.http.base import BaseController
class UserController(BaseController): """ Handle HTTP requests through asynchronous controller actions.
Resource actions are generated by default. Use the invokable option to generate only ``__call__`` and the API option to annotate JSON responses. """
async def __call__(self) -> JSONResponse: """Handle the request for this controller.
Returns ------- JSONResponse Response produced when this action is implemented. """A functional implementation of that single action would be:
from orionis.http import JSONResponse, responsefrom orionis.http.base import BaseController
class UserController(BaseController): async def __call__(self) -> JSONResponse: return response.json({"status": "ready"})In routes/api.py:
from app.http.controllers.usercontroller import UserControllerfrom orionis.support.facades import Route
Route.get("/user-summary", UserController)This endpoint returns JSON with status 200. Resource and Invokable examples are alternatives; register a single action per HTTP verb and path combination.
Annotations, responses and API context
Section titled “Annotations, responses and API context”A JSONResponse annotation does not itself convert the returned value. The examples construct responses through response.json(...).
Separately, the kernel accepts dict or msgspec.Struct results and converts them into JSONResponse with status 200, independently of --api and the annotation. It does not convert arbitrary values: construct a text response with response.text(...), for example. See HTTP responses.
API or WEB context comes from app.withRouting(...) configuration, rather than --api, the controller name or its annotation. A controller generated without --api can also return JSON, as the first Resource example does. See Separating API and WEB routes.
Comparing variants
Section titled “Comparing variants”Arguments in the table follow orionis make:http-controller or python -B reactor make:http-controller. The type is the expected return once the action is implemented, rather than a response already created by the skeleton.
| Structure | Arguments | Generated actions | Return annotation | Typical use | Route registration |
|---|---|---|---|---|---|
| Resource | UserController | All seven resource actions. | HttpResponse | Several operations for one resource. | [UserController, "index"] |
| Invokable | UserController --invoke | __call__ | HttpResponse | One specific operation. | UserController |
| Resource with API | UserController --api | The same seven actions. | JSONResponse | Several operations returning JSON. | [UserController, "index"] |
| Invokable with API | UserController --invoke --api | __call__ | JSONResponse | One operation returning JSON. | UserController |
Parameters and dependency injection
Section titled “Parameters and dependency injection”When handling a route, Orionis constructs the controller class and resolves its constructor’s annotated dependencies. It then invokes the action, supplying route parameters by name and resolving its remaining dependencies. Preparing metadata during startup does not execute ordinary constructors or actions.
This example uses two concrete services without dependencies, which the container can construct automatically. Save app/services/user_label_service.py:
import logging
class UserLabelService: def label(self, user_id: int) -> str: return f"User {user_id}"
class UserAuditService: def record_view(self, user_id: int, path: str) -> None: logging.getLogger(__name__).info("Viewing user %s at %s", user_id, path)In app/http/controllers/show_user_controller.py:
from app.services.user_label_service import UserAuditService, UserLabelServicefrom orionis.http import JSONResponse, Request, responsefrom orionis.http.base import BaseController
class ShowUserController(BaseController): def __init__(self, labels: UserLabelService) -> None: self.labels = labels
async def __call__( self, request: Request, user_id: int, audit: UserAuditService, ) -> JSONResponse: audit.record_view(user_id, request.path) return response.json({ "id": user_id, "label": self.labels.label(user_id), "path": request.path, })Register the class in routes/api.py, replacing the equivalent declaration if already using that path:
from app.http.controllers.show_user_controller import ShowUserControllerfrom orionis.support.facades import Route
Route.get("/users/{user_id:int}", ShowUserController)GET /users/42 returns {"id": 42, "label": "User 42", "path": "/users/42"}.
labels: UserLabelServicelets the container construct and inject the service into the constructor. Abstract services or contracts need their corresponding binding.audit: UserAuditServiceresolves when invoking this action. The client does not send it in the URL or payload: it is a server dependency.request: Requestobtains the current request registered in the HTTP scope; you do not construct it manually.user_idarrives as an integer through the{user_id:int}converter. Accept that name in the action, including when receivingRequest.- Annotations identify dependencies, while the converter declared in the route determines segment conversion. Adding
: intonly to the signature does not change the route pattern. Responseis the action’s return value; the controller does not automatically receive a pre-created response.
Use the constructor for services shared by several actions of a class and method parameters for services needed by one action. The same mechanism applies to Resource actions: if you rename __call__ to show, register [ShowUserController, "show"]. Constructor dependencies resolve first, followed by action arguments; route values are supplied to the action, rather than the constructor.
This example needs no bindings because both services are concrete and auto-constructible. To share the same instance between resolutions, configure its lifetime. Injection happens when Orionis constructs and invokes the controller; a direct Python call such as controller.show(...) requires you to supply its arguments.
Organization and considerations
Section titled “Organization and considerations”Group controllers within app/http/controllers by responsibility or domain as the project grows; for example, admin/user generates a class inside admin. Use consistent names, Resource for a resource’s actions and Invokable for specific operations. These are organizational recommendations, rather than router restrictions.
Keep HTTP coordination in controllers and delegate business logic to services. The base class does not perform database operations or automatically implement generated actions.
Middleware execute before invocation and may prevent it. If an action fails, HTTP layers and the kernel delegate handling to the exception system. For formats, headers, cookies or downloads, see Responses.