Skip to content

Responses

Responses determine how your API sends data back to clients. Ravyn supports multiple response types from simple JSON to file downloads, templates, and streaming. all with clean, type-safe syntax.

What You'll Learn

  • Available response types and when to use them
  • Returning JSON with different serializers (ORJSON, UJSON)
  • Serving templates, files, and streams
  • Managing status codes
  • Adding responses to OpenAPI documentation

Quick Start

Simple JSON Response

from ravyn import Ravyn, get
from ravyn.responses import JSONResponse

@get("/users")
def list_users() -> JSONResponse:
    return JSONResponse({"users": ["Alice", "Bob"]})

app = Ravyn()
app.add_route(list_users)

Ravyn automatically converts Pydantic models, dataclasses, and dicts to JSON:

from ravyn import Ravyn, get
from pydantic import BaseModel

class User(BaseModel):
    name: str
    email: str

@get("/user")
def get_user() -> User:
    return User(name="Alice", email="alice@example.com")
    # Automatically converted to JSONResponse!

app = Ravyn()
app.add_route(get_user)

Tip

Return Pydantic models, dataclasses, or dicts directly. Ravyn converts them to JSON automatically.


Available Response Types

Response Use Case Example
JSONResponse API responses (default) {"status": "ok"}
TemplateResponse HTML pages Jinja2 templates
RedirectResponse Redirects Login → Dashboard
FileResponse File downloads PDFs, images
StreamingResponse Large data Video streaming
PlainTextResponse Plain text Simple messages

JSON Responses

JSONResponse (ORJSON - Fastest)

Ravyn uses ORJSON by default for maximum performance:

from ravyn import get
from ravyn.responses import JSONResponse

@get("/data")
def get_data() -> JSONResponse:
    return JSONResponse({
        "items": [1, 2, 3],
        "total": 3
    })

Direct Import

from ravyn.responses import JSONResponse  # ORJSON-based

UJSONResponse (Alternative)

Another fast JSON serializer:

from ravyn.responses.encoders import UJSONResponse

@get("/data")
def get_data() -> UJSONResponse:
    return UJSONResponse({"message": "Using UJSON"})

Warning

UJSON and ORJSON require installation: pip install ujson orjson

Status Codes

Control status codes in multiple ways:

from ravyn import get, post
from ravyn.responses import JSONResponse

# Option 1: In the response
@get("/method1")
def method1() -> JSONResponse:
    return JSONResponse({"created": True}, status_code=201)

# Option 2: In the handler decorator
@post("/method2", status_code=201)
def method2() -> JSONResponse:
    return JSONResponse({"created": True})

# Option 3: Both (response takes precedence!)
@post("/method3", status_code=201)
def method3() -> JSONResponse:
    return JSONResponse({"created": True}, status_code=202)
    # Returns 202, not 201!

Priority: Response status_code > Handler status_code


Template Responses

Render HTML templates with Jinja2:

from ravyn import get
from ravyn.responses import Template

@get("/profile")
def profile(name: str) -> Template:
    return Template(
        name="profile.html",
        context={"user_name": name}
    )

Setup Template Engine

Configure in settings:

from ravyn import RavynSettings
from ravyn.configurations import TemplateConfig

class Settings(RavynSettings):
    @property
    def template_config(self) -> TemplateConfig:
        return TemplateConfig(directory="templates")

[!INFO] For async templates (e.g., iterating over Edgy QuerySets), see Template Configuration.


Redirect Responses

Redirect users to different URLs:

from ravyn import get, post
from ravyn.responses import Redirect

@post("/login")
def login(username: str, password: str) -> Redirect:
    # Authenticate user...
    return Redirect(path="/dashboard")

@get("/old-page")
def old_page() -> Redirect:
    return Redirect(path="/new-page", status_code=301)  # Permanent redirect

File Responses

Send files for download:

from ravyn import get
from ravyn.responses import File

@get("/download")
def download_report() -> File:
    return File(
        path="/reports/monthly.pdf",
        filename="report.pdf"
    )

@get("/image")
def get_image() -> File:
    return File(
        path="/images/logo.png",
        media_type="image/png"
    )

Streaming Responses

Stream large responses:

from ravyn import get
from ravyn.responses import Stream

async def generate_data():
    for i in range(1000):
        yield f"data: {i}\n\n"

@get("/stream")
async def stream_data() -> Stream:
    return Stream(generate_data())

Other Response Types

Plain Text

from ravyn.responses import PlainText

@get("/health")
def health() -> PlainText:
    return PlainText("OK")

Direct Returns

Return basic Python types directly:

@get("/string")
def return_string() -> str:
    return "Hello, World!"

@get("/dict")
def return_dict() -> dict:
    return {"message": "Hello"}

@get("/list")
def return_list() -> list:
    return [1, 2, 3]

Ravyn automatically wraps these in appropriate responses.


OpenAPI Response Documentation

Document multiple response types in your OpenAPI spec:

from ravyn import get, post
from ravyn.openapi.datastructures import OpenAPIResponse
from pydantic import BaseModel

class Item(BaseModel):
    sku: str
    description: str

class Error(BaseModel):
    detail: str

@post(
    "/items",
    responses={
        201: OpenAPIResponse(model=Item, description="Item created"),
        400: OpenAPIResponse(model=Error, description="Validation error"),
        401: OpenAPIResponse(model=Error, description="Not authorized")
    }
)
def create_item() -> Item:
    return Item(sku="ABC123", description="New item")

List Responses

Document array responses:

@get(
    "/items",
    responses={
        200: OpenAPIResponse(model=[Item], description="List of items")
    }
)
def list_items() -> list[Item]:
    return [Item(sku="A", description="Item A")]

Tip

Use model=[YourModel] (list syntax) to indicate array responses in OpenAPI.


Common Pitfalls & Fixes

Pitfall 1: Status Code Confusion

Problem: Handler status_code doesn't apply.

# Confusing - which status code wins?
@post("/create", status_code=201)
def create() -> JSONResponse:
    return JSONResponse({"created": True}, status_code=202)
    # Returns 202, not 201!

Solution: Be consistent. use one or the other:

# Clear - use handler decorator
@post("/create", status_code=201)
def create() -> JSONResponse:
    return JSONResponse({"created": True})

# Or use response
@post("/create")
def create() -> JSONResponse:
    return JSONResponse({"created": True}, status_code=201)

Pitfall 2: Missing ORJSON/UJSON

Problem: ModuleNotFoundError when using fast JSON serializers.

# Error if orjson not installed
from ravyn.responses import JSONResponse

Solution: Install the dependencies:

pip install orjson ujson

Pitfall 3: Template Engine Not Configured

Problem: ImproperlyConfigured when using templates.

# Error - no template engine configured
@get("/page")
def page() -> Template:
    return Template(name="page.html", context={})

Solution: Configure template engine in settings:

from ravyn import RavynSettings
from ravyn.configurations import TemplateConfig

class Settings(RavynSettings):
    @property
    def template_config(self) -> TemplateConfig:
        return TemplateConfig(directory="templates")

Pitfall 4: File Path Errors

Problem: File not found when serving files.

# Wrong - relative path might not work
@get("/download")
def download() -> File:
    return File(path="report.pdf")  # Where is this file?

Solution: Use absolute paths:

# Correct
from pathlib import Path

@get("/download")
def download() -> File:
    file_path = Path(__file__).parent / "reports" / "report.pdf"
    return File(path=str(file_path))

Response Headers and Cookies

All responses support headers and cookies:

from ravyn.responses import JSONResponse

@get("/with-headers")
def with_headers() -> JSONResponse:
    return JSONResponse(
        {"message": "Hello"},
        headers={"X-Custom-Header": "value"},
        cookies={"session_id": "abc123"}
    )

Next Steps

Now that you understand responses, explore: