Skip to content

Response

In routup v5, responses are return-based. Handlers return a value, and routup converts it to a Response object automatically.

Return Values

typescript
// 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:

typescript
defineCoreHandler((event) => {
    event.response.status = 201;
    event.response.headers.set('X-Custom', 'value');
    return { created: true };
});

Note: event.response settings are ignored when you return a Response object 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:

typescript
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:

typescript
import { defineCoreHandler, sendRedirect } from 'routup';

router.get('/old', defineCoreHandler((event) => {
    return sendRedirect(event, '/new');
}));

sendCreated

Send a 201 Created response:

typescript
import { defineCoreHandler, sendCreated } from 'routup';

router.post('/users', defineCoreHandler(async (event) => {
    return sendCreated(event, { id: 1 });
}));

sendAccepted

Send a 202 Accepted response:

typescript
import { defineCoreHandler, sendAccepted } from 'routup';

router.post('/jobs', defineCoreHandler(async (event) => {
    return sendAccepted(event);
}));

sendStream

Stream data to the client:

typescript
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:

typescript
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:

typescript
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;
}));