# Routing

> Nitro supports filesystem routing to automatically map files to routes. By combining code-splitting with compiled routes, it removes the need for a runtime router, leaving only minimal compiled logic.

## Request handler

Nitro request handler is a function accepting an `event` object, which is a [H3Event](https://h3.dev/guide/api/h3event#h3event-properties) object.

<code-group>

```ts [Single function]
import type { H3Event } from "nitro";

export default (event: H3Event) => {
  return "world";
}
```

```ts [defineHandler]
import { defineHandler } from "nitro";

// For better type inference
export default defineHandler((event) => {
  return "world";
});
```

</code-group>

## Filesystem routing

Nitro supports file-based routing for your API routes (files are automatically mapped to [h3 routes](https://h3.dev/guide/basics/routing)). Defining a route is as simple as creating a file inside the `api/` or `routes/` directory.

You can only define one handler per files and you can [append the HTTP method](#specific-request-method) to the filename to define a specific request method.

```text
routes/
  api/
    test.ts      <-- /api/test
  hello.get.ts   <-- /hello (GET only)
  hello.post.ts  <-- /hello (POST only)
vite.config.ts
```

You can nest routes by creating subdirectories.

```txt
routes/
  api/
    [org]/
      [repo]/
        index.ts   <-- /api/:org/:repo
        issues.ts  <-- /api/:org/:repo/issues
      index.ts     <-- /api/:org
package.json
```

#### Route Groups

In some cases, you may want to group a set of routes together in a way which doesn't affect file-based routing. For this purpose, you can put files in a folder which is wrapped in parentheses `(` and `)`.

For example:

```txt
routes/
  api/
    (admin)/
      users.ts   <-- /api/users
      reports.ts <-- /api/reports
    (public)/
      index.ts   <-- /api
package.json
```

<note>

The route groups are not part of the route definition and are only used for organization purposes.

</note>

### Static routes

First, create a file in `routes/` or `routes/api/` directory. The filename will be the route path.

Then, export a fetch-compatible function:

```ts [routes/api/test.ts]
import { defineHandler } from "nitro";

export default defineHandler(() => {
  return { hello: "API" };
});
```

### Dynamic routes

#### Single param

To define a route with params, use the `[<param>]` syntax where `<param>` is the name of the param. The param will be available in the `event.context.params` object or using the [`getRouterParam`](https://h3.dev/utils/request#getrouterparamevent-name-opts-decode) utility.

```ts [routes/hello/[name].ts]
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  const { name } = event.context.params;

  return `Hello ${name}!`;
});
```

Call the route with the param `/hello/nitro`, you will get:

```txt [Response]
Hello nitro!
```

#### Multiple params

You can define multiple params in a route using `[<param1>]/[<param2>]` syntax where each param is a folder. You **cannot** define multiple params in a single filename of folder.

```ts [routes/hello/[name]/[age].ts]
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  const { name, age } = event.context.params;

  return `Hello ${name}! You are ${age} years old.`;
});
```

#### Catch-all params

You can capture all the remaining parts of a URL using `[...<param>]` syntax. This will include the `/` in the param.

```ts [routes/hello/[...name].ts]
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  const { name } = event.context.params;

  return `Hello ${name}!`;
});
```

Call the route with the param `/hello/nitro/is/hot`, you will get:

```txt [Response]
Hello nitro/is/hot!
```

### Specific request method

You can append the HTTP method to the filename to force the route to be matched only for a specific HTTP request method, for example `hello.get.ts` will only match for `GET` requests. You can use any HTTP method you want.

Supported methods: `get`, `post`, `put`, `delete`, `patch`, `head`, `options`, `connect`, `trace`.

<code-group>

```js [GET]
// routes/users/[id].get.ts
import { defineHandler } from "nitro";

export default defineHandler(async (event) => {
  const { id } = event.context.params;

  // Do something with id

  return `User profile!`;
});
```

```js [POST]
// routes/users.post.ts
import { defineHandler } from "nitro";

export default defineHandler(async (event) => {
  const body = await event.req.json();

  // Do something with body like saving it to a database

  return { updated: true };
});
```

</code-group>

### Catch-all route

You can create a special route that will match all routes that are not matched by any other route. This is useful for creating a default route.

To create a catch-all route, create a file named `[...].ts`.

```ts [routes/[...].ts]
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  return `Hello ${event.url}!`;
});
```

### Environment specific handlers

You can specify for a route that will only be included in specific builds by adding a `.dev`, `.prod` or `.prerender` suffix to the file name, for example: `routes/test.get.dev.ts` or `routes/test.get.prod.ts`.

The suffix is placed after the method suffix (if any):

```txt
routes/
  env/
    index.dev.ts       <-- /env (dev only)
    index.get.prod.ts  <-- /env (GET, prod only)
```

<tip>

You can specify multiple environments or specify a preset name as environment using programmatic registration of routes via [`routes`](#routes-config) config.

</tip>

### Ignoring files

You can use the `ignore` config option to exclude files from route scanning. It accepts an array of glob patterns relative to the server directory.

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  ignore: [
    "routes/api/**/_*",   // Ignore files starting with _ in api/
    "middleware/_*.ts",    // Ignore middleware starting with _
    "routes/_*.ts",       // Ignore root routes starting with _
  ],
});
```

## Programmatic route handlers

In addition to filesystem routing, you can register route handlers programmatically using the `routes` config option.

### `routes` config

The `routes` option allows you to map route patterns to handlers:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  routes: {
    "/api/hello": "./server/routes/api/hello.ts",
    "/api/custom": {
      handler: "./server/routes/api/hello.ts",
      method: "POST",
      lazy: true,
    },
    "/virtual": {
      handler: "#virtual-route",
    },
  },
});
```

Each route entry can be a simple string (handler path) or an object with the following options:

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        handler
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Path to event handler file or virtual module ID
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        method
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      HTTP method to match (<code>
        get
      </code>
      
      , <code>
        post
      </code>
      
      , etc.)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        lazy
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Use lazy loading to import handler
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        format
      </code>
    </td>
    
    <td>
      <code>
        "web" | "node"
      </code>
    </td>
    
    <td>
      Handler type. <code>
        "node"
      </code>
      
       handlers are converted to web-compatible
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        env
      </code>
    </td>
    
    <td>
      <code>
        string | string[]
      </code>
    </td>
    
    <td>
      Environments to include this handler (<code>
        "dev"
      </code>
      
      , <code>
        "prod"
      </code>
      
      , <code>
        "prerender"
      </code>
      
      , or a preset name)
    </td>
  </tr>
</tbody>
</table>

### `handlers` config

The `handlers` array is useful for registering middleware with control over route matching:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  handlers: [
    {
      route: "/api/**",
      handler: "./server/middleware/api-auth.ts",
      middleware: true,
    },
  ],
});
```

Each handler entry supports the following options:

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        route
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      HTTP pathname pattern (e.g., <code>
        /test
      </code>
      
      , <code>
        /api/:id
      </code>
      
      , <code>
        /blog/**
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        handler
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Path to event handler file or virtual module ID
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        method
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      HTTP method to match (<code>
        get
      </code>
      
      , <code>
        post
      </code>
      
      , etc.)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        middleware
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Run handler as middleware before route handlers
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        lazy
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Use lazy loading to import handler
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        format
      </code>
    </td>
    
    <td>
      <code>
        "web" | "node"
      </code>
    </td>
    
    <td>
      Handler type. <code>
        "node"
      </code>
      
       handlers are converted to web-compatible
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        env
      </code>
    </td>
    
    <td>
      <code>
        string | string[]
      </code>
    </td>
    
    <td>
      Environments to include this handler (<code>
        "dev"
      </code>
      
      , <code>
        "prod"
      </code>
      
      , <code>
        "prerender"
      </code>
      
      , or a preset name)
    </td>
  </tr>
</tbody>
</table>

## Middleware

Nitro route middleware can hook into the request lifecycle.

<tip>

A middleware can modify the request before it is processed, not after.

</tip>

Middleware are auto-registered within the `middleware/` directory.

```md
middleware/
  auth.ts
  logger.ts
  ...
routes/
  hello.ts
```

### Simple middleware

Middleware are defined exactly like route handlers with the only exception that they should not return anything.
Returning from middleware behaves like returning from a request - the value will be returned as a response and further code will not be ran.

```ts [middleware/auth.ts]
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  // Extends or modify the event
  event.context.user = { name: "Nitro" };
});
```

Middleware in `middleware/` directory are automatically registered for all routes. If you want to register a middleware for a specific route, see [Object Syntax Event Handler](https://h3.dev/guide/basics/handler#object-syntax).

<note>

Returning anything from a middleware will close the request and should be avoided! Any returned value from middleware will be the response and further code will not be executed however **this is not recommended to do!**

</note>

### Route Meta

You can define route handler meta at build-time using `defineRouteMeta` macro in the event handler files.

<important>

This feature is currently experimental.

</important>

```ts [routes/api/test.ts]
import { defineRouteMeta } from "nitro";
import { defineHandler } from "nitro";

defineRouteMeta({
  openAPI: {
    tags: ["test"],
    description: "Test route description",
    parameters: [{ in: "query", name: "test", required: true }],
  },
});

export default defineHandler(() => "OK");
```

<read-more to="https://swagger.io/specification/v3/">

This feature is currently usable to specify OpenAPI meta. See swagger specification for available OpenAPI options.

</read-more>

### Execution order

Middleware are executed in directory listing order.

```md
middleware/
  auth.ts <-- First
  logger.ts <-- Second
  ... <-- Third
```

Prefix middleware with a number to control their execution order.

```md
middleware/
  1.logger.ts <-- First
  2.auth.ts <-- Second
  3.... <-- Third
```

<note>

Remember that file names are sorted as strings, thus for example if you have 3 files `1.filename.ts`, `2.filename.ts` and `10.filename.ts`, the `10.filename.ts` will come after the `1.filename.ts`. To avoid this, prefix `1-9` with a `0` like `01`, if you have more than 10 middleware in the same directory.

</note>

### Request filtering

Middleware are executed on every request.

Apply custom logic to scope them to specific conditions.

For example, you can use the URL to apply a middleware to a specific route:

```ts [middleware/auth.ts]
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  // Will only execute for /auth route
  if (event.url.pathname.startsWith('/auth')) {
    event.context.user = { name: "Nitro" };
  }
});
```

### Route-scoped middleware

You can register middleware for specific route patterns using the [`handlers`](#handlers-config) config with the `middleware` option and a specific `route`:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  handlers: [
    {
      route: "/api/**",
      handler: "./server/middleware/api-auth.ts",
      middleware: true,
    },
  ],
});
```

Unlike global middleware (registered in the `middleware/` directory which match `/**`), route-scoped middleware only run for requests matching the specified pattern.

## Error handling

You can use the [utilities available in H3](https://h3.dev/guide/basics/error) to handle errors in both routes and middlewares.

The way errors are sent back to the client depends on the environment. In development, requests with an `Accept` header of `text/html` (such as browsers) will receive a HTML error page. In production, errors are always sent in JSON.

This behaviour can be overridden by some request properties (e.g.: `Accept` or `User-Agent` headers).

## Code splitting

Nitro creates a separate chunk for each route handler. Chunks load on-demand when first requested, so `/api/users` doesn't load code for `/api/posts`.

See [`inlineDynamicImports`](/config#inlinedynamicimports) to bundle everything into a single file.

## Route rules

Nitro allows you to add logic at the top-level for each route of your configuration. It can be used for redirecting, proxying, caching, authentication, and adding headers to routes.

It is a map from route pattern (following [rou3](https://github.com/h3js/rou3)) to route options.

When `cache` option is set, handlers matching pattern will be automatically wrapped with `defineCachedHandler`. See the [cache guide](/docs/cache) to learn more about this function.

<note>

`swr: true|number` is shortcut for `cache: { swr: true, maxAge: number }`

</note>

You can set route rules in the `nitro.routeRules` options.

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/blog/**': { swr: true },
    '/blog2/**': { swr: 600 },
    '/blog3/**': { static: true },
    '/blog4/**': { cache: { /* cache options*/ } },
    '/assets/**': { headers: { 'cache-control': 's-maxage=0' } },
    '/api/v1/**': { cors: true, headers: { 'access-control-allow-methods': 'GET' } },
    '/old-page': { redirect: '/new-page' },
    '/old-page/**': { redirect: '/new-page/**' },
    '/proxy/example': { proxy: 'https://example.com' },
    '/proxy/**': { proxy: '/api/**' },
    '/admin/**': { basicAuth: { username: 'admin', password: 'supersecret' } },
  }
});
```

### Rule merging and overrides

Route rules are matched from least specific to most specific. When multiple rules match a request, their options are merged, with more specific rules taking precedence.

You can use `false` to disable a rule that was set by a more general pattern:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/api/cached/**': { swr: true },
    '/api/cached/no-cache': { cache: false, swr: false },
    '/admin/**': { basicAuth: { username: 'admin', password: 'secret' } },
    '/admin/public/**': { basicAuth: false },
  }
});
```

### Headers

Set custom response headers for matching routes:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/api/**': { headers: { 'cache-control': 's-maxage=60' } },
    '**': { headers: { 'x-powered-by': 'Nitro' } },
  }
});
```

### CORS

Enable CORS headers with the `cors: true` shortcut. This sets `access-control-allow-origin: *`, `access-control-allow-methods: *`, `access-control-allow-headers: *`, and `access-control-max-age: 0`.

You can override individual CORS headers using `headers`:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/api/v1/**': {
      cors: true,
      headers: { 'access-control-allow-methods': 'GET' },
    },
  }
});
```

### Redirect

Redirect matching routes to another URL. Use a string for a simple redirect (defaults to `307` status), or an object for more control:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    // Simple redirect (307 status)
    '/old-page': { redirect: '/new-page' },
    // Redirect with custom status
    '/legacy': { redirect: { to: 'https://example.com/', status: 308 } },
    // Wildcard redirect — preserves the path after the pattern
    '/old-blog/**': { redirect: 'https://blog.example.com/**' },
  }
});
```

### Proxy

Proxy requests to another URL. Supports both internal and external targets:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    // Proxy to exact URL
    '/api/proxy/example': { proxy: 'https://example.com' },
    // Proxy to internal route
    '/api/proxy/**': { proxy: '/api/echo' },
    // Wildcard proxy — preserves the path after the pattern
    '/cdn/**': { proxy: 'https://cdn.jsdelivr.net/**' },
    // Proxy with options
    '/external/**': {
      proxy: {
        to: 'https://api.example.com/**',
        // Additional H3 proxy options...
      },
    },
  }
});
```

### Basic auth

Protect routes with HTTP Basic Authentication:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/admin/**': {
      basicAuth: {
        username: 'admin',
        password: 'supersecret',
        realm: 'Admin Area',  // Optional, shown in the browser prompt
      },
    },
    // Disable basic auth for a sub-path
    '/admin/public/**': { basicAuth: false },
  }
});
```

### Caching (SWR / Static)

Control caching behavior with `cache`, `swr`, or `static` options:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    // Enable stale-while-revalidate caching
    '/blog/**': { swr: true },
    // SWR with maxAge in seconds
    '/blog/posts/**': { swr: 600 },
    // Full cache options
    '/api/data/**': {
      cache: {
        maxAge: 60,
        swr: true,
        // ...other cache options
      },
    },
    // Disable caching
    '/api/realtime/**': { cache: false },
  }
});
```

<tip>

`swr: true` is a shortcut for `cache: { swr: true }` and `swr: <number>` is a shortcut for `cache: { swr: true, maxAge: <number> }`.

</tip>

### Prerender

Mark routes for prerendering at build time:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/about': { prerender: true },
    '/dynamic/**': { prerender: false },
  }
});
```

### ISR (Vercel)

Configure Incremental Static Regeneration for Vercel deployments:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/isr/**': { isr: true },
    '/isr-ttl/**': { isr: 60 },
    '/isr-custom/**': {
      isr: {
        expiration: 60,
        allowQuery: ['q'],
        group: 1,
      },
    },
  }
});
```

### Route rules reference

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        headers
      </code>
    </td>
    
    <td>
      <code>
        Record<string, string>
      </code>
    </td>
    
    <td>
      Custom response headers
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        redirect
      </code>
    </td>
    
    <td>
      <code>
        string | { to: string, status?: number }
      </code>
    </td>
    
    <td>
      Redirect to another URL (default status: <code>
        307
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        proxy
      </code>
    </td>
    
    <td>
      <code>
        string | { to: string, ...proxyOptions }
      </code>
    </td>
    
    <td>
      Proxy requests to another URL
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cors
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Enable permissive CORS headers
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cache
      </code>
    </td>
    
    <td>
      <code>
        object | false
      </code>
    </td>
    
    <td>
      Cache options (see <a href="/docs/cache">
        cache guide
      </a>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        swr
      </code>
    </td>
    
    <td>
      <code>
        boolean | number
      </code>
    </td>
    
    <td>
      Shortcut for <code>
        cache: { swr: true, maxAge: number }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        static
      </code>
    </td>
    
    <td>
      <code>
        boolean | number
      </code>
    </td>
    
    <td>
      Shortcut for static caching
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        basicAuth
      </code>
    </td>
    
    <td>
      <code>
        { username, password, realm? } | false
      </code>
    </td>
    
    <td>
      HTTP Basic Authentication
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        prerender
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Enable/disable prerendering
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        isr
      </code>
    </td>
    
    <td>
      <code>
        boolean | number | object
      </code>
    </td>
    
    <td>
      Incremental Static Regeneration (Vercel)
    </td>
  </tr>
</tbody>
</table>

### Runtime route rules

Route rules can be provided through `runtimeConfig`, allowing overrides via environment variables without rebuilding:

```ts [nitro.config.ts]
import { defineConfig } from "nitro";

export default defineConfig({
  runtimeConfig: {
    nitro: {
      routeRules: {
        '/api/**': { headers: { 'x-env': 'production' } },
      },
    },
  },
});
```

## Config reference

These config options control routing behavior:

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        baseURL
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        "/"
      </code>
    </td>
    
    <td>
      Base URL for all routes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        apiBaseURL
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        "/api"
      </code>
    </td>
    
    <td>
      Base URL for routes in the <code>
        api/
      </code>
      
       directory
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        apiDir
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        "api"
      </code>
    </td>
    
    <td>
      Directory name for API routes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        routesDir
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        "routes"
      </code>
    </td>
    
    <td>
      Directory name for file-based routes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        serverDir
      </code>
    </td>
    
    <td>
      <code>
        string | false
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Server directory for scanning routes, middleware, plugins, etc.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        scanDirs
      </code>
    </td>
    
    <td>
      <code>
        string[]
      </code>
    </td>
    
    <td>
      <code>
        []
      </code>
    </td>
    
    <td>
      Additional directories to scan for routes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        routes
      </code>
    </td>
    
    <td>
      <code>
        Record<string, string | handler>
      </code>
    </td>
    
    <td>
      <code>
        {}
      </code>
    </td>
    
    <td>
      Route-to-handler mapping
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        handlers
      </code>
    </td>
    
    <td>
      <code>
        NitroEventHandler[]
      </code>
    </td>
    
    <td>
      <code>
        []
      </code>
    </td>
    
    <td>
      Programmatic handler registration (mainly for middleware)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        routeRules
      </code>
    </td>
    
    <td>
      <code>
        Record<string, NitroRouteConfig>
      </code>
    </td>
    
    <td>
      <code>
        {}
      </code>
    </td>
    
    <td>
      Route rules for matching patterns
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ignore
      </code>
    </td>
    
    <td>
      <code>
        string[]
      </code>
    </td>
    
    <td>
      <code>
        []
      </code>
    </td>
    
    <td>
      Glob patterns to ignore during file scanning
    </td>
  </tr>
</tbody>
</table>
