Ingest Ingest Composable Server/less IO Framework
GitHub

Reference Layer

Router

Router keeps route matching, route composition, and route metadata in one reusable layer that the server can build on.

Class ReferenceMethods, Properties, Examples

On This Page

Structural Map

  1. 1. Properties
  2. 2. Methods
  3. 2.1. HTTP Method Routing
  4. 2.2. Generic Route Definition
  5. 2.3. Event Handling
  6. 2.4. Event Emission
  7. 2.5. Route Resolution
  8. 2.6. Request and Response Creation
  9. 2.7. Router Composition
  10. 2.8. Controller Mounting
  11. 3. Automatic Router Detection
  12. 4. Routing Interfaces
  13. 4.1. Action Router (Traditional)
  14. 4.2. Entry Router (File-based)
  15. 4.3. Import Router (Lazy Loading)
  16. 4.4. View Router (Template-based)
  17. 5. Route Patterns
  18. 5.1. Parameter Routes
  19. 5.2. Wildcard Routes
  20. 5.3. Regular Expression Routes
  21. 6. Event System Integration
  22. 6.1. Priority-Based Execution
  23. 6.2. Event Hooks
  24. 7. Type Parameters
  25. 8. Examples
  26. 8.1. Basic REST API
  27. 8.2. Middleware Pattern
  28. 8.3. File-Based Routing
  29. 9. Build Integration

Router keeps route matching, route composition, and route metadata in one reusable layer that the server can build on.

The Router class provides event-driven routing capabilities with pattern matching and parameter extraction for building flexible web applications and APIs.

TypeScript
import { Router } from '@stackpress/ingest';

const router = new Router<RequestType, ResponseType>();
  1. Properties
  2. Methods
  3. Routing Interfaces
  4. Automatic Router Detection
  5. Route Patterns
  6. Event System Integration
  7. Type Parameters
  8. Examples
  9. Build Integration

1. Properties

The following properties are available when instantiating a Router.

PropertyTypeDescription
actionActionRouter<R, S, this>Traditional Express.js-like routing interface
entryEntryRouter<R, S, this>File-based routing interface
importImportRouter<R, S, this>Dynamic import routing interface
viewViewRouter<R, S, this>Template-based routing interface
entriesMapMap of entry-based routes
expressionsMapMap of route expressions and patterns
importsMapMap of import-based routes
listenersobjectEvent listener map
routesMapMap of route definitions
viewsMapMap of view-based routes

2. Methods

The following methods are available when instantiating a Router.

2.1. HTTP Method Routing

The following examples show how to define routes for different HTTP methods.

TypeScript
// GET route
router.get('/users', ({ req, res }) => {
  res.setJSON({ users: [] });
});

// POST route
router.post('/users', ({ req, res }) => {
  const userData = req.data();
  res.setJSON({ user: userData }, 201);
});

// PUT route
router.put('/users/:id', ({ req, res }) => {
  const userId = req.data('id');
  res.setJSON({ id: userId, updated: true });
});

// DELETE route
router.delete('/users/:id', ({ req, res }) => {
  const userId = req.data('id');
  res.setJSON({ id: userId, deleted: true });
});

// PATCH route
router.patch('/users/:id', ({ req, res }) => {
  const userId = req.data('id');
  res.setJSON({ id: userId, patched: true });
});

// OPTIONS route
router.options('/users', ({ res }) => {
  res.setJSON({ allow: ['GET', 'POST', 'PATCH', 'OPTIONS'] });
});

// HEAD route
router.head('/users', ({ res }) => {
  res.code = 200;
});

// CONNECT and TRACE are also available
router.connect('/proxy', ({ res }) => {
  res.code = 200;
});

router.trace('/users', ({ req, res }) => {
  res.setJSON({ trace: req.url.pathname });
});

// Handle any method
router.all('/health', ({ req, res }) => {
  res.setJSON({ status: 'healthy' });
});

Parameters

ParameterTypeDescription
pathstringRoute path with optional parameters
actionAnyRouterAction<R, S, this>Route handler function
prioritynumberPriority level (default: 0). Can be negative. Higher numbers run first, then ties follow definition order.

Returns

The Router instance to allow method chaining.

2.2. Generic Route Definition

The following example shows how to define routes with specific HTTP methods.

TypeScript
router.route('GET', '/users/:id', ({ req, res }) => {
  const userId = req.data('id');
  res.setJSON({ id: userId });
});

router.route('PATCH', '/users/:id', ({ req, res }) => {
  const userId = req.data('id');
  res.setJSON({ id: userId, patched: true });
});

Parameters

ParameterTypeDescription
methodMethodHTTP method (GET, POST, PUT, DELETE, etc.)
pathstringRoute path with optional parameters
actionAnyRouterAction<R, S, this>Route handler function
prioritynumberPriority level (default: 0). Can be negative. Higher numbers run first, then ties follow definition order.

Returns

The Router instance to allow method chaining.

2.3. Event Handling

The following example shows how to add event listeners for reactive programming.

TypeScript
// Listen to all requests
router.on('request', ({ req, res }) => {
  console.log(`${req.method} ${req.url.pathname}`);
});

// Listen to specific route events
router.on('GET /api/users', ({ req, res }) => {
  console.log('Users API accessed');
});

// Pattern-based event matching
router.on(/^GET \/api\/.*$/, ({ req, res }) => {
  console.log('API endpoint accessed');
});

Parameters

ParameterTypeDescription
event`string\RegExp`Event name or pattern
actionAnyRouterAction<R, S, this>Event handler function
prioritynumberPriority level (default: 0). Can be negative. Higher numbers run first, then ties follow definition order.

Returns

The Router instance to allow method chaining.

2.4. Event Emission

The following example shows how to emit events manually for custom workflows.

TypeScript
const req = router.request({ url: 'http://localhost/test' });
const res = router.response();

const status = await router.emit('custom-event', req, res);
console.log(status.code); // 200, 404, etc.

Parameters

ParameterTypeDescription
eventstringEvent name to emit
reqRequest<R>Request object
resResponse<S>Response object

Returns

A promise that resolves to a Status object indicating success or failure.

2.5. Route Resolution

The following examples show how to resolve routes and get response data.

TypeScript
// Resolve by method and path
const response = await router.resolve('GET', '/users/123');

// Resolve by event name
const response = await router.resolve('user-created', userData);

// With custom request data
const response = await router.resolve('POST', '/users', {
  name: 'John',
  email: '[email protected]'
});

Parameters for route resolution

ParameterTypeDescription
method`Method\'*'`HTTP method
pathstringRoute path
request`Request<R>\Record<string, any>`Request data (optional)
responseResponse<S>Response object (optional)

Parameters for event resolution

ParameterTypeDescription
eventstringEvent name
request`Request<R>\Record<string, any>`Request data (optional)
responseResponse<S>Response object (optional)

Returns

A promise that resolves to a partial StatusResponse object.

2.6. Request and Response Creation

The following examples show how to create request and response objects.

TypeScript
// Create request
const req = router.request({
  url: 'http://example.com/api',
  method: 'POST',
  data: { name: 'John' },
  headers: { 'Content-Type': 'application/json' }
});

// Create response
const res = router.response({
  headers: { 'Content-Type': 'application/json' },
  data: { message: 'Success' }
});

Parameters for request

ParameterTypeDescription
initPartial<RequestOptions<R>>Request initialization options

Parameters for response

ParameterTypeDescription
initPartial<ResponseOptions<S>>Response initialization options

Returns

A new Request or Response instance.

2.7. Router Composition

The following example shows how to merge routes from other routers.

TypeScript
const apiRouter = new Router();
apiRouter.get('/api/users', handler);

const mainRouter = new Router();
mainRouter.use(apiRouter); // Merges routes and listeners

Parameters

ParameterTypeDescription
routerRouter<R, S>Another router to merge routes from

Returns

The Router instance to allow method chaining.

2.8. Controller Mounting

The following example shows how to mount decorated controllers onto a router.

TypeScript
import {
  Controller,
  Get,
  Router,
  type HttpAction
} from '@stackpress/ingest/http';

type HttpProps = Parameters<HttpAction>[0];

@Controller('/api')
class UserController {
  @Get('/users')
  public list({ res }: HttpProps) {
    res.setBody('text/plain', 'list');
  }
}

const router = new Router();
router.mount(UserController);

mount() accepts controller classes or controller instances and registers both route decorators and @On(...) listeners through the existing router APIs.

Parameters

ParameterTypeDescription
controllersControllerMountable[]Controller classes or controller instances to register on this router.

Returns

The Router instance to allow method chaining.

3. Automatic Router Detection

The Router class can automatically determine which routing interface to use based on the action type, providing a seamless development experience.

TypeScript
// Inline handler
router.get('/users', ({ req, res }) => { /* handler */ });

// Entry route
router.get('/users/:id', './routes/users/get.js');

// Import route
router.get('/users', () => import('./routes/users.js'));

// View route
router.get('/profile', './views/profile.hbs');

The router analyzes the provided action and selects the appropriate interface:

  • Function with parameters → Action Router (traditional routing)
  • Parameterless function → Import Router (lazy loading)
  • String path ending in a template file → View Router
  • String path ending in a route module → Entry Router

This automatic detection eliminates the need to explicitly specify which routing interface to use, making the API more intuitive and reducing boilerplate code.

You can still call the explicit interfaces directly when you want the intent to be obvious in code:

TypeScript
router.action.get('/users', ({ res }) => res.setResults([]));
router.entry.get('/users/:id', './routes/users/get.js');
router.import.get('/users', () => import('./routes/users.js'));
router.view.get('/profile', './views/profile.hbs');

4. Routing Interfaces

The Router class provides four different routing interfaces for maximum flexibility in how you define and organize your routes.

4.1. Action Router (Traditional)

Express.js-like routing with inline handlers for immediate function execution.

TypeScript
router.action.get('/users', ({ req, res }) => {
  res.setJSON({ users: [] });
});

router.action.post('/users', ({ req, res }) => {
  const userData = req.data();
  res.setJSON(userData, 201);
});

4.2. Entry Router (File-based)

File-based routing that loads handlers from external files for better organization.

TypeScript
router.entry.get('/users', './routes/users.js');
router.entry.post('/users', './routes/create-user.js');

The target file should export a default function:

TypeScript
// routes/users.js
export default function handler({ req, res }) {
  res.setJSON({ users: [] });
}

4.3. Import Router (Lazy Loading)

Dynamic import routing for on-demand loading, route-aware tooling, and build boundaries.

TypeScript
router.import.get('/users', () => import('./routes/users.js'));
router.import.post('/users', () => import('./routes/create-user.js'));

4.4. View Router (Template-based)

Template-based routing for rendering views and server-side templates.

TypeScript
router.view.get('/users', './views/users.hbs');
router.view.get('/profile', './views/profile.hbs');

5. Route Patterns

The Router supports various route patterns for flexible URL matching and parameter extraction.

5.1. Parameter Routes

Extract dynamic segments from URLs using named parameters.

TypeScript
// Single parameter
router.get('/users/:id', ({ req, res }) => {
  const userId = req.data('id');
  res.setJSON({ id: userId });
});

// Multiple parameters
router.get('/users/:userId/posts/:postId', ({ req, res }) => {
  const userId = req.data('userId');
  const postId = req.data('postId');
  res.setJSON({ userId, postId });
});

5.2. Wildcard Routes

Handle dynamic paths with wildcard matching for flexible routing.

TypeScript
// Single wildcard
router.get('/files/*', ({ req, res }) => {
  const filename = req.data('0'); // First wildcard match
  res.setJSON({ filename });
});

// Catch-all wildcard
router.get('/static/**', ({ req, res }) => {
  const path = req.data('0'); // Full wildcard match
  res.setJSON({ path });
});

5.3. Regular Expression Routes

Use regular expressions for complex pattern matching requirements.

TypeScript
// Regex pattern matching
router.on(/^GET \/api\/v(\d+)\/users$/, ({ req, res }) => {
  const version = req.event?.data.args[0]; // Captured group
  res.setJSON({ version, users: [] });
});

6. Event System Integration

The Router is built on a powerful event system that enables reactive programming and middleware patterns.

6.1. Priority-Based Execution

Control the order of event handler execution using priority levels.

TypeScript
// Higher priority executes first. Negative priorities are valid too.
router.on('request', middleware1, 10);
router.on('request', middleware2, 5);
router.on('request', middleware3, 1);
router.on('request', middleware4, -10);

6.2. Event Hooks

Implement before and after hooks for cross-cutting concerns.

TypeScript
// Before hook
router.action.before = async (event) => {
  console.log('Before:', event.event);
  return true; // Continue execution
};

// After hook
router.action.after = async (event) => {
  console.log('After:', event.event);
};

7. Type Parameters

The Router class accepts two generic type parameters for type safety.

ParameterDefaultDescription
RunknownRequest resource type
SunknownResponse resource type
TypeScript
interface UserRequest {
  userId: string;
  permissions: string[];
}

interface ApiResponse {
  data: any;
  meta: { timestamp: number };
}

const router = new Router<UserRequest, ApiResponse>();

8. Examples

The following examples demonstrate common Router usage patterns and best practices.

8.1. Basic REST API

TypeScript
const router = new Router();

// List users
router.get('/users', ({ req, res }) => {
  const users = [
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane' }
  ];
  res.setJSON({ users });
});

// Get user by ID
router.get('/users/:id', ({ req, res }) => {
  const userId = req.data('id');
  const user = { id: userId, name: 'John' };
  res.setJSON({ user });
});

// Create user
router.post('/users', async ({ req, res }) => {
  await req.load();
  const userData = req.data();
  const user = { id: Date.now(), ...userData };
  res.setJSON({ user }, 201);
});

8.2. Middleware Pattern

TypeScript
const router = new Router();

// Global middleware
router.on('request', ({ req, res }) => {
  console.log(`${req.method} ${req.url.pathname}`);
  return true; // Continue processing
}, 10);

// Authentication middleware
router.on('request', ({ req, res }) => {
  const token = req.headers.get('authorization');
  if (!token && req.url.pathname.startsWith('/protected')) {
    res.setError('Authentication required', {}, [], 401);
    return false; // Stop processing
  }
  return true;
}, 5);

// Protected route
router.get('/protected/data', ({ req, res }) => {
  res.setJSON({ data: 'secret information' });
});

8.3. File-Based Routing

TypeScript
const router = new Router();

// Entry-based routes
router.entry.get('/api/users', './routes/users.js');
router.entry.post('/api/users', './routes/create-user.js');

// Import-based routes for lazy loading and tooling boundaries
router.import.get('/api/products', () => import('./routes/products.js'));
router.import.get('/api/orders', () => import('./routes/orders.js'));

// View-based routes for templates
router.view.get('/users', './views/users.hbs');
router.view.get('/profile', './views/profile.hbs');

9. Build Integration

The Router exposes routing information that can be used by bundlers and build tools for optimization.

TypeScript
const router = new Router();
router.import.get('/users', () => import('./routes/users.js'));
router.import.get('/posts', () => import('./routes/posts.js'));
router.entry.get('/admin/users', './routes/admin/users.js');
router.view.get('/profile', './views/profile.hbs');

// Access build information
console.log(router.routes);      // Route definitions
console.log(router.imports);     // Dynamic imports
console.log(router.entries);     // File entries
console.log(router.views);       // View templates
console.log(router.expressions); // Route patterns

This information can be used to generate static route manifests, pre-bundle route modules, package route-aware deployments, and produce other build artifacts without reverse-engineering the application at runtime.