# How to Group Routes in OmniRoute: Next.js App Router Organization Guide

> Learn how to group routes in OmniRoute for Next.js App Router. Organize your API endpoints using file-system conventions for cleaner URL namespaces. Boost your app's structure today.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-08-17

---

**OmniRoute groups API routes by leveraging Next.js App Router's file-system convention, where nested folders under `src/app/api/v1/` automatically create logical URL namespaces like `/v1/chat` or `/v1/tts`.**

OmniRoute is an open-source API gateway that organizes its public HTTP endpoints using the Next.js App Router's folder-based routing system. Learning how to group routes in OmniRoute enables you to structure endpoints by feature—such as chat, embeddings, or images—while maintaining a clean, scalable codebase. All route definitions reside under `src/app/api/v1/`, with the directory hierarchy directly mapping to the exposed URL structure.

## Understanding the Route Group Hierarchy

OmniRoute implements a four-level nesting strategy to separate concerns and manage complexity. According to the OmniRoute source code, the hierarchy works as follows:

### Root API Entry Point

The foundation sits at [`src/app/api/v1/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/route.ts), which acts as the top-level catch-all that forwards requests to sub-folders. This file handles the initial routing logic for all version 1 endpoints.

### Feature Groups

Logical collections group related endpoints by functionality. For example, [`src/app/api/v1/chat/completions/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/chat/completions/route.ts) contains all chat-completion handlers, while `src/app/api/v1/images/` handles image generation. The folder name becomes the URL prefix automatically.

### Dynamic Segments

Parameterized paths accept variables like API keys or tokens within the URL. The path `src/app/api/v1/vscode/[token]/v1/chat/completions/route.ts` demonstrates how dynamic segments (denoted by square brackets) allow VS Code-specific routes to extract a token directly from the request path.

### Shared Utilities

Common middleware and helpers live in `src/app/api/v1/_shared/` and `src/app/api/v1/_helpers/`. Files like [`src/app/api/v1/_shared/rateLimit.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/_shared/rateLimit.ts) provide reusable rate-limiting logic imported by multiple route groups.

## How to Create a New Route Group

To add a new functional group to OmniRoute, follow this workflow:

1. **Create a folder** under `src/app/api/v1/` that reflects the feature name (e.g., `tts` for text-to-speech).

2. **Add a [`route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/route.ts) file** that exports the HTTP handlers (`GET`, `POST`, etc.). Use `[[...slug]].ts` for catch-all routes if needed.

3. **Reuse shared helpers** by importing utilities from `src/app/api/v1/_shared/` for CORS, Zod validation, authentication, or rate limiting.

4. **Optionally nest sub-folders** for further granularity (e.g., `v1/models/`, `v1/embeddings/`).

## Standard Route Implementation Pattern

All routes in OmniRoute follow a consistent pattern that applies middleware before delegating to core handlers. In [`src/app/api/v1/chat/completions/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/chat/completions/route.ts), the implementation chains CORS, authentication, and Zod validation:

```typescript
// src/app/api/v1/chat/completions/route.ts
import { handleChatCore } from '@/open-sse/handlers/chatCore';
import { z } from 'zod';
import { cors } from '@/open-sse/middleware/cors';
import { auth } from '@/open-sse/middleware/auth';

export async function POST(req: Request) {
  // Apply CORS & auth middleware
  await cors(req);
  await auth(req);

  // Validate body with Zod
  const schema = z.object({
    model: z.string(),
    messages: z.array(z.object({ role: z.string(), content: z.string() })),
    // … other fields …
  });
  const body = await req.json();
  schema.parse(body);

  // Delegate to the core handler
  return handleChatCore(body, req);
}

```

The router automatically exposes any files under `src/app/api/v1/chat/*` beneath the `/v1/chat` URL namespace.

## Practical Grouping Examples

### Adding a Text-to-Speech Group

To create a new "text-to-speech" endpoint group, execute the following commands to set up the folder and route file:

```bash
mkdir -p src/app/api/v1/tts
cat > src/app/api/v1/tts/route.ts <<'EOF'
import { handleTts } from '@/open-sse/handlers/tts';
import { cors } from '@/open-sse/middleware/cors';
import { auth } from '@/open-sse/middleware/auth';
import { z } from 'zod';

export async function POST(req: Request) {
  await cors(req);
  await auth(req);

  const schema = z.object({
    model: z.string(),
    input: z.string(),
  });
  const body = await req.json();
  schema.parse(body);

  return handleTts(body, req);
}
EOF

```

Once created, the endpoint is immediately available at:

```bash
POST https://localhost:20128/v1/tts
{
  "model": "elevenlabs/tts",
  "input": "Hello, OmniRoute!"
}

```

### Grouping with Shared Middleware

Existing features can import shared utilities to maintain consistency. In [`src/app/api/v1/images/edits/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/images/edits/route.ts), the route applies centralized rate limiting before processing:

```typescript
// src/app/api/v1/images/edits/route.ts
import { handleImageEdit } from '@/open-sse/handlers/imageEdit';
import { rateLimit } from '@/app/api/v1/_shared/rateLimit';

export async function POST(req: Request) {
  await rateLimit(req);
  return handleImageEdit(req);
}

```

## Key Files for Route Organization

Understanding these core files helps navigate the OmniRoute codebase:

- **[`src/app/api/v1/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/route.ts)** — Root catch-all that routes requests to sub-folders.
- **[`src/app/api/v1/chat/completions/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/chat/completions/route.ts)** — Example grouped endpoint for Chat Completion logic.
- **[`src/app/api/v1/_shared/rateLimit.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/_shared/rateLimit.ts)** — Shared rate-limit middleware imported by multiple groups.
- **`src/app/api/v1/vscode/[token]/v1/chat/completions/route.ts`** — Demonstrates dynamic segments with URL parameters.
- **[`src/app/api/v1/_helpers/apiKeyScope.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/_helpers/apiKeyScope.ts)** — Helper functions that scope API-key usage across different route groups.

## Summary

- OmniRoute uses **Next.js App Router file-system routing** to organize API endpoints under `src/app/api/v1/`.
- **Folder names automatically create URL namespaces**—any folder created under `v1/` becomes a route prefix.
- **Shared middleware** (CORS, auth, rate limiting) lives in `src/app/api/v1/_shared/` and is imported by specific route handlers.
- **Dynamic segments** use square-bracket notation (e.g., `[token]`) to capture URL parameters.
- New groups require only a folder and a [`route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/route.ts) file exporting HTTP method handlers.

## Frequently Asked Questions

### How does OmniRoute automatically group routes?

OmniRoute leverages Next.js App Router conventions where the directory structure under `src/app/api/v1/` directly maps to URL paths. Any folder created inside `v1/` automatically groups its contained [`route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/route.ts) files under that folder's name in the URL (e.g., `v1/chat/` becomes `/v1/chat/*`).

### Can I use dynamic parameters in route groups?

Yes. OmniRoute supports dynamic segments using square-bracket folder names like `[token]`. The file `src/app/api/v1/vscode/[token]/v1/chat/completions/route.ts` captures the token value from the URL and makes it available to the route handler for scoping requests or authentication.

### Where should I place middleware that applies to multiple groups?

Reusable middleware should be placed in `src/app/api/v1/_shared/` (for utilities like [`rateLimit.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/rateLimit.ts)) or `src/app/api/v1/_helpers/` (for API key scoping logic). Route files then import and invoke these functions explicitly, as seen in [`src/app/api/v1/chat/completions/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/chat/completions/route.ts) with `cors(req)` and `auth(req)`.

### What is the difference between `_shared` and `_helpers` folders?

In OmniRoute's structure, `src/app/api/v1/_shared/` typically contains middleware and infrastructure code like CORS headers and rate limiting. The `src/app/api/v1/_helpers/` directory contains business-logic utilities such as [`apiKeyScope.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/apiKeyScope.ts) that manage API key validation and scoping across different route groups.