# How to Define Custom Routes in OmniRoute: A Step-by-Step Guide

> Learn to define custom routes in OmniRoute with this step-by-step guide. Create route files, export HTTP methods, and leverage built-in pipeline features for efficient API development.

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

---

**Define custom routes in OmniRoute by creating a folder under `src/app/api/v1/`, adding a [`route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/route.ts) file that exports HTTP methods, and following the established pipeline of CORS handling, Zod validation, authentication, and error sanitization.**

OmniRoute is an open-source routing layer built on Next.js 16’s App Router that standardizes API endpoint creation through a predictable file-based structure. To define custom routes in OmniRoute, developers must adhere to the repository's conventions located in `src/app/api/v1/` and implement handlers that integrate with the shared validation, authentication, and error-handling utilities.

## Understanding OmniRoute’s Routing Architecture

OmniRoute leverages Next.js 16’s App Router to provide a filesystem-based API structure where every endpoint lives under `src/app/api/v1/`. Each route is implemented as a directory containing a [`route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/route.ts) file that exports HTTP verb handlers (`GET`, `POST`, `OPTIONS`, etc.).

The architecture enforces a uniform processing pipeline: **CORS → Zod Validation → Auth → Resilience → Handler**. This guarantees that every custom route benefits from circuit-breaker handling, rate-limit cooldowns, and error sanitization without requiring boilerplate code.

## Step-by-Step Guide to Defining Custom Routes

### 1. Create the Route Directory Structure

Create a new directory under `src/app/api/v1/` that reflects your desired URL path. For example, to create a `/api/v1/my-feature` endpoint, create the folder `src/app/api/v1/my-feature/`. This folder will contain your [`route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/route.ts) file and any route-specific utilities.

### 2. Implement the Route Handler

Inside your new folder, create a [`route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/route.ts) file that exports the HTTP methods you need. This file serves as the entry point and must handle the **CORS pre-flight check** (`OPTIONS`) explicitly or rely on Next.js automatic CORS handling while ensuring preflight requests are allowed.

The handler should:
- Parse the incoming request body
- Validate payloads using Zod schemas from [`src/shared/validation/schemas.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/validation/schemas.ts) via the `validateRequest` utility
- Extract authentication context using `getAuthContext` from [`src/sse/services/auth.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/sse/services/auth.ts)

### 3. Delegate to Business Logic Handlers

Most route-level logic lives in the `open-sse/handlers/` directory (e.g., [`chatCore.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/chatCore.ts), [`embeddings.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/embeddings.ts)). Your [`route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/route.ts) should act as a thin wrapper that calls the appropriate handler, passing the validated payload and any auth context. This separation keeps HTTP concerns separate from business logic.

### 4. Implement Uniform Error Handling

All errors must be converted into safe responses using `buildErrorBody` and `sanitizeErrorMessage` from [`open-sse/utils/error.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/utils/error.ts). This ensures internal stack traces never leak to clients and provides consistent error formatting across the API.

### 5. Document the Route

Add a description to [`docs/architecture/ARCHITECTURE.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/docs/architecture/ARCHITECTURE.md) under the "Adding a New API Route" section. This ensures future developers understand the route’s purpose and confirms it follows established conventions.

## Complete Code Example

Here is a complete implementation of a custom route following OmniRoute patterns:

```typescript
// src/app/api/v1/my-feature/route.ts
import { NextResponse } from 'next/server';
import { z } from 'zod';
import { validateRequest } from '@/src/shared/validation/schemas';
import { getAuthContext } from '@/src/sse/services/auth';
import { myFeatureHandler } from '@/src/open-sse/handlers/myFeature';
import { buildErrorBody } from '@/src/open-sse/utils/error';

// Define the shape of the request body
const bodySchema = z.object({
  prompt: z.string(),
  maxTokens: z.number().int().positive().default(256),
});

// POST handler – runs validation, auth, then delegates
export async function POST(req: Request) {
  try {
    const json = await req.json();
    const payload = validateRequest(json, bodySchema);
    const auth = await getAuthContext(req);
    const result = await myFeatureHandler(payload, auth);
    
    return NextResponse.json(result);
  } catch (err) {
    // Uniform error response – never leaks stack traces
    const { status, body } = buildErrorBody(err);
    return new NextResponse(body, { status });
  }
}

```

The corresponding handler implementation in the streaming layer:

```typescript
// src/open-sse/handlers/myFeature.ts
import { HandlerPayload, AuthContext } from '@/src/open-sse/types';

export async function myFeatureHandler(
  payload: HandlerPayload,
  auth?: AuthContext,
) {
  // Example: forward the request to a provider executor
  const executor = getExecutor('myProvider');
  const providerResp = await executor.execute(payload);
  
  // Transform the provider response into the API shape
  return {
    answer: providerResp.text,
    usage: providerResp.usage,
  };
}

```

## Key Files and Conventions

When you define custom routes in OmniRoute, you will interact with these critical files:

- **[`src/app/api/v1/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/route.ts)** – Provides shared CORS utilities and common error helpers used by every route file
- **[`src/app/api/v1/chat/completions/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/chat/completions/route.ts)** – A reference implementation demonstrating the full pattern of validation, auth, delegation, and error sanitization
- **`src/open-sse/handlers/`** – Contains the actual business-logic handlers that routes call into (e.g., [`chatCore.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/chatCore.ts), [`embeddings.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/embeddings.ts))
- **[`src/shared/validation/schemas.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/validation/schemas.ts)** – Central hub for Zod request-validation schemas used across multiple routes
- **[`src/sse/services/auth.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/sse/services/auth.ts)** – Contains `getAuthContext` for extracting API keys or JWT tokens from requests
- **[`open-sse/utils/error.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/utils/error.ts)** – Houses `buildErrorBody` and `sanitizeErrorMessage` for consistent error handling
- **[`docs/architecture/ARCHITECTURE.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/docs/architecture/ARCHITECTURE.md)** – Documents the "Adding a New API Route" workflow and architectural decisions

## Summary

- **Define custom routes in OmniRoute** by creating directories under `src/app/api/v1/` with a [`route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/route.ts) file that exports HTTP methods
- Always validate requests using Zod schemas from [`src/shared/validation/schemas.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/validation/schemas.ts) before processing
- Delegate business logic to handlers in `open-sse/handlers/` to maintain separation of concerns
- Sanitize all errors using `buildErrorBody` from [`open-sse/utils/error.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/utils/error.ts) to prevent information leakage
- Follow the reference implementation in [`src/app/api/v1/chat/completions/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/chat/completions/route.ts) for best practices
- Document new routes in [`docs/architecture/ARCHITECTURE.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/docs/architecture/ARCHITECTURE.md) to maintain team alignment

## Frequently Asked Questions

### Where should I place my custom route files in OmniRoute?

Place all custom route directories under `src/app/api/v1/` to ensure they are picked up by the Next.js App Router. Each route must be a folder containing a [`route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/route.ts) file that exports the required HTTP methods (e.g., `export const POST = ...`).

### How does OmniRoute handle authentication for custom routes?

Authentication is handled by importing `getAuthContext` from [`src/sse/services/auth.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/sse/services/auth.ts) within your [`route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/route.ts) file. This function extracts API keys or JWT tokens from the request headers and returns an `AuthContext` object that you can pass to your business logic handlers.

### What validation library does OmniRoute use for custom routes?

OmniRoute uses **Zod** for request validation. Import the `validateRequest` utility from [`src/shared/validation/schemas.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/validation/schemas.ts) to validate incoming JSON against predefined schemas. This centralizes validation logic and ensures type safety across all custom routes.

### How do I prevent sensitive error details from leaking in custom routes?

Always wrap your route logic in a try-catch block and use `buildErrorBody` from [`open-sse/utils/error.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/utils/error.ts) to process errors. This utility sanitizes error messages using `sanitizeErrorMessage`, ensuring that stack traces and internal implementation details are never exposed to API consumers.