On this page
responses
introduction
the Response class wraps the raw node http response. however, you almost never interact with the response object directly. instead, controllers return one of the globally available response helpers: render(), json(), redirect(), back(), abort(), status(), or stream().
dframework detects the returned helper and finalizes the http response automatically.
basic responses
the status() helper is used to set the http status code. it returns a chainable response object. you can chain .json() or .send() onto it.
1export default class WebhookController {2 async handle() {3 // responds with a 202 accepted and empty body4 return status(202).send('');5 }6}
headers and cookies
you can attach headers and cookies to the response before finalizing it. these methods are chainable.
1export default class DownloadController {2 async export() {3 const csv = await generateCsv();4 return status(200)5 .set('Content-Disposition', 'attachment; filename="export.csv"')6 .type('text/csv')7 .send(csv);8 }9}to set a cookie, use the .cookie() method.
1export default class ThemeController {2 async update() {3 return status(200)4 .cookie('theme', 'dark', { httpOnly: false, path: '/', maxAge: 31536000 })5 .json({ success: true });6 }7}
json responses
the json() helper automatically sets the Content-Type to application/json; charset=utf-8 and serializes the provided object.
1export default class ApiController {2 async user(req) {3 const user = await User.find(req.params.id);4 return json({ success: true, user });5 }6}if validation errors were bound to the response via .withErrors(), the framework automatically injects them into the json payload. if the application is running in local development mode with the debugbar enabled, performance metrics are also automatically injected into the json payload.
rendering views
the render() helper evaluates a view template and sends the resulting html. rendering is scheduled asynchronously, which allows you to chain data binding methods before the html is actually compiled.
1export default class ProfileController {2 async show() {3 const user = Auth.user();4 5 return render('app.profile.show', { user })6 .withData({ title: 'my profile' })7 .antiPeeping(false);8 }9}if a form submission fails, you can flash the validation errors and the old input back to the view using .withErrors() and .withInput(). you can also flash custom key/value pairs using .with(key, value).
1export default class SettingsController {2 async update(req) {3 const validation = validate(req.body, rules);4 5 if (validation.fails()) {6 return back().withErrors(validation.errors()).withInput(req.body).with('toast', 'settings could not be saved');7 }8 9 // ...10 }11}
streaming responses
the stream(writer) helper sends a response body in chunks with backpressure support. it receives an async function (write, close) where write(chunk) sends a chunk and returns a promise that resolves when the transport is ready for more data, and close() ends the response.
streaming is ideal for proxying large files (audio, video, documents) without buffering the entire payload in memory. set headers (content type, range, cache control) before calling stream(), just like any other response method.
dframework handles backpressure automatically. if the client or transport cannot keep up, write() will wait until the socket is writable again before resolving. the response pool is not released until close() is called, so long running streams do not interfere with object reuse.
1export default class StreamController {2 async audio(req) {3 const file = await AudioFile.find(req.params.id);4 if (!file) return abort(404);5 6 const upstream = await fetch(file.url, {7 headers: { range: req.headers['range'] }8 });9 10 return status(upstream.status)11 .set('accept-ranges', 'bytes')12 .set('content-range', upstream.headers.get('content-range'))13 .type('audio/mpeg')14 .stream(async (write, close) => {15 const reader = upstream.body.getReader();16 while (true) {17 const { done, value } = await reader.read();18 if (done) break;19 await write(Buffer.from(value));20 }21 close();22 });23 }24}close() must be called when you are done writing. if your writer function returns without calling close(), the framework will call it automatically for you. if the writer throws an error, the response is ended and the pool is released.
redirects
the redirect(url, code = 302, force = false) helper creates a redirect response.
as noted in the controllers documentation, dframework redirect helper is strictly protected against open redirect vulnerabilities. it refuses to redirect to cross origin or protocol relative urls, throwing an error instead (except in local development).
it is also spa aware. if the request was made by the client side router (dSPAHttpRequest), it returns a json instruction for the frontend router to execute instead of issuing a standard http 302. setting the force boolean to true forces the spa router to perform a hard page reload.
1export default class AuthController {2 async logout() {3 await Auth.logout();4 return redirect('/home');5 }6}the back() helper redirects the user to their previous location based on the session's internal history tracking.
1export default class PostController {2 async destroy(req) {3 await Post.delete(req.params.id);4 return back('/fallback-url');5 }6}
error responses
the abort(code, message) helper immediately halts execution and returns an error response. it is context aware: if the request was made via ajax, it automatically returns a json error payload. if it was a standard browser request, it passes the error to the global error handler which renders the appropriate html error page (e.g. 404, 500).
1export default class FileController {2 async show(req) {3 const file = await File.find(req.params.id);4 5 if (!file) {6 return abort(404, 'file not found');7 }8 9 if (file.owner_id !== Auth.user().id) {10 return abort(403, 'unauthorized access');11 }12 13 return json({ file });14 }15}
