Skip to content
📌 Enable SQLite data replication to S3 with Litestream sync

Response

Response Data

The response from the handler function is passed to the template to be converted to HTML. The handler response is accessible through .Data, or $.Data from a nested template scope such as range or with. Any supported Starlark value can be used as the return value. Using a dictionary is recommended, so that error handling is easier. Adding an Error key in the response dict can indicate to the template that an error condition needs to be handled.

For example, a handler like

app.star
def handler(req):
    name = req.Query.get("name")

    if name:
        return {"Name": name[0], "Error": None}
    else:
        return {"Error": "Name not specified", "Name": None}

app = ace.app("test", routes = [ace.html("/")])

allows the template to handle the error by doing

{{block "openrun_body" .}}
{{if .Data.Error}}
    <div style="color: red">{{.Data.Error}}</div>
{{else}}
    Hi {{.Data.Name}}
{{end}}
{{end}}

Redirect Response

If the API needs to redirect the client to another location after a POST/PUT/DELETE operation, the handler function can return an ace.redirect structure. The fields in this structure are:

PropertyOptionalTypeDefaultNotes
urlfalsestringThe url to redirect to
codetrueint303The HTTP status code, 303 or 302

For example, this code does a 303 redirect after a POST API, which provides handling for update requests.

app.star
def create_game(req):
    level = req.Form["level"]
    ret = http.post(SERVICE_URL + "/api/create_game/" + level[0])
    return ace.redirect(req.AppPath + "/game/" + ret.value.json()["GameId"])

Custom Response

In some cases, a custom response needs to be generated, with special headers. Or the response needs to use a template different from the one defined in the route, which could happen in the case of an error. For such cases, an ace.response structure can be returned by the handler. The fields in this structure are:

PropertyOptionalTypeDefaultNotes
datafalseobjectThe response data
blocktruestringTemplate block override; otherwise uses the route block
typetruestringinherited from the route type definitionIf “json”, block is ignored
codetrueint200HTTP status code
retargettruestringHX-Retarget header value, CSS selector to target, like “#error_id”
reswaptruestringHX-Reswap, like “outerHTML”
redirecttruestringSet HX-Redirect on an HTML response.
downloadtruestringAttachment filename; see File Downloads below.
content_typetruestringapplication/octet-stream for downloadsDownload MIME type.

For example, this handler code uses retarget to handle errors by updating the html property which has id “gameErrorId”

app.star
ret = http.post(api_url).value.json()
if ret.get("Error"):
    return ace.response(ret, "game_error_block", retarget="#gameErrorId")
return fetch_game(req, game_id)

This code returns a 404 with a custom body generated from a template block called “invalid_challenge_block”

app.star
if challenge.get("Error"):
    return ace.response(challenge, "invalid_challenge_block", code=404)

htmx 4 swaps error responses (4xx and 5xx) into the target like any other response, so the block above is shown in place of the target; htmx 2 did not swap them. To keep an error response out of the page, or to route it elsewhere, use the hx-status attribute on the requesting element, like hx-status:404="swap:none" or hx-status:4xx="target:#errors", or set htmx.config.noSwap for the whole app.

File Downloads

Return ace.response with download set to a filename to send an attachment. Set content_type to its MIME type; the default is application/octet-stream. Download data can be a string, bytes, or a download stream returned by a plugin.

app.star
def download_report(req):
    return ace.response(
        "name,count\nbookmarks,12\n",
        download="report.csv",
        content_type="text/csv",
    )

app = ace.app("Reports", routes=[ace.api("/report", handler=download_report)])

Downloads bypass template and JSON rendering.

JSON Response

HTML routes return HTML by default; ace.api routes return JSON by default. There are some cases where data needs to be returned to the client in JSON format. The type property can be used for those cases. For example, this API returns JSON

app.star
ace.api("/memory", handler=memory_handler),

Here, the response from the handler function is returned as JSON, no template is used. Also, in this handler, if there is a call to ace.response, the type will default to JSON since that is the type specified at the route level. Mime type detection based on the Accept header is planned, it is not currently supported.