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

Define custom routes in OmniRoute by creating a folder under src/app/api/v1/, adding a 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 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 file and any route-specific utilities.

2. Implement the Route Handler

Inside your new folder, create a 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:

3. Delegate to Business Logic Handlers

Most route-level logic lives in the open-sse/handlers/ directory (e.g., chatCore.ts, embeddings.ts). Your 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. 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 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:

// 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:

// 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:

Summary

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 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 within your 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 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 to process errors. This utility sanitizes error messages using sanitizeErrorMessage, ensuring that stack traces and internal implementation details are never exposed to API consumers.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →