Skip to main content

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:
  1. Storage: Routes stored as simple [method, path, handler] tuples
  2. No Preprocessing: Routes are stored exactly as provided
  3. Sequential Matching: Each request tests routes one by one
  4. String Operations: Uses string indexOf and character code checks for speed
  5. 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)

Performance Characteristics

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)

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
}

Performance Characteristics by Route Type

// 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!

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:
  1. Order by frequency - Put most-hit routes first
  2. Minimize route count - Fewer routes = faster matching
  3. Use static paths - Faster than parameters or wildcards
  4. Avoid complex patterns - Simple parameters are faster
  5. 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

FeatureLinearRouterPatternRouterTrieRouterRegExpRouter
Static routesO(n)O(n)O(n)O(1)
Dynamic routesO(n)O(n)O(n×m)O(1)
Pattern supportGoodGoodExcellentLimited
Startup timeInstantFastFastSlow
MemoryMinimalLowMediumHigh
ComplexityMinimalLowMediumHigh
Route limit< 20< 50< 200Unlimited

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

Build docs developers (and LLMs) love