Skip to content

App

The App is the central building block. It manages a stack of handlers and dispatches requests through them.

typescript
import { App, defineCoreHandler, serve } from 'routup';

const app = new App();

app.get('/', defineCoreHandler((event) => {
    return 'Hello, World!';
}));

serve(app, { port: 3000 });

HTTP Methods

The app provides shorthand methods for all common HTTP verbs:

typescript
app.get('/users', defineCoreHandler((event) => { /* ... */ }));
app.post('/users', defineCoreHandler((event) => { /* ... */ }));
app.put('/users/:id', defineCoreHandler((event) => { /* ... */ }));
app.patch('/users/:id', defineCoreHandler((event) => { /* ... */ }));
app.delete('/users/:id', defineCoreHandler((event) => { /* ... */ }));

Mounting

Global

Mount a handler or app without a path prefix:

typescript
app.use(defineCoreHandler((event) => {
    // runs for all requests
    return event.next();
}));

Path

Mount on a specific path prefix:

typescript
app.use('/api', apiApp);

Or set the path in the app options:

typescript
const apiApp = new App({ path: '/api' });
app.use(apiApp);

Nesting

Apps can be nested for modular route organization:

typescript
const users = new App();
users.get('/', defineCoreHandler((event) => {
    return [{ id: 1, name: 'Alice' }];
}));
users.get('/:id', defineCoreHandler((event) => {
    return { id: event.params.id };
}));

const api = new App();
api.use('/users', users);

const app = new App();
app.use('/api', api);
// GET /api/users, GET /api/users/:id

app.mount() experimental

Mount an external fetch handler at a given path. The handler receives requests with the mount prefix stripped from the URL.

typescript
// Mount an object with a fetch method
app.mount('/api', externalApp);

// Mount a plain fetch function
app.mount('/proxy', (request) => {
    return fetch(request);
});

This is useful for integrating other frameworks or services that expose a Fetch-compatible interface.

Timeout

Global Timeout

Set a global request timeout that applies to the entire dispatch pipeline. When exceeded, a 408 Request Timeout response is returned and event.signal is aborted:

typescript
const app = new App({ options: { timeout: 5000 } }); // 5 seconds

Per-Handler Timeout

Set a default timeout for individual handler execution. Each handler's fn() is independently timed. When the timeout fires, event.signal inside the handler is aborted for cooperative cancellation:

typescript
const app = new App({
    options: {
        handlerTimeout: 2000,              // 2s default per handler
        handlerTimeoutOverridable: false,  // handlers can only narrow, not extend (default)
    },
});

Handlers can override the default via the verbose syntax:

typescript
app.get('/slow', defineCoreHandler({
    timeout: 10000, // 10s for this handler only
    fn: async (event) => fetchSlowData(),
}));

When handlerTimeoutOverridable is false, the effective timeout is Math.min(handlerTimeout, handler.timeout). Set it to true to let handlers extend beyond the default.

Cooperative Cancellation

Both timeout levels abort event.signal when the deadline fires. Handlers can pass it to signal-aware APIs:

typescript
defineCoreHandler(async (event) => {
    const res = await fetch('https://api.example.com', { signal: event.signal });
    return res.json();
});

app.fetch()

You can call app.fetch() directly with a Request object to get a Response. This is the Web Fetch API compatible entry point that runtime adapters call internally:

typescript
const app = new App();
app.get('/', defineCoreHandler(() => 'Hello'));

const response = await app.fetch(
    new Request('http://localhost/')
);
console.log(await response.text()); // "Hello"

This is useful for testing, serverless environments, and any runtime that supports the Fetch API.