# How to Structure Complex API Logic in 9router: A Modular Architecture Guide

> Structure complex API logic in 9router effectively. Learn to isolate route handlers, modularize MITM proxy logic, and use translator registries for efficient API management.

- Repository: [decolua/9router](https://github.com/decolua/9router)
- Tags: architecture
- Published: 2026-05-08

---

**Structure complex API logic in 9router by isolating Next.js route handlers, modular MITM proxy logic in `src/mitm/handlers/`, and provider-specific translators using the `open-sse/translator` registry.**

9router is an open-source routing layer that normalizes requests between OpenAI-compatible clients and diverse AI providers. When you structure complex API logic in 9router following its established patterns, you create maintainable, streaming-ready endpoints without entangling business logic in your main application code.

## Core Architectural Components

The recommended architecture separates concerns into three distinct layers that communicate through strict contracts.

### Thin API Entry Points in Next.js Routes

Route files under `src/app/api/v1/*/route.js` should remain minimal delegators. They ensure initialization and forward requests to specialized handlers, keeping HTTP transport concerns separate from business logic.

Example from [`src/app/api/v1/chat/completions/route.js`](https://github.com/decolua/9router/blob/main/src/app/api/v1/chat/completions/route.js):

```javascript
import { ensureInitialized } from './route.js';
import { handleCompletions } from '@/sse/handlers/completions.js';

export async function POST(request) {
  await ensureInitialized();
  return await handleCompletions(request);
}

```

See the full implementation at [src/app/api/v1/chat/completions/route.js](https://github.com/decolua/9router/blob/master/src/app/api/v1/chat/completions/route.js).

### MITM Handlers for Proxy Logic

Complex forwarding logic belongs in `src/mitm/handlers/`. These modules consume the base utilities `fetchRouter` and `pipeSSE` from [`src/mitm/handlers/base.js`](https://github.com/decolua/9router/blob/main/src/mitm/handlers/base.js) to manage request forwarding and Server-Sent Events (SSE) streaming.

In [`src/mitm/handlers/kiro.js`](https://github.com/decolua/9router/blob/main/src/mitm/handlers/kiro.js), the pattern demonstrates intercepting requests, applying transformations, and streaming responses:

```javascript
const { fetchRouter, pipeSSE } = require('./base');
const { translateRequest } = require('../translator/index');

async function handleKiro(req, res) {
  const body = await req.text();
  const translated = translateRequest(body, 'kiro');
  const routerRes = await fetchRouter(translated, '/v1/chat/completions', req.headers);
  await pipeSSE(routerRes, res);
}

```

Reference the complete handler at [src/mitm/handlers/kiro.js](https://github.com/decolua/9router/blob/master/src/mitm/handlers/kiro.js).

### The Translator Registry for Format Normalization

The [`open-sse/translator/index.js`](https://github.com/decolua/9router/blob/main/open-sse/translator/index.js) file maintains a registry that maps OpenAI-compatible payloads to provider-native formats. Request translators live in `open-sse/translator/request/` and response translators in `open-sse/translator/response/`.

To register a new provider, implement a pure transformation function and register it via `FORMATS.registerRequest()`:

```javascript
// open-sse/translator/request/openai-to-claude.js
import { FORMATS } from '../formats.js';

export function openAiToClaude(openaiBody) {
  return {
    prompt: openaiBody.messages[0].content,
    max_tokens: openaiBody.max_tokens
  };
}

FORMATS.registerRequest('claude', openAiToClaude);

```

View the registration pattern at [open-sse/translator/request/openai-to-claude.js](https://github.com/decolua/9router/blob/master/open-sse/translator/request/openai-to-claude.js).

## Recommended Implementation Workflow

Follow these steps to structure complex API logic in 9router when adding new providers or endpoints.

1. **Create a minimal route file** under `src/app/api/v1/<feature>/route.js` that imports `ensureInitialized` from the completions route and delegates to your handler.

2. **Build a dedicated MITM handler** in `src/mitm/handlers/<name>.js`. Import `fetchRouter` and `pipeSSE` from `./base` to handle HTTP forwarding and SSE streaming. Call `translateRequest()` before forwarding and `translateResponse()` after receiving the upstream response.

3. **Implement translator pairs** if the provider uses non-OpenAI schemas. Place request normalizers in `open-sse/translator/request/` and response denormalizers in `open-sse/translator/response/`. Each file should export a pure function and register it with the `FORMATS` object exported from the registry.

4. **Access shared state through stores** rather than hardcoding configuration. Import `providerStore` from [`src/store/providerStore.js`](https://github.com/decolua/9router/blob/main/src/store/providerStore.js) to retrieve provider metadata, API keys, and user-specific settings within your handlers.

## Practical Code Example

Here is a complete implementation of a custom provider endpoint following 9router’s architectural conventions.

**Route layer** ([`src/app/api/v1/custom/route.js`](https://github.com/decolua/9router/blob/main/src/app/api/v1/custom/route.js)):

```javascript
import { ensureInitialized } from '@/app/api/v1/chat/completions/route.js';
import { handleCustomProvider } from '@/mitm/handlers/custom.js';

export async function POST(request) {
  await ensureInitialized();
  return await handleCustomProvider(request);
}

```

**Handler layer** ([`src/mitm/handlers/custom.js`](https://github.com/decolua/9router/blob/main/src/mitm/handlers/custom.js)):

```javascript
const { fetchRouter, pipeSSE } = require('./base');
const { translateRequest, translateResponse } = require('../translator/index');

async function handleCustomProvider(req, res) {
  const body = await req.text();
  const upstreamBody = translateRequest(body, 'customProvider');
  const upstreamRes = await fetchRouter(upstreamBody, '/v1/chat/completions', req.headers);
  await pipeSSE(upstreamRes, res);
}

module.exports = { handleCustomProvider };

```

**Translator layer** ([`open-sse/translator/request/customProvider.js`](https://github.com/decolua/9router/blob/main/open-sse/translator/request/customProvider.js)):

```javascript
import { FORMATS } from '../formats.js';

function customProviderRequest(openaiPayload) {
  return {
    query: openaiPayload.messages.map(m => m.content).join('\n'),
    temperature: openaiPayload.temperature
  };
}

FORMATS.registerRequest('customProvider', customProviderRequest);

```

## Summary

- **Structure complex API logic in 9router** by keeping Next.js routes thin and delegating to MITM handlers.
- Place request/response format conversions in the `open-sse/translator/` registry using pure functions registered via `FORMATS.registerRequest()` and `FORMATS.registerResponse()`.
- Reuse `fetchRouter` and `pipeSSE` from [`src/mitm/handlers/base.js`](https://github.com/decolua/9router/blob/main/src/mitm/handlers/base.js) to ensure consistent proxy behavior and SSE streaming support.
- Initialize translators once using `ensureInitialized()` to prevent duplicate registration side effects.
- Store provider configuration in [`src/store/providerStore.js`](https://github.com/decolua/9router/blob/main/src/store/providerStore.js) rather than hardcoding credentials or endpoints in handlers.

## Frequently Asked Questions

### How do I add support for a new AI provider in 9router?

Create a new request translator in `open-sse/translator/request/` that maps OpenAI fields to the provider's schema, register it with `FORMATS.registerRequest()`, and implement a MITM handler in `src/mitm/handlers/` that invokes `translateRequest()` before calling `fetchRouter()`. This keeps provider logic isolated and reusable across endpoints.

### Where should I place business logic that transforms API responses?

Use the response translator pattern by adding files to `open-sse/translator/response/`. These modules should export pure functions that convert provider-specific payloads back to OpenAI-compatible formats, registering them via `FORMATS.registerResponse()` in the translator index. This separation ensures response normalization remains testable and provider-agnostic.

### What is the purpose of the ensureInitialized() function?

`ensureInitialized()`, defined in [`src/app/api/v1/chat/completions/route.js`](https://github.com/decolua/9router/blob/main/src/app/api/v1/chat/completions/route.js), guarantees that `initTranslators()` runs exactly once per application instance. This prevents duplicate translator registration and ensures the `FORMATS` registry is fully populated before any request handling occurs, avoiding runtime errors during translation.

### Can I use standard Node.js HTTP clients instead of fetchRouter?

While possible, using `fetchRouter` from [`src/mitm/handlers/base.js`](https://github.com/decolua/9router/blob/main/src/mitm/handlers/base.js) is recommended because it handles credential injection from `providerStore`, request signing, and response streaming consistently across all providers. It ensures your complex API logic integrates seamlessly with 9router's MITM architecture and supports transparent SSE piping through `pipeSSE()`.