Skip to content

HTTP Response Handling ​

Component return values are automatically converted to HTTP responses. This page covers all response types and how to handle errors.

JSON Response ​

Most component return values are serialized as JSON with a 200 OK status:

filtrera
from {
  status = 'success'
  data = ['item1', 'item2']
}

Response:

http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "success",
  "data": ["item1", "item2"]
}

Arrays, records, numbers, text, and booleans are all serialized to their JSON equivalents.

No Content Response ​

Return nothing to send a 204 No Content response:

filtrera
param orderId: text

let _ = deleteOrder orderId

from nothing

Response:

http
HTTP/1.1 204 No Content

This is useful for fire-and-forget operations where no response body is needed.

Stream Response ​

Components can return streams for file downloads or large responses:

filtrera
param fileId: text

from files.read($'documents/{fileId}')

The stream's MIME type is automatically set as the Content-Type header, and the content is streamed to the client without buffering.

This is efficient for:

  • File downloads
  • Large data exports
  • Binary content

Raw HTTP Response ​

For full control over status code, headers, and body, return a record with a numeric statusCode field. The presence of this field is what signals "this is a raw response":

filtrera
from {
  statusCode = 302
  headers = {
    'Location' -> '/login'
  }
}

Response:

http
HTTP/1.1 302 Found
Location: /login

The shape of the response record is:

typescript
{
  statusCode: number
  headers?: { text -> text | [text] }
  content?: value
}

Status Code ​

Any HTTP status code is supported:

filtrera
// Created
from { statusCode = 201, content = { id = newid } }

// Custom error with details
from {
  statusCode = 422
  content = {
    error = {
      code = 'VALIDATION_FAILED'
      details = { field = 'email' }
    }
  }
}

// I'm a teapot
from { statusCode = 418 }

Headers ​

A header value can be a single text (the common case) or a list of text for multi-value headers like Set-Cookie:

filtrera
from {
  statusCode = 200
  headers = {
    'Cache-Control' -> 'public, max-age=3600'
    'X-Trace-Id' -> 'abc-123'
    'Set-Cookie' -> [
      'session=abc; HttpOnly'
      'theme=dark'
    ]
  }
}

If you set Content-Type via headers, it overrides the default that would otherwise be inferred from the content:

filtrera
from {
  statusCode = 200
  headers = {
    'Content-Type' -> 'application/xml'
  }
  content = '<?xml version="1.0"?><ok/>'
}

Content ​

The content field can be any value:

content typeBehavior
omitted / nothingEmpty body
textWritten as-is, default Content-Type: text/plain; charset=utf-8
streamWritten as binary, default Content-Type from stream's MIME type
record/list/number/etc.JSON-serialized, default Content-Type: application/json
filtrera
// Plain text response
from {
  statusCode = 200
  content = 'Hello, world!'
}

// Custom JSON shape with custom status
from {
  statusCode = 202
  headers = { 'Location' -> '/jobs/123/status' }
  content = { jobId = '123', status = 'queued' }
}

When to use raw responses ​

Use the raw response model when you need to:

  • Set a non-default HTTP status code (redirects, custom errors, 2xx variants)
  • Add custom response headers (Location, Cache-Control, Set-Cookie, ...)
  • Return a non-JSON body with a custom Content-Type

For everything else, prefer the simpler "just return a value" or "return an error record" patterns shown above.

Error Response ​

Return a record matching the Error type to indicate failure:

filtrera
param orderId: text | nothing

from orderId match
  nothing |> {
    error = {
      code = 'MISSING_PARAMETER'
      message = 'Order ID is required'
    }
  }
  id |> processOrder(id)

Error type structure:

typescript
{
  error: {
    code: string
    message?: string
  }
}

Response:

http
HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": {
    "code": "MISSING_PARAMETER",
    "message": "Order ID is required"
  }
}

Error Status Codes ​

The HTTP status code is determined by the error code:

Error Code PatternHTTP Status
NOT_FOUND404
UNAUTHORIZED401
FORBIDDEN403
Other codes400

Error Handling Best Practices ​

Provide meaningful error messages:

filtrera
param productId: text

let product = query skus(skuNumber) where skuNumber == productId first

from product match
  nothing |> {
    error = {
      code = 'NOT_FOUND'
      message = $'Product {productId} not found'
    }
  }
  p |> p

Validate input early:

filtrera
param email: text | nothing
param name: text | nothing

from { email, name } match
  { email: text, name: text } |> createUser(email, name)
  |> {
    error = {
      code = 'INVALID_INPUT'
      message = 'Email and name are required'
    }
  }

HTTP Status Codes ​

StatusCondition
200Successful response with body
204Successful response, no content
400Invalid request or general error
401Authentication required
403Forbidden (authorization failed)
404Resource not found
405Wrong HTTP method
500Internal server error

Response Headers ​

Response headers are automatically set based on the content type:

Content TypeHeader Value
JSON dataapplication/json
StreamStream's MIME type
SSEtext/event-stream

Custom response headers are not configurable through ingress properties — use the Raw HTTP Response model when you need to set them.

Large Responses ​

For large responses, consider:

  1. Pagination: Return data in pages with cursor-based pagination
  2. Streaming: Use iterators for lazy evaluation
  3. SSE: For real-time updates, use Server-Sent Events

Graph queries automatically use cursor-based pagination:

filtrera
param cursor: text | nothing
param pageSize: number = 100

from query orders(orderNumber, status, createdAt)
  where status == 'pending'
  pageSize pageSize
  cursor cursor

See Also ​

© 2026 Hantera AB. Org. no.: 559242-9582