Routing {#basic-routing}#
๐ฅ Hot Tips
- You don't need
app = Tina4()in route files - just importget,post, etc. directly - All route handlers must be
async def- Tina4 is 100% async-native - Path parameters are auto-injected in the exact order they appear in the URL
- Use type hints:
{id:int},{price:float},{path:path}- Tina4 converts them automatically requestandresponseare automatically added as the last arguments if you don't declare them- Save files in a
routes/folder โ auto-discovered, zero config needed - Stack decorators freely:
@get("/users") @post("/users")works on the same function - Use
@describe("...")for beautiful Swagger docs
The routing system in Tina4 Python is decorator-driven, fully async-ready, and designed for clarity and speed. Routes are defined using imported method decorators directly, with no app instance required in route files.
Core Imports#
from tina4_python.Router import get, post, put, delete, patchfrom tina4_python.Router import middleware, securedfrom tina4_python import descriptionfrom tina4_python import HTTP_OK, HTTP_FORBIDDEN, HTTP_BAD_REQUESTBasic Route Definition#
@get("/hello")async def hello_world(request, response): return response("Hello, Tina4 Python!")Stack multiple HTTP methods on one handler:
@get("/users")@post("/users")async def users_handler(request, response): return response({"users": []})Route Parameters (Dynamic Paths) {#dynamic-routing}#
@get("/users/{id}")async def get_user(id: str, request, response): return response({"user_id": id})โ# With automatic type conversion@get("/users/{id:int}")async def get_user_int(id: int, request, response): return response({"user_id": id, "type": type(id).__name__})โ# Multiple parameters + path (greedy)@get("/files/{filepath:str}")async def serve_file(filepath: str, request, response): return response.file(filepath)Supported converters: int, float, str (default), path
Query Parameters#
@get("/search")async def search(request, response): q = request.params.get("q", "world") page = int(request.params.get("page", 1)) return response(f"Searching '{q}' - page {page}")Prefixes & File Organization#
Just use the path you want - no special prefix decorator needed:
@get("/admin/dashboard")async def admin_dashboard(request, response): return response("Admin Area")Put files in routes/admin_routes.py โ auto-loaded. <!-- I think this section should be removed from here and just a link added to the middle ware documenation. It does not belong here and makes the page busy. I suggest an entry like the further reading in php } -->
Middleware#
class AuthMiddleware: @staticmethod def before_route(request, response): if request.headers.get("authorization") != "Bearer secret123": response.http_code = 401 return request, response # stops chain return request, responseโ @staticmethod def after_route(request, response): Response.add_header("X-Powered-By", "Tina4") return request, responseโ@middleware(AuthMiddleware)@get("/protected")async def protected_route(request, response): return response("Top secret data")<!-- Same comment as for middleware, I think this section should be replaced with a link -->
Metadata & Swagger#
@get("/api/users")@description("Retrieve the full list of users") # see the full list under Swagger linkasync def list_users(request, response): return response({"users": [...]})Secured Routes#
@secured()@get("/profile")async def profile(request, response): return response({"message": "Welcome to your profile"})Response Helpers#
return response({"json": "yes"}) # application/jsonreturn response("<h1>Hello</h1>") # text/htmlreturn response.redirect("/login") # 302return response.file("report.pdf", "uploads") # send/file downloadreturn response.render("index.twig", {"title": "Home"})return response("plain text", HTTP_OK, TEXT_PLAIN) # text/plainWith custom status:
return response("Not found", HTTP_NOT_FOUND)WebSockets#
You can add a websocket endpoint as a @get route, this requires simple-websocket, add with uv add simple-websocket
from tina4_python.Websocket import Websocketโ@get("/ws/chat")async def chat_ws(request, response): ws = await Websocket(request).connection() try: while True: data = await ws.receive() await ws.send(f"Echo: {data}") finally: await ws.close() return response("")Auto-Discovery#
Tina4 automatically loads routes from:
- Any file inside
routes/folder
Zero manual registration required.
Summary Table#
| Feature | Syntax Example | Notes |
|---|---|---|
| Route | @get("/path") | Must be async def |
| Path Params | /users/{id:int} | Auto-injected + conversion |
| Query Params | request.params.get("q") | Dict-like |
| Middleware | @middleware(MyClass) | before_route / after_route |
| Description | @description("Text") | Populates Swagger UI |
| Secured | @secured() | Built-in auth guard |
| Responses | response() .file() .redirect() | All via injected response |
| WebSockets | @get("/ws") + Websocket(request) | Full async support |
| Auto-discovery | Drop file in routes/ | No config needed |
๐ฅ Hot Tips
- Prefer explicit
request, responsearguments - they are auto-injected only when needed - Use
response.file()for serving uploads (root is project folder by default) - Return early in
before_routemiddleware to block the request @descriptionis your friend for auto-generated Swagger/OpenAPI docs- Combine
@get+@poston the same function to handle multiple methods cleanly - Your route files can be anywhere under
./src/routes/- Tina4 finds them magically - Always
awaitWebSocket send/receive - they are fully async
Happy routing with Tina4 Python! ๐