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

© 2024 Hantera AB. All rights reserved.