Expose
expose returns data from an Apiwork controller. It serializes records through the representation, applies adapter behavior, and renders the response.
Usage
def show
invoice = Invoice.find(params[:id])
expose invoice
endFor collections:
def index
expose Invoice.all
endThe adapter determines what happens next — serialization, eager loading, pagination, and key transformation are handled automatically.
Meta
Pass metadata alongside the response:
def index
invoices = Invoice.all
expose invoices, meta: { total: invoices.count }
endThe adapter merges meta into the response. For collections with pagination, the adapter adds pagination metadata automatically.
Typed Meta
By default, meta is an untyped optional object. To give it a shape — visible in exports and validated in development — extend the response in the contract:
class InvoiceContract < ApplicationContract
representation InvoiceRepresentation
action :index do
response do
body do
object :meta do
integer :total
integer :filtered
end
end
end
end
endThe typed meta shape appears in OpenAPI, TypeScript, and Zod exports. The controller passes the values at runtime:
def index
invoices = Invoice.where(filter_params)
expose invoices, meta: { total: Invoice.count, filtered: invoices.count }
endStatus
expose defaults to :ok (200) for all actions except create, which defaults to :created (201).
Override with status::
def accept
order.accept!
expose order, status: :accepted
endNo Content
When the contract declares no_content!, expose returns 204 with an empty body:
class InvoiceContract < ApplicationContract
action :destroy do
response { no_content! }
end
enddef destroy
Invoice.find(params[:id]).destroy
expose nil
endError Detection
When using the standard adapter, expose checks for validation errors on the record. If the record has errors, the adapter converts them to a 422 response with structured domain errors.
def create
invoice = Invoice.create(contract.body[:invoice])
expose invoice
endIf Invoice.create fails validation, expose returns:
{
"layer": "domain",
"issues": [
{
"code": "required",
"detail": "Required",
"path": ["invoice", "number"],
"pointer": "/invoice/number",
"meta": {}
}
]
}The adapter handles both success and failure automatically.
See Validation for the full error mapping.
Without Representation
When the contract has no linked representation, expose renders data as-is with key transformation:
def status
expose({ version: "1.0", uptime: process_uptime })
endResponse Validation
In development, expose validates the response against the contract and logs warnings for mismatches. This catches shape drift between the representation and the contract definition.
See also
- Controller reference —
exposemethod details - Serialization — how the adapter serializes responses
- Validation — domain error handling
- HTTP Errors — transport-level errors via
expose_error