> ## Documentation Index
> Fetch the complete documentation index at: https://wemstudios.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Blaze Router: Sub-Routers, Trie Matching, and Patterns

> Blaze's TrieRouter delivers O(log n) route matching. Compose your app with sub-routers, wildcard patterns, and per-router middleware stacks.

Blaze's routing engine is built on a prefix trie (radix tree) rather than a linear array of compiled regular expressions. Every registered route pattern is inserted into the trie at startup. At request time, Blaze walks the trie to find the matching pattern in **O(log n)** time — regardless of how many routes your app defines. This eliminates the linear-scan performance cliff that Express's `path-to-regexp` approach suffers from as route counts grow.

***

## Route patterns

The TrieRouter supports the same expressive pattern syntax you'd expect from an Express-style framework, with one addition: regular expression constraints on named parameters.

| Pattern             | Example                                    | Description                                                                   |
| ------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
| Static segments     | `/users/me`                                | Exact match on every segment. Highest matching priority.                      |
| Named parameters    | `/users/:id`                               | Captures the segment value into `req.params.id`.                              |
| Optional parameters | `/posts/:slug?`                            | The segment is captured if present; the route also matches without it.        |
| Wildcards           | `/files/*`                                 | Matches all remaining segments. Captured into `req.params['*']`.              |
| Regexp constraints  | `/posts/:date([0-9]{4}-[0-9]{2}-[0-9]{2})` | Validates the captured segment against the regexp before accepting the match. |
| Multiple segments   | `/orgs/:org/repos/:repo`                   | Each `:param` is captured independently into `req.params`.                    |

```ts theme={null}
// Static — exact path only
app.get('/health', (req, res) => res.json({ status: 'ok' }))

// Named parameter
app.get('/users/:id', (req, res) => {
  res.json({ userId: req.params.id })
})

// Optional parameter — matches /posts and /posts/my-slug
app.get('/posts/:slug?', (req, res) => {
  if (req.params.slug) {
    res.json({ slug: req.params.slug })
  } else {
    res.json({ page: 'index' })
  }
})

// Wildcard — captures /files/images/photo.jpg → req.params['*'] = 'images/photo.jpg'
app.get('/files/*', async (req, res) => {
  const obj = await req.env.R2.get(req.params['*'])
  if (!obj) return res.status(404).json({ error: 'Not found' })
  res.stream((w) => obj.body.pipeTo(w))
})

// Regexp constraint — only matches ISO dates like /events/2024-12-01
app.get('/events/:date([0-9]{4}-[0-9]{2}-[0-9]{2})', (req, res) => {
  res.json({ date: req.params.date })
})

// Multiple parameters
app.get('/orgs/:org/repos/:repo', (req, res) => {
  const { org, repo } = req.params
  res.json({ org, repo })
})
```

<Note>
  Static segments always take priority over named parameters, which take
  priority over wildcards. This means `/users/me` always matches the static
  route even when `/users/:id` is also registered.
</Note>

***

## Sub-routers

`app.Router()` creates an independent sub-router with its own Layer stack. Mount it with `app.use(prefix, router)` to scope its routes and middleware under a URL prefix. Sub-routers automatically inherit the parent's `env` and `ctx` — you don't need to pass them manually.

<Steps>
  <Step title="Create a sub-router">
    Call `app.Router()` to instantiate the sub-router, then register routes on it just as you would on `app`:

    ```ts routes/users.ts theme={null}
    import { createApp } from 'blaze'
    import type { Env } from '../types/env'

    // Sub-routers are typed with the same Env as the parent
    const users = createApp<Env>().Router()

    users.get('/', async (req, res) => {
      const page  = Number(req.query.get('page') ?? 1)
      const limit = Number(req.query.get('limit') ?? 20)

      const { results } = await req.env.DB
        .prepare('SELECT id, name, email FROM users LIMIT ? OFFSET ?')
        .bind(limit, (page - 1) * limit)
        .all()

      res.json({ page, results })
    })

    users.get('/:id', async (req, res) => {
      const user = await req.env.DB
        .prepare('SELECT * FROM users WHERE id = ?')
        .bind(req.params.id)
        .first()

      if (!user) return res.status(404).json({ error: 'User not found' })
      res.json(user)
    })

    users.post('/', async (req, res) => {
      const { name, email } = await req.json<{ name: string; email: string }>()
      const result = await req.env.DB
        .prepare('INSERT INTO users (name, email) VALUES (?, ?) RETURNING id')
        .bind(name, email)
        .first()

      res.status(201).json({ id: result?.id })
    })

    export { users }
    ```
  </Step>

  <Step title="Add scoped middleware">
    Middleware registered on the sub-router only runs for requests matched by that router. This keeps auth, logging, and validation logic close to the routes they protect:

    ```ts routes/api.ts theme={null}
    import { bearerAuth } from 'blaze/middleware/bearer-auth'
    import { rateLimit }  from 'blaze/middleware/rate-limit'

    const api = app.Router()

    // These middleware run only for /api/* requests
    api.use(bearerAuth({ secret: (req) => req.env.JWT_SECRET }))
    api.use(rateLimit({ store: (req) => req.env.RATE_LIMIT_KV, max: 100 }))

    api.use('/users', users)    // mounts at /api/v1/users when parent mounts at /api/v1
    api.use('/posts', posts)
    ```
  </Step>

  <Step title="Mount the sub-router">
    Pass the sub-router to `app.use()` with a path prefix. Blaze strips the prefix from `req.path` before dispatching into the sub-router, so routes registered as `/` on the sub-router respond to the mount prefix on the parent:

    ```ts src/index.ts theme={null}
    import { createApp } from 'blaze'
    import { logger }    from 'blaze/middleware/logger'
    import { cors }      from 'blaze/middleware/cors'
    import { users }     from './routes/users'
    import { posts }     from './routes/posts'
    import type { Env }  from './types/env'

    const app = createApp<Env>()

    // Global middleware
    app.use(logger())
    app.use(cors({ origins: (req) => req.env.ALLOWED_ORIGINS.split(',') }))

    // Versioned sub-routers
    app.use('/api/v1/users', users)
    app.use('/api/v1/posts', posts)

    export default { fetch: app.fetch }
    ```
  </Step>
</Steps>

<Tip>
  Mount your sub-routers at versioned prefixes like `/api/v1` so you can
  introduce `/api/v2` routers in parallel without breaking existing clients.
  Each version gets its own router, its own middleware stack, and — when the
  time comes — its own deprecation notice middleware.
</Tip>

***

## Route chaining

`router.route(path)` (or `app.route(path)`) returns a chainable `Route` object. Register GET, POST, PUT, PATCH, and DELETE handlers on the same path without repeating it:

```ts theme={null}
app
  .route('/articles/:id')
  .get(async (req, res) => {
    const article = await req.env.DB
      .prepare('SELECT * FROM articles WHERE id = ?')
      .bind(req.params.id)
      .first()

    if (!article) return res.status(404).json({ error: 'Not found' })
    res.json(article)
  })
  .put(requireAuth, async (req, res) => {
    const { title, body } = await req.json()
    await req.env.DB
      .prepare('UPDATE articles SET title = ?, body = ? WHERE id = ?')
      .bind(title, body, req.params.id)
      .run()

    res.json({ ok: true })
  })
  .patch(requireAuth, async (req, res) => {
    const changes = await req.json()
    // partial update logic …
    res.json({ ok: true })
  })
  .delete(requireAuth, async (req, res) => {
    await req.env.DB
      .prepare('DELETE FROM articles WHERE id = ?')
      .bind(req.params.id)
      .run()

    res.status(204).send()
  })
```

***

## Middleware scope

Blaze gives you three levels of middleware granularity. Use whichever scope is most appropriate for each concern:

<Tabs>
  <Tab title="Global">
    Registered with `app.use()` and no path — runs on every request before any route handler:

    ```ts theme={null}
    import { requestId } from 'blaze/middleware/request-id'
    import { logger }    from 'blaze/middleware/logger'

    app.use(requestId())   // attaches req.id and X-Request-Id header
    app.use(logger())      // logs method, path, status, duration
    ```
  </Tab>

  <Tab title="Path-scoped">
    Registered with `app.use('/prefix', fn)` — runs only when the request URL starts with that prefix. Useful for auth on a section of the API:

    ```ts theme={null}
    import { jwtAuth } from 'blaze/middleware/jwt'

    // Only /api/* requests go through JWT validation
    app.use('/api', jwtAuth({ secret: (req) => req.env.JWT_SECRET }))

    // Only /admin/* requests require admin role
    app.use('/admin', requireAdmin)
    ```
  </Tab>

  <Tab title="Route-level">
    Passed as extra arguments to a route registration — runs only for that specific path and method:

    ```ts theme={null}
    import { bodyLimit } from 'blaze/middleware/body-limit'

    app.post(
      '/uploads',
      bodyLimit({ maxSize: 50 * 1024 * 1024 }),  // 50 MB cap
      requireAuth,
      handleUpload,
    )
    ```
  </Tab>
</Tabs>

<Note>
  Middleware layers run in **registration order**. Global middleware registered
  before `app.use('/prefix', router)` runs before the sub-router's own
  middleware. Register error handlers last so they can catch errors from all
  earlier layers.
</Note>
