Essentials
Outgoing response
The mirror of the incoming event: what your handler returns is turned into a response by the active context. Return plain data and let the context shape it, or take control with an explicit response when the situation calls for it.
#Return data, stay portable
The principle
A handler that builds a platform response is tied to that platform. A handler that returns a value leaves the shaping to the boundary, so the same handler yields JSON on an API and a rendered view in the browser.
In Stone.js
Return an object or array and it becomes the body, serialised by the context. Throw a domain error and the error handler maps it to the right status. Most handlers never build a response at all.
@Get('/:id')
show (event: IncomingHttpEvent) {
return this.tasks.find(event.get('id')) // -> 200 + JSON, on an HTTP context
}#Taking control
When you need a specific status, headers, or cookies, build a response with the HTTP layer's helpers and set what you need.
import { jsonHttpResponse } from '@stone-js/http-core'
@Post('/')
create (event: IncomingHttpEvent) {
const task = this.tasks.add(event.get('title'))
const response = jsonHttpResponse(task, 201) // explicit status
response.setHeader('Location', `/tasks/${task.id}`)
return response
}#A status without naming a platform
Those helpers are HTTP, which is right in a handler that knows it serves HTTP and wrong in a module meant to run anywhere. There are two portable ways to answer with a status, and both leave the shaping to the boundary:
// on the route, when the status is a constant of the endpoint
@Post('/', { response: { type: 'json', status: 201 } })
create (event: IncomingHttpEvent) { return this.tasks.add(event.get('title')) }
// from the handler, when the status is decided at run time
async handle (event: IncomingEvent) {
const report = await this.checks.run()
return { content: report, statusCode: report.ok ? 200 : 503 }
}The second form is the kernel's own: an object carrying a statusCode is handed to the platform's resolver, which turns it into an HTTP response here and into an exit code on a CLI. Building the agnostic OutgoingResponse works too and takes the same road. Nothing in either form names a platform, which is what lets a module serve every context.
#Response helpers
| Helper | Type | Description |
|---|---|---|
| jsonHttpResponse(data, status?) | (data, status) => Response | A JSON body with an optional status. |
| htmlHttpResponse(html, status?) | (html, status) => Response | An HTML body. |
| noContentHttpResponse() | () => Response | A 204 with no body. |
| jsonpHttpResponse(data, cb) | (data, callback) => Response | A JSONP body for a named callback. |
| redirectHttpResponse(url, status?) | (url, status) => Response | A redirect (default 302). |
| fileHttpResponse(file) | (file) => Response | Stream or download a file. |
| emptyHttpResponse() | () => Response | An empty body (defaults to 200). |
For the common failures there are status shortcuts, so you can be explicit when throwing a domain error is not the right fit: badRequestHttpResponse, unauthorizedHttpResponse, forbiddenHttpResponse, notFoundHttpResponse, serverErrorHttpResponse.
#Shaping a response
Any response is fluent: set the status, headers, cookies and caching hints before returning it. The caching setters (setEtag, setLastModified) let a handler participate in conditional requests without touching the platform.
return jsonHttpResponse(report)
.setStatus(200)
.setHeader('Cache-Control', 'public, max-age=60')
.setEtag(report.hash) // enables 304 on re-request
.setLastModified(report.updatedAt)| Setter | Type | Description |
|---|---|---|
| setStatus(code) | (number) => this | The HTTP status. |
| setHeader(name, value) | (name, value) => this | A response header. |
| setCookie(name, value, opts?) | (name, value, opts) => this | A cookie (see Cookies). |
| setContent(data) | (data) => this | Replace the body. |
| setEtag(tag) / setLastModified(date) | caching | Conditional-request hints for 304 handling. |