Tina4

Routing {#basic-routing}#

๐Ÿ”ฅ Hot Tips

  • You don't need app = Tina4() in route files - just import get, 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
  • request and response are 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#

python
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_REQUEST

Basic Route Definition#

python
@get("/hello")async def hello_world(request, response):    return response("Hello, Tina4 Python!")

Stack multiple HTTP methods on one handler:

python
@get("/users")@post("/users")async def users_handler(request, response):    return response({"users": []})

Route Parameters (Dynamic Paths) {#dynamic-routing}#

python
@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#

python
@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:

python
@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#

python
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#

python
@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#

python
@secured()@get("/profile")async def profile(request, response):    return response({"message": "Welcome to your profile"})

Response Helpers#

python
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/plain

With custom status:

python
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

python
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#

FeatureSyntax ExampleNotes
Route@get("/path")Must be async def
Path Params/users/{id:int}Auto-injected + conversion
Query Paramsrequest.params.get("q")Dict-like
Middleware@middleware(MyClass)before_route / after_route
Description@description("Text")Populates Swagger UI
Secured@secured()Built-in auth guard
Responsesresponse() .file() .redirect()All via injected response
WebSockets@get("/ws") + Websocket(request)Full async support
Auto-discoveryDrop file in routes/No config needed

๐Ÿ”ฅ Hot Tips

  • Prefer explicit request, response arguments - they are auto-injected only when needed
  • Use response.file() for serving uploads (root is project folder by default)
  • Return early in before_route middleware to block the request
  • @description is your friend for auto-generated Swagger/OpenAPI docs
  • Combine @get + @post on the same function to handle multiple methods cleanly
  • Your route files can be anywhere under ./src/routes/ - Tina4 finds them magically
  • Always await WebSocket send/receive - they are fully async
:::

Happy routing with Tina4 Python! ๐Ÿš€