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.
import { Router } from '@stackpress/ingest';
const router = new Router<RequestType, ResponseType>();- Properties
- Methods
- Routing Interfaces
- Automatic Router Detection
- Route Patterns
- Event System Integration
- Type Parameters
- Examples
- Build Integration
The following properties are available when instantiating a Router.
| Property | Type | Description |
|---|---|---|
action |
ActionRouter<R, S, this> |
Traditional Express.js-like routing interface |
entry |
EntryRouter<R, S, this> |
File-based routing interface |
import |
ImportRouter<R, S, this> |
Dynamic import routing interface |
view |
ViewRouter<R, S, this> |
Template-based routing interface |
entries |
Map |
Map of entry-based routes |
expressions |
Map |
Map of route expressions and patterns |
imports |
Map |
Map of import-based routes |
listeners |
object |
Event listener map |
routes |
Map |
Map of route definitions |
views |
Map |
Map of view-based routes |
The following methods are available when instantiating a Router.
The following examples show how to define routes for different HTTP methods.
// 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
| Parameter | Type | Description |
|---|---|---|
path |
string |
Route path with optional parameters |
action |
AnyRouterAction<R, S, this> |
Route handler function |
priority |
number |
Priority level (default: 0). Can be negative. Higher numbers run first, then ties follow definition order. |
Returns
The Router instance to allow method chaining.
The following example shows how to define routes with specific HTTP methods.
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
| Parameter | Type | Description |
|---|---|---|
method |
Method |
HTTP method (GET, POST, PUT, DELETE, etc.) |
path |
string |
Route path with optional parameters |
action |
AnyRouterAction<R, S, this> |
Route handler function |
priority |
number |
Priority level (default: 0). Can be negative. Higher numbers run first, then ties follow definition order. |
Returns
The Router instance to allow method chaining.
The following example shows how to add event listeners for reactive programming.
// 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
| Parameter | Type | Description |
|---|---|---|
event |
string|RegExp |
Event name or pattern |
action |
AnyRouterAction<R, S, this> |
Event handler function |
priority |
number |
Priority level (default: 0). Can be negative. Higher numbers run first, then ties follow definition order. |
Returns
The Router instance to allow method chaining.
The following example shows how to emit events manually for custom workflows.
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
| Parameter | Type | Description |
|---|---|---|
event |
string |
Event name to emit |
req |
Request<R> |
Request object |
res |
Response<S> |
Response object |
Returns
A promise that resolves to a Status object indicating success or failure.
The following examples show how to resolve routes and get response data.
// 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: 'john@example.com'
});Parameters for route resolution
| Parameter | Type | Description |
|---|---|---|
method |
Method|'*' |
HTTP method |
path |
string |
Route path |
request |
Request<R>|Record<string, any> |
Request data (optional) |
response |
Response<S> |
Response object (optional) |
Parameters for event resolution
| Parameter | Type | Description |
|---|---|---|
event |
string |
Event name |
request |
Request<R>|Record<string, any> |
Request data (optional) |
response |
Response<S> |
Response object (optional) |
Returns
A promise that resolves to a partial StatusResponse object.
The following examples show how to create request and response objects.
// 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
| Parameter | Type | Description |
|---|---|---|
init |
Partial<RequestOptions<R>> |
Request initialization options |
Parameters for response
| Parameter | Type | Description |
|---|---|---|
init |
Partial<ResponseOptions<S>> |
Response initialization options |
Returns
A new Request or Response instance.
The following example shows how to merge routes from other routers.
const apiRouter = new Router();
apiRouter.get('/api/users', handler);
const mainRouter = new Router();
mainRouter.use(apiRouter); // Merges routes and listenersParameters
| Parameter | Type | Description |
|---|---|---|
router |
Router<R, S> |
Another router to merge routes from |
Returns
The Router instance to allow method chaining.
The following example shows how to mount decorated controllers onto a router.
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
| Parameter | Type | Description |
|---|---|---|
controllers |
ControllerMountable[] |
Controller classes or controller instances to register on this router. |
Returns
The Router instance to allow method chaining.
The Router class can automatically determine which routing interface to use based on the action type, providing a seamless development experience.
// 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:
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');The Router class provides four different routing interfaces for maximum flexibility in how you define and organize your routes.
Express.js-like routing with inline handlers for immediate function execution.
router.action.get('/users', ({ req, res }) => {
res.setJSON({ users: [] });
});
router.action.post('/users', ({ req, res }) => {
const userData = req.data();
res.setJSON(userData, 201);
});File-based routing that loads handlers from external files for better organization.
router.entry.get('/users', './routes/users.js');
router.entry.post('/users', './routes/create-user.js');The target file should export a default function:
// routes/users.js
export default function handler({ req, res }) {
res.setJSON({ users: [] });
}Dynamic import routing for on-demand loading, route-aware tooling, and build boundaries.
router.import.get('/users', () => import('./routes/users.js'));
router.import.post('/users', () => import('./routes/create-user.js'));Template-based routing for rendering views and server-side templates.
router.view.get('/users', './views/users.hbs');
router.view.get('/profile', './views/profile.hbs');The Router supports various route patterns for flexible URL matching and parameter extraction.
Extract dynamic segments from URLs using named parameters.
// 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 });
});Handle dynamic paths with wildcard matching for flexible routing.
// 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 });
});Use regular expressions for complex pattern matching requirements.
// 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: [] });
});The Router is built on a powerful event system that enables reactive programming and middleware patterns.
Control the order of event handler execution using priority levels.
// 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);Implement before and after hooks for cross-cutting concerns.
// 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);
};The Router class accepts two generic type parameters for type safety.
| Parameter | Default | Description |
|---|---|---|
R |
unknown |
Request resource type |
S |
unknown |
Response resource type |
interface UserRequest {
userId: string;
permissions: string[];
}
interface ApiResponse {
data: any;
meta: { timestamp: number };
}
const router = new Router<UserRequest, ApiResponse>();The following examples demonstrate common Router usage patterns and best practices.
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);
});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' });
});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');The Router exposes routing information that can be used by bundlers and build tools for optimization.
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 patternsThis 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.