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.
Nitro v3 Alpha docs are a work in progress — expect updates, rough edges, and occasional inaccuracies.

Request handler

Nitro request handler is a function accepting an event object, which is a H3Event object.

import type { H3Event } from "nitro/h3";

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

Filesystem routing

Nitro supports file-based routing for your API routes (files are automatically mapped to h3 routes). 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 to the filename to define a specific request method.

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.

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

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:

routes/api/test.ts
import { defineHandler } from "nitro/h3";

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 utility.

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

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

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

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

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.

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

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.

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

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:

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.

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

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

  // Do something with id

  return `User profile!`;
});

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.

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

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.

You can specify multiple environments or specify a preset name as environment using programmatic registration of routes via handlers[] config.

Middleware

Nitro route middleware can hook into the request lifecycle.

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

Middleware are auto-registered within the middleware/ directory.

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.

middleware/auth.ts
import { defineHandler } from "nitro/h3";

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.

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!

Route Meta

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

🚧 This feature is currently experimental.
routes/api/test.ts
import { defineRouteMeta } from "nitro/runtime";
import { defineHandler } from "nitro/h3";

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

export default defineHandler(() => "OK");
This feature is currently usable to specify OpenAPI meta. See swagger specification for available OpenAPI options.

Execution order

Middleware are executed in directory listing order.

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

Prefix middleware with a number to control their execution order.

middleware/
  1.logger.ts <-- First
  2.auth.ts <-- Second
  3.... <-- Third
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.

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:

middleware/auth.ts
import { defineHandler } from "nitro/h3";

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

Error handling

You can use the utilities available in H3 to handle errors in both routes and middlewares.

The way errors are sent back to the client depends on the route's path. For most routes Content-Type is set to text/html by default and a simple html error page is delivered. If the route starts with /api/ (either because it is placed in api/ or routes/api/) the default will change to application/json and a JSON object will be sent.

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

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 and adding headers to routes.

It is a map from route pattern (following rou3) to route options.

When cache option is set, handlers matching pattern will be automatically wrapped with defineCachedEventHandler. See the cache guide to learn more about this function.

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

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

nitro.config.ts
import { defineNitroConfig } from "nitro/config";

export default defineConfig({
  routeRules: {
    '/blog/**': { swr: true },
    '/blog/**': { swr: 600 },
    '/blog/**': { static: true },
    '/blog/**': { 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/**' },
  }
});