The Doh Router seamlessly integrates traditional HTTP handling with Socket.IO, providing a unified interface for both protocols that offers:
Configuration is managed through the pod.yaml
file. Key settings include:
express_config:
# Basic HTTP/HTTPS configuration
hostname: example.com
strict_hostnames: false
port: 3000
ssl_port: 443
ssl_info:
key: /path/to/key.pem
cert: /path/to/cert.pem
# Tunneling configuration
tunnel_ssl_port: 8443
tunnel_remote_url: https://tunnel.example.com:8443
# Host forwarding rules (path patterns to external URLs)
host_forwards:
'/api/external/*': 'https://api.external-service.com'
# Array of paths to be ignored by the default file exposure
ignore_paths:
- .doh
- .git
- pod.yaml
- .env
- '*.env'
# Security configurations
cors_hosts: ['trusted-origin.com']
cors_wildcard: false
only_use_defined_hosts: false
# Content Security Policy settings
helmet: true
content_security_policy: false
use_content_security_policy_defaults: true
# Max age for caching in seconds
cache_max_age: 10
# Maximum size for request bodies
body_size_limit: '50mb'
# Configuration for rate limiting
rate_limit:
windowMs: 900000 # 15 minutes
max: 100 # Limit each IP to 100 requests per windowMs
# Request logging
log_routes: false
New in Doh
The Doh Router now prioritizes file-based routing over code-based route registration. This means:
index.html
), that file is served directly..html
file or any directory containing an index.html
.Router.AddRoute
or similar APIs.This approach enables a more intuitive, filesystem-driven development experience, while still supporting advanced API and dynamic routes via code.
Routing Flow Summary:
The Router includes a built-in analytics system that tracks route usage, performance metrics, and custom events:
// Enable analytics in pod configuration
Doh.Pod('express_router', {
browser_pod: {
analytics: {
enabled: true,
excludePaths: ['/health', '/metrics', '*.js', '*.css', '*.map', '*.ico'],
sampleRate: 1.0
}
}
})
// Register analytics event handlers
Router.onAnalytics('route', function(routeData) {
console.log(`Route accessed: ${routeData.path} (${routeData.method})`);
// Process route analytics data (response time, client info, etc.)
});
Router.onAnalytics('event', function(eventData) {
console.log(`Custom event: ${eventData.event}`);
// Process custom event data
});
// Track custom events from client or server
Router.trackEvent('user_action', {
action: 'click',
elementId: 'submit-button',
page: '/checkout'
});
The analytics system automatically collects:
Doh Router unifies HTTP and Socket.IO routing, allowing a single route definition to handle both protocols seamlessly:
// We depend on express_router to ensure Router is properly initialized
Doh.Module('mymodule_routes', ['express_router'], function(Router) {
Router.AddRoute('/api/data', async function(data, req, res, callback) {
// 'data' contains the request payload, regardless of HTTP method or Socket.IO
// 'req' and 'res' are provided for HTTP requests
// 'callback' is used for Socket.IO responses
const result = await processData(data);
// Use SendJSON for both HTTP and Socket.IO responses
Router.SendJSON(res, result, callback);
});
});
Note: With the new routing priority, code-based route registration is now secondary to file-based routing. If a request matches an HTML file or directory, that file is served first. Only unmatched requests are handled by registered routes.
Best Practice: Use file-based routing for static and template-driven pages, and code-based routes for APIs, dynamic endpoints, or advanced logic.
The Router implements sophisticated conflict detection to prevent ambiguous routing:
// These routes would conflict (exact match)
Router.AddRoute('/users', handleUsers);
Router.AddRoute('/users', handleUsersAgain); // Will log a conflict warning
// These won't conflict (static path vs wildcard)
Router.AddRoute('/users/:id', handleUserById);
Router.AddRoute('/users/profile', handleUserProfile); // Takes precedence due to static path
// These would conflict (wildcards in same position)
Router.AddRoute('/products/:category', handleCategory);
Router.AddRoute('/products/:id', handleProductId); // Will log a conflict warning
The conflict detection follows these rules:
/
) only conflicts with another base pathDoh Router emulates HTTP requests over WebSockets, allowing for protocol-agnostic APIs:
// Client-side (using Socket.IO)
socket.emit('/api/data', { some: 'data' }, (response) => {
console.log(response); // Handled just like an HTTP response
});
// Server-side (same route handling both HTTP and Socket.IO)
Router.AddRoute('/api/data', function(data, req, res, callback) {
// Process data...
Router.SendJSON(res, { result: 'success' }, callback);
});
The socket connection automatically:
The Router provides several methods for sending responses:
// Send JSON response (works for both HTTP and Socket.IO)
Router.SendJSON(res, { result: 'success' }, callback);
// Send HTML response with template processing
Router.SendHTML(res, 'my_module', ['dependency1', 'dependency2'], 'Page Title', 'path/to/template.html', customHeadContent);
// Send raw content with optional templating
Router.SendContent(res, '<h1>Hello World</h1>', {
title: 'Custom Page',
head: '<link rel="stylesheet" href="/styles.css">',
dependencies: ['my_dependency']
});
Doh Router provides advanced forwarding capabilities:
// Simple forwarding
Router.AddHostForward('/api/external/*', 'https://api.example.com');
// Forwarding with request/response interceptor
Router.AddHostForward('/api/transform/*', 'https://backend.example.com',
async function(response, req, res, callback, requestOptions) {
// Transform the response before sending it back
// This could modify headers, data, or perform other operations
return response;
}
);
The tunneling feature enables secure remote access to local development environments:
# Server configuration (public-facing)
express_config:
tunnel_ssl_port: 8443
ssl_info:
key: /path/to/key.pem
cert: /path/to/cert.pem
# Client configuration (private development environment)
express_config:
tunnel_remote_url: https://tunnel.example.com:8443
This creates a secure bidirectional tunnel between environments:
tunnel_ssl_port
tunnel_remote_url
The Router includes comprehensive security features:
// Global middleware for all routes
Router.AddMiddleware('*', Users.parseToken);
// Path-specific middleware
Router.AddMiddleware('/admin/*', [
Users.requireAuth,
adminAccessCheck
]);
// Route-specific middleware
Router.AddRoute('/protected', [Users.requireAuth],
function(data, req, res, callback) {
// Protected route logic
Router.SendJSON(res, { protected: 'data' }, callback);
});
Additional security features include:
// Create custom middleware
const loggingMiddleware = (req, res, next) => {
console.log(`Request: ${req.method} ${req.url}`);
const startTime = Date.now();
// Measure response time
res.on('finish', () => {
const duration = Date.now() - startTime;
console.log(`Response: ${res.statusCode} (${duration}ms)`);
});
next();
};
// Apply middleware globally for specific paths
Router.AddMiddleware('/api/*', loggingMiddleware);
// Stack middleware for specific routes
Router.AddRoute('/important', [
loggingMiddleware,
authMiddleware,
validationMiddleware
], handleImportantRoute);
// Forward different API paths to specialized microservices
Router.AddHostForward('/api/users/*', 'https://user-service.internal');
Router.AddHostForward('/api/products/*', 'https://product-service.internal');
Router.AddHostForward('/api/orders/*', 'https://order-service.internal');
// Forward with request transformation
Router.AddHostForward('/legacy/*', 'https://legacy-system.internal',
async function(response, req, res, callback, requestOptions) {
// Convert legacy response format to modern API format
const transformedData = transformLegacyResponse(await response.data);
response.data = transformedData;
return response;
}
);
This creates a protocol-agnostic API that works identically over HTTP and WebSockets, making it ideal for both traditional web applications and real-time interactive experiences.
The Doh Router includes a built-in Hot Module Replacement (HMR) system that enables live updates of HTML content without requiring page refreshes. All HTML files served through file-based routing are automatically equipped with HMR capabilities.
Doh.processHtml
function in the express server automatically injects HMR scripts into all HTML responses:// HMR script injected after <body> tag
const hmrScript = `
<script type="module">
import "${Doh.hostUrlString()}/doh_js/deploy.js";
await Doh.load('hmr');
await Doh.live_html('${DohPath.DohSlash(filepath)}');
</script>`;
When creating custom HTML templates, you don't need to add any special code - the HMR functionality is automatically injected. The system works with:
This creates a streamlined development workflow where changes to HTML files are instantly reflected in the browser.
The Doh Router includes built-in image resizing capabilities that can be accessed through URL parameters. This feature allows for dynamic image resizing without requiring pre-processing or storing multiple versions of the same image.
Image size presets are configured in the pod.yaml
file under express_config
:
express_config:
image_size_presets:
icon: { width: 32, height: 32, fit: 'cover' }
'small-thumb': { width: 64, height: 64, fit: 'cover' }
'large-thumb': { width: 128, height: 128, fit: 'cover' }
smaller: { width: 240, fit: 'inside' }
small: { width: 320, fit: 'inside' }
medium: { width: 640, fit: 'inside' }
large: { width: 1024, fit: 'inside' }
Each preset can specify:
width
: Target width in pixelsheight
: Target height in pixels (optional)fit
: Resize fitting method ('cover', 'contain', 'inside', 'outside', 'fill')To resize an image, simply add the size
parameter to the image URL:
<!-- Original image -->
<img src="/images/photo.jpg">
<!-- Resized versions -->
<img src="/images/photo.jpg?size=icon">
<img src="/images/photo.jpg?size=small-thumb">
<img src="/images/photo.jpg?size=large-thumb">
<img src="/images/photo.jpg?size=small">
<img src="/images/photo.jpg?size=medium">
<img src="/images/photo.jpg?size=large">
The image resizing feature supports the following formats:
You can extend or override the default size presets in your pod.yaml
:
express_config:
image_size_presets:
# Override existing presets
icon: { width: 48, height: 48, fit: 'cover' }
# Add new presets
'hero': { width: 1920, height: 1080, fit: 'cover' }
'card': { width: 400, height: 300, fit: 'cover' }
'avatar': { width: 150, height: 150, fit: 'cover' }
# Custom aspect ratio
'banner': { width: 1200, height: 400, fit: 'cover' }
# Maintain aspect ratio
'sidebar': { width: 300, fit: 'inside' }
<!-- Responsive images with different sizes -->
<picture>
<source media="(max-width: 320px)" srcset="/images/hero.jpg?size=small">
<source media="(max-width: 640px)" srcset="/images/hero.jpg?size=medium">
<source media="(max-width: 1024px)" srcset="/images/hero.jpg?size=large">
<img src="/images/hero.jpg?size=hero" alt="Hero image">
</picture>
<!-- Thumbnail grid -->
<div class="thumbnails">
<img src="/images/item1.jpg?size=small-thumb" alt="Item 1">
<img src="/images/item2.jpg?size=small-thumb" alt="Item 2">
<img src="/images/item3.jpg?size=small-thumb" alt="Item 3">
</div>
<!-- User avatars -->
<img src="/images/avatar.jpg?size=icon" alt="User avatar">
This feature provides a flexible and efficient way to handle image resizing without requiring pre-processing or storing multiple versions of the same image.
New Feature - Inline Module Loading
The Doh Router now supports bundling all required Doh modules directly into HTML responses, eliminating the need for separate network requests to load dependencies. This provides significant performance improvements, especially for production deployments.
Enable bundling in your pod.yaml
:
express_config:
bundle_doh_modules: true # Default: false
bundle_manifests_with_doh_modules: true # Default: true - bundles manifest files inline
bundle_scripts_with_doh_modules: false # Default: false - bundles script dependencies inline
bundle_imports_with_doh_modules: false # Default: false - bundles import dependencies inline (requires esbuild_options.splitting = false)
The Doh Router provides granular control over what gets bundled inline:
bundle_doh_modules
(default: false
)
bundle_manifests_with_doh_modules
(default: true
)
bundle_doh_modules
, and is generally safe to leave set at all timesbundle_scripts_with_doh_modules
(default: false
)
.js
files) from package load blocksbundle_imports_with_doh_modules
(default: false
)
esbuild_options.splitting = false
to work properlyWhen enabled, the Router automatically:
Router.SendHTML()
callsbrowser??
, nodejs??
) during bundlingPerformance:
Deployment:
Developer Experience:
Recommended bundling configurations:
Production (Performance Focus):
express_config:
bundle_doh_modules: true
bundle_manifests_with_doh_modules: true
bundle_scripts_with_doh_modules: false # Test carefully before enabling
bundle_imports_with_doh_modules: false # Use only if needed
Development (Flexibility Focus):
express_config:
bundle_doh_modules: false
bundle_manifests_with_doh_modules: true # Safe to enable always
bundle_scripts_with_doh_modules: false
bundle_imports_with_doh_modules: false
Aggressive Bundling (Maximum Inlining):
express_config:
bundle_doh_modules: true
bundle_manifests_with_doh_modules: true
bundle_scripts_with_doh_modules: true
bundle_imports_with_doh_modules: true
esbuild_options:
splitting: false # Required for bundle_imports_with_doh_modules
Best suited for:
Traditional loading preferred for:
The bundling system respects all existing Doh loading conventions:
browser??
, nodejs??
) is properly filteredThis feature bridges the gap between Doh's flexible module system and the performance characteristics of traditional bundled applications.
The route conflict detection system in Doh identifies potential conflicts between registered* routes while allowing for flexible and intuitive URL structuring. It prevents ambiguous routing scenarios while permitting rich, nested route structures.
*file-based routes always supercede registered routing
When adding a new route, the system checks it against all existing routes to detect any conflicts. If a conflict is found, the system prevents the new route from being added and reports the conflict.
Exact Match Conflict
Static Part Precedence
Wildcard Conflict
*
and parameter placeholders (e.g., :id
).Longer Path Precedence
Base Path Conflict
/
) only conflicts with another base path (/
)./users
vs /users
/users
vs /users/profile
/users/*
vs /users/account
/users/:id
vs /users/*
/users/:id/posts
vs /users/*/comments
/users/profile
vs /users/:id
/users/*
vs /users/*/settings
/users/:id/profile
vs /users/*/settings
/
vs /users
/users/:id
vs /users/:name
/products/:category/*
vs /products/:id
/blog/:year/:month
vs /blog/:slug
/api/v1/*
vs /api/v2/*
/search
vs /search/:query
/items/:id
vs /items/new
/users/:id/posts/:postId
vs /users/active/posts/*
:id
)/products/:category/:id
vs /products/featured/:id
:category
)/articles/*
vs /articles/:id/edit
/users/*
vs /users/:id/profile
/api/v1/:resource/*
vs /api/v1/:resource/:id
/
vs /