Response Return Styles

Robyn automatically converts your handler's return value into a proper HTTP response. Here's a complete reference of every supported return style.

text/plain

String

The simplest return style. Returns the string as text/plain with status 200. The string is encoded as UTF-8 automatically.

Response

GET
/hello
from robyn import Robyn

app = Robyn(__file__)

@app.get("/hello")
def hello():
    return "Hello, World!"

Dictionary or List

Returning a dict or list automatically serializes the value to JSON and sets Content-Type: application/json with status 200.

Response

GET
/data
@app.get("/user")
def get_user():
    return {"name": "Alice", "age": 30}

Response Object

The Response object gives you full control over status code, headers, and body. Use it when you need to customize everything about the response.

Response

GET
/custom
from robyn import Robyn, Response, Headers

app = Robyn(__file__)

@app.get("/custom")
async def custom_response():
    return Response(
        status_code=200,
        headers=Headers({"X-Custom": "value"}),
        description="OK",
    )

Bytes

Returning a bytes object sets Content-Type: application/octet-stream with status 200. Useful for binary data such as images or file content generated in memory.

Response

GET
/binary
@app.get("/binary")
async def binary_data():
    return b"binary data"

Pydantic BaseModel

If you return a Pydantic BaseModel instance, Robyn serializes it to JSON automatically with Content-Type: application/json and status 200.

Caveat: Pydantic must be installed as an optional dependency (pip install pydantic). If it is not installed, the model will not be detected and will fall through to the default string serialization.

Response

GET
/model
from pydantic import BaseModel
from robyn import Robyn

app = Robyn(__file__)

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

@app.get("/model")
def get_model():
    return User(name="Alice", email="alice@example.com", age=30)

Tuple (body, headers, status_code)

A 3-element tuple of (body, headers, status_code) lets you set a custom status code and headers inline. The body element is itself formatted using the same rules (string, dict, bytes, etc.).

Caveat: The tuple must have exactly 3 elements. Passing a tuple with a different number of elements raises a ValueError.

Response

POST
/create
from robyn import Headers

@app.get("/not-found")
def not_found():
    return (
        {"error": "Resource not found"},
        Headers({"X-Error": "true"}),
        404,
    )

FileResponse / serve_file / serve_html

Robyn provides helper functions for serving files. serve_file sets Content-Disposition: attachment and auto-detects the MIME type. serve_html sets Content-Type: text/html. You can also construct a FileResponse directly for full control.

Response

GET
/download
from robyn import Robyn
from robyn.responses import serve_file

app = Robyn(__file__)

@app.get("/download")
def download():
    return serve_file("report.pdf")

html()

The html() helper wraps a raw HTML string into a Response with Content-Type: text/html and status 200. Useful for returning dynamically generated HTML without a template engine.

Response

GET
/page
from robyn import Robyn
from robyn.responses import html

app = Robyn(__file__)

@app.get("/page")
def page():
    return html("<h1>Hello, World!</h1><p>Welcome to Robyn.</p>")

StreamingResponse

StreamingResponse sends chunked responses using a generator (sync or async). The default media_type is text/event-stream, but you can set it to any MIME type. The body is streamed chunk by chunk, so the client receives data as it is produced.

Response

GET
/stream
from robyn import Robyn
from robyn.responses import StreamingResponse

app = Robyn(__file__)

@app.get("/stream")
def stream():
    def generate():
        for i in range(5):
            yield f"chunk {i}\n"

    return StreamingResponse(
        content=generate(),
        media_type="text/plain",
    )

SSEResponse

SSEResponse is a convenience wrapper around StreamingResponse pre-configured for Server-Sent Events. Use it with the SSEMessage helper to format messages in the SSE protocol. Each message can have optional event, id, and retry fields.

For a deeper guide on Server-Sent Events, see the SSE documentation.

Response

GET
/events
from robyn import Robyn
from robyn.responses import SSEResponse, SSEMessage
import time

app = Robyn(__file__)

@app.get("/events")
def events():
    def event_stream():
        for i in range(10):
            yield SSEMessage(
                f"Event {i}",
                event="update",
                id=str(i),
            )
            time.sleep(1)

    return SSEResponse(event_stream())

What's next?

Now that you know every way to return data from a Robyn handler, explore file uploads to learn how to receive files from clients.