Overview
The LinearRouter is the simplest router implementation in Hono. It stores routes in a flat array and matches them sequentially using string operations and minimal RegExp. It has virtually no build overhead and supports all routing patterns.
How It Works
LinearRouter uses a direct, no-frills approach:
Storage : Routes stored as simple [method, path, handler] tuples
No Preprocessing : Routes are stored exactly as provided
Sequential Matching : Each request tests routes one by one
String Operations : Uses string indexOf and character code checks for speed
Minimal RegExp : Only uses RegExp for complex parameter patterns
Algorithm Details
Routes stored in a flat array, no data structures
Static routes matched with simple string equality
Wildcards matched with indexOf() operations
Parameters extracted using string slicing and indexOf
Parameter patterns compile RegExp on-demand with the d flag for indices
Optional parameters handled by creating multiple route entries
Combines wildcards and parameters (unlike some other routers)
All Routes O(n) - Sequential search, no optimization
Build Time O(1) - Zero preprocessing per route
Memory Minimal - Just the route array
Startup Time Instant - No compilation or tree building
When to Use
Serverless/Lambda functions with cold starts
Small applications with < 20 routes
Applications where startup time is critical
Development and testing (fast iteration)
Learning Hono (simple, predictable behavior)
Edge functions with minimal compute time
Routes with complex patterns (wildcards + parameters)
Large applications with 100+ routes
High-traffic APIs (many requests per second)
Applications with mostly static routes (no advantage)
When maximum request throughput is required
Configuration
To use the LinearRouter, specify it when creating your Hono instance:
import { Hono } from 'hono'
import { LinearRouter } from 'hono/router/linear-router'
const app = new Hono ({ router: new LinearRouter () })
Usage Examples
Basic Routing
import { Hono } from 'hono'
import { LinearRouter } from 'hono/router/linear-router'
const app = new Hono ({ router: new LinearRouter () })
// Static routes
app . get ( '/' , ( c ) => c . text ( 'Home' ))
app . get ( '/about' , ( c ) => c . text ( 'About' ))
app . get ( '/contact' , ( c ) => c . text ( 'Contact' ))
// Dynamic routes
app . get ( '/users/:id' , ( c ) => {
const id = c . req . param ( 'id' )
return c . json ({ userId: id })
})
// Multiple parameters
app . get ( '/posts/:postId/comments/:commentId' , ( c ) => {
const { postId , commentId } = c . req . param ()
return c . json ({ postId , commentId })
})
Advanced Patterns
LinearRouter supports all routing patterns:
// Wildcards
app . get ( '/static/*' , ( c ) => c . text ( 'Static files' ))
// Wildcards in middle of path
app . get ( '/api/*/status' , ( c ) => c . text ( 'Status' ))
// Parameters with patterns
app . get ( '/users/:id{[0-9]+}' , ( c ) => {
const id = c . req . param ( 'id' ) // Guaranteed numeric
return c . json ({ userId: parseInt ( id ) })
})
// Complex patterns
app . get ( '/files/:name{.+ \\ .(jpg|png|gif)}' , ( c ) => {
const name = c . req . param ( 'name' )
return c . json ({ filename: name })
})
Wildcard + Parameter Combinations
Unlike RegExpRouter, LinearRouter supports combining wildcards with parameters:
// ✅ Supported by LinearRouter
app . get ( '/users/:id/*' , ( c ) => {
const id = c . req . param ( 'id' )
return c . json ({ userId: id , sub: c . req . path })
})
// Complex combinations
app . get ( '/orgs/:org/*/members/:id' , ( c ) => {
const { org , id } = c . req . param ()
return c . json ({ org , memberId: id })
})
Routes combining wildcards (*) with parameters (:name) will throw UnsupportedPathError. This is a known limitation.
Optional Parameters
// Optional parameter
app . get ( '/docs/:page?' , ( c ) => {
const page = c . req . param ( 'page' ) || 'index'
return c . json ({ page })
})
// Creates two routes internally:
// 1. /docs/:page
// 2. /docs
Catch-All Routes
// Match everything
app . get ( '*' , ( c ) => c . text ( 'Catch all' ))
app . get ( '/*' , ( c ) => c . text ( 'Catch all with slash' ))
// Both are treated specially for performance
How Matching Works
Matching Algorithm
LinearRouter optimizes for common cases:
// Conceptual matching algorithm
function match ( method : string , path : string ) {
const handlers = []
for ( const [ routeMethod , routePath , handler ] of this . #routes ) {
if ( routeMethod !== method && routeMethod !== 'ALL' ) {
continue // Skip wrong method
}
// Fast path: exact match or with trailing slash
if ( routePath === path || routePath + '/' === path ) {
handlers . push ([ handler , {}])
continue
}
// Special fast path: catch-all
if ( routePath === '*' || routePath === '/*' ) {
handlers . push ([ handler , {}])
continue
}
// Has wildcard but no parameters
if ( hasStar && ! hasLabel ) {
// Use string indexOf for fast wildcard matching
const parts = routePath . split ( '*' )
if ( matchesWildcardPattern ( path , parts )) {
handlers . push ([ handler , {}])
}
continue
}
// Has parameters but no wildcard
if ( hasLabel && ! hasStar ) {
// Extract parameters using string operations
const params = extractParams ( path , routePath )
if ( params ) {
handlers . push ([ handler , params ])
}
continue
}
// Has both parameters and wildcards
if ( hasLabel && hasStar ) {
throw new UnsupportedPathError () // Not supported
}
}
return [ handlers ]
}
Optimization Strategies
// LinearRouter uses character code checks for speed
if ( path . charCodeAt ( pos ) === 47 ) { // Check for '/' character
// Process path separator
}
if ( routePath . charCodeAt ( routePath . length - 1 ) === 42 ) { // Check for '*'
// Handle trailing wildcard
}
Static Routes
Wildcard Routes
Parameter Routes
// Best case: Early in route list + exact match
app . get ( '/' , handler ) // 1 iteration
app . get ( '/api' , handler ) // 2 iterations (if '/' checked first)
// Worst case: Last route
app . get ( '/zzz' , handler ) // n iterations
// Optimization: Put frequent routes first!
// Wildcards use indexOf operations
app . get ( '/static/*' , handler ) // O(m) string search
app . get ( '/api/*/status' , handler ) // O(m) per part
// Catch-all is optimized
app . get ( '*' , handler ) // O(1) check
// Parameters extract via string slicing
app . get ( '/users/:id' , handler ) // O(m) to find '/' positions
// With pattern: compiles RegExp on first match
app . get ( '/users/:id{[0-9]+}' , handler ) // O(m) + RegExp
Advanced Features
Parameter Pattern Matching
LinearRouter uses the RegExp d flag for efficient parameter extraction:
// Pattern: /users/:id{[0-9]+}
app . get ( '/users/:id{[0-9]+}' , handler )
// Internally creates:
const pattern = '[0-9]+'
const regex = new RegExp ( pattern , 'd' ) // 'd' flag for indices
const match = regex . exec ( restOfPath )
// Uses match.indices to extract exact parameter boundaries
value = path . slice ( ... match . indices [ 0 ])
Trailing Slash Handling
Automatically handles trailing slashes:
app . get ( '/users/:id' , handler )
// Both match:
// GET /users/123 ✓
// GET /users/123/ ✓
Method Matching
Method check happens before route matching for efficiency:
app . get ( '/api' , getHandler )
app . post ( '/api' , postHandler )
// POST /api
// 1. Check GET route: method mismatch → skip
// 2. Check POST route: method match → test path → match!
Optimization Tips
Maximize LinearRouter performance:
Order by frequency - Put most-hit routes first
Minimize route count - Fewer routes = faster matching
Use static paths - Faster than parameters or wildcards
Avoid complex patterns - Simple parameters are faster
Group by method - Method check is cheap
Optimization Example
// ✅ Optimized: Hot path first
app . get ( '/' , homeHandler ) // Most traffic
app . get ( '/api/health' , healthHandler ) // Health checks frequent
app . get ( '/api/users/:id' , getUserHandler ) // Common API call
app . get ( '/admin/*' , adminHandler ) // Rare
// ❌ Not optimized: Rare routes first
app . get ( '/admin/debug/logs/*' , logsHandler ) // Rarely accessed
app . get ( '/' , homeHandler ) // Should be first!
Use Cases
Perfect for Serverless
// AWS Lambda / Cloudflare Workers
import { Hono } from 'hono'
import { LinearRouter } from 'hono/router/linear-router'
// Zero cold-start overhead from routing
const app = new Hono ({ router: new LinearRouter () })
app . get ( '/' , ( c ) => c . text ( 'Hello' ))
app . get ( '/api/:resource' , ( c ) => c . json ({ resource: c . req . param ( 'resource' ) }))
export default app
// Starts instantly, no trie building or RegExp compilation!
Microservices with Few Routes
// Service with 5-10 routes
const app = new Hono ({ router: new LinearRouter () })
// Each microservice has limited routes
app . get ( '/health' , health )
app . get ( '/ready' , ready )
app . get ( '/metrics' , metrics )
app . get ( '/api/resource/:id' , getResource )
app . post ( '/api/resource' , createResource )
// Linear search is fine with so few routes!
Limitations
LinearRouter limitations:
O(n) for all routes - No fast path for static routes
No route deduplication - Duplicate routes both match
Order dependent - Route order matters significantly
No wildcard + parameter - Throws UnsupportedPathError
Unsupported Patterns
// ❌ Wildcard + parameter combination
app . get ( '/users/:id/*' , handler ) // Throws UnsupportedPathError
app . get ( '/:org/repos/*/issues/:id' , handler ) // Throws UnsupportedPathError
Comparison with Other Routers
Feature LinearRouter PatternRouter TrieRouter RegExpRouter Static routes O(n) O(n) O(n) O(1) Dynamic routes O(n) O(n) O(n×m) O(1) Pattern support Good Good Excellent Limited Startup time Instant Fast Fast Slow Memory Minimal Low Medium High Complexity Minimal Low Medium High Route limit < 20 < 50 < 200 Unlimited
When Linear is Better
Cold Start Optimization LinearRouter has zero build overhead, making it perfect for serverless functions where cold start time matters more than request speed.
Simple Applications For apps with < 20 routes, the performance difference is negligible and the simplicity is valuable.
Complex Patterns When you need wildcard + parameter combinations, LinearRouter is more capable than RegExpRouter.
Development Speed Zero compilation time means instant feedback during development.
Internal Architecture
Route Storage
class LinearRouter < T > {
name = 'LinearRouter'
#routes : [ string , string , T ][] = [] // [method, path, handler]
}
Add Method
add ( method : string , path : string , handler : T ) {
// Handle optional parameters
const paths = checkOptionalParameter ( path ) || [ path ]
// Add each variant
for ( const p of paths ) {
this . #routes . push ([ method , p , handler ])
}
}
Match Method
See “How Matching Works” section above for the detailed algorithm.
Source Code Reference
The LinearRouter implementation can be found at:
Router: src/router/linear-router/router.ts
See Also
PatternRouter Simple router using RegExp patterns
SmartRouter Automatically select the best router
Routing Guide Learn about choosing the right router
Performance Guide Optimize your Hono application