Response
In routup v5, responses are return-based. Handlers return a value, and routup converts it to a Response object automatically.
Return Values
// String — text/plain
defineCoreHandler(() => 'Hello, World!');
// Object or Array — application/json
defineCoreHandler(() => ({ users: [] }));
// Response — used as-is
defineCoreHandler(() => new Response('Created', { status: 201 }));
// ReadableStream — streamed to client
defineCoreHandler(() => someReadableStream);
// Blob — sent with appropriate content type
defineCoreHandler(() => new Blob(['data'], { type: 'text/plain' }));
// ArrayBuffer / Uint8Array — sent as binary
defineCoreHandler(() => new ArrayBuffer(8));
// null — empty response
defineCoreHandler(() => null);Returning undefined
undefined is not an implicit pass-through. A handler that returns undefined must have either called event.next() (forwarding the downstream result) or intend event.next() to be invoked later from an async callback.
If event.next() is never invoked, the pipeline waits on event.signal: a global or per-handler timeout surfaces as 408 Request Timeout; with no timeout configured the request hangs by design. See Handlers → Returning undefined for details.
Status and Headers
Use event.response to set status codes and headers before returning:
defineCoreHandler((event) => {
event.response.status = 201;
event.response.headers.set('X-Custom', 'value');
return { created: true };
});Note:
event.responsesettings are ignored when you return aResponseobject directly.
Response Helpers
Routup provides helper functions for common response patterns:
sendFile
Send a file with support for range requests, ETag generation, and automatic content-type detection:
import { defineCoreHandler, sendFile } from 'routup';
import fs from 'node:fs/promises';
import { createReadStream } from 'node:fs';
import { Readable } from 'node:stream';
router.get('/download', defineCoreHandler(async (event) => {
return sendFile(event, {
stats: () => fs.stat('/path/to/file.pdf'),
content: (opts) => {
return Readable.toWeb(createReadStream('/path/to/file.pdf', opts)) as ReadableStream;
},
name: 'file.pdf',
});
}));sendRedirect
Redirect the client to another URL:
import { defineCoreHandler, sendRedirect } from 'routup';
router.get('/old', defineCoreHandler((event) => {
return sendRedirect(event, '/new');
}));sendCreated
Send a 201 Created response:
import { defineCoreHandler, sendCreated } from 'routup';
router.post('/users', defineCoreHandler(async (event) => {
return sendCreated(event, { id: 1 });
}));sendAccepted
Send a 202 Accepted response:
import { defineCoreHandler, sendAccepted } from 'routup';
router.post('/jobs', defineCoreHandler(async (event) => {
return sendAccepted(event);
}));sendStream
Stream data to the client:
import { defineCoreHandler, sendStream } from 'routup';
router.get('/stream', defineCoreHandler((event) => {
return sendStream(event, readableStream);
}));sendFormat
Content-negotiate and send a response in the appropriate format:
import { defineCoreHandler, sendFormat } from 'routup';
router.get('/data', defineCoreHandler((event) => {
return sendFormat(event, {
default: () => 'key=value',
'application/json': () => ({ key: 'value' }),
'text/plain': () => 'key=value',
});
}));createEventStream
Create a Server-Sent Events (SSE) stream:
import { defineCoreHandler, createEventStream } from 'routup';
router.get('/events', defineCoreHandler((event) => {
const stream = createEventStream(event);
let count = 0;
const interval = setInterval(() => {
stream.write(`count: ${count}`);
count++;
if (count > 100) {
stream.end();
clearInterval(interval);
}
}, 1000);
return stream.response;
}));