# How to Handle Request Validation with OmniRoute: Zod Schemas and ValidatedJsonBody

> Learn how to handle request validation with OmniRoute using Zod schemas. Automatically get strongly-typed data or a 400 response for invalid requests.

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

---

**OmniRoute validates every incoming API request using Zod schemas and the `validatedJsonBody` helper, which returns a discriminated union containing either strongly-typed data or a pre-built 400 Bad Request response.**

OmniRoute provides a robust, type-safe approach to API request validation built on Zod schemas. By leveraging the `validatedJsonBody` helper function located in [`src/shared/validation/helpers.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/validation/helpers.ts), developers can enforce strict payload contracts while maintaining consistent error responses across all endpoints. This guide explains how to implement request validation in your OmniRoute API routes using the exact patterns found in the diegosouzapw/OmniRoute source code.

## The Core Validation Pattern

OmniRoute centralizes request validation through a single async helper that combines JSON parsing with schema enforcement. This eliminates boilerplate and ensures uniform error handling across the entire API surface.

### The ValidatedJsonBody Helper

The `validatedJsonBody` function in [`src/shared/validation/helpers.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/validation/helpers.ts) accepts a standard Web API `Request` object and a Zod schema, returning a discriminated union:

```typescript
// src/shared/validation/helpers.ts
export async function validatedJsonBody<TSchema extends z.ZodTypeAny>(
  request: Request,
  schema: TSchema
): Promise<ValidatedJsonBodyResult<z.infer<TSchema>>>

```

The function returns one of two shapes:

- **`{ success: true, data }`** – The payload is valid and typed as `z.infer<typeof schema>`
- **`{ success: false, response }`** – The payload failed validation or is malformed JSON; `response` is a ready-to-return `NextResponse` with a standard error envelope

## Step-by-Step Implementation

### Define Your Zod Schema

All validation schemas live under `src/shared/validation/schemas/` and are exported as named constants. Define the exact shape of your request payload using Zod:

```typescript
// src/shared/validation/schemas/combo.ts
import { z } from "zod";

export const createComboSchema = z.object({
  name: z.string().min(1),
  targets: z.array(z.string()),
  // …other fields
});

```

### Parse and Validate the Request Body

Import your schema and the helper into your route handler. Call `validatedJsonBody` at the very start of the handler:

```typescript
// src/app/api/v1/combo/route.ts
import { validatedJsonBody } from "@/shared/validation/helpers";
import { createComboSchema } from "@/shared/validation/schemas/combo";

export async function POST(request: Request) {
  const parsed = await validatedJsonBody(request, createComboSchema);
  if (!parsed.success) return parsed.response;   // Returns 400 Bad Request immediately

  const { name, targets } = parsed.data;        // Fully typed as z.infer<typeof createComboSchema>
  // …business logic
}

```

### Handle PATCH and Complex Updates

The same pattern applies to partial updates or specialized endpoints. In [`src/app/api/v1/settings/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/settings/route.ts), the handler validates settings updates with a dedicated schema:

```typescript
// src/app/api/v1/settings/route.ts
import { validatedJsonBody } from "@/shared/validation/helpers";
import { settingsUpdateSchema } from "@/shared/validation/schemas/settings";

export async function PATCH(request: Request) {
  const body = await validatedJsonBody(request, settingsUpdateSchema);
  if (!body.success) return body.response;           // 400 Bad Request

  // `body.data` is typed as the exact shape of `settingsUpdateSchema`
  await updateSettings(body.data);
  return new Response(null, { status: 204 });
}

```

## Standard Error Response Format

When validation fails, OmniRoute returns a consistent JSON envelope that allows clients to rely on a single error-parsing routine:

```json
{
  "error": {
    "message": "Invalid request",
    "details": [
      { "field": "name", "message": "String must contain at least 1 character(s)" },
      { "field": "targets", "message": "Expected array, received undefined" }
    ]
  }
}

```

This structure is automatically generated by `validatedJsonBody` whenever the Zod schema rejects the input or the request body contains malformed JSON. The helper returns a `NextResponse` with status 400 and the above payload, eliminating the need for manual error formatting in individual routes.

## Middleware Integration and Execution Order

Request validation occurs at a specific point in the middleware pipeline. According to the source code in [`src/app/api/v1/completions/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/completions/route.ts), the execution order is intentional:

1. CORS handling
2. Compression header processing
3. Prompt injection guards (when applicable)
4. **`validatedJsonBody` validation**
5. Business logic execution

This ordering ensures that cross-cutting concerns like CORS are handled before expensive validation or processing occurs:

```typescript
// src/app/api/v1/completions/route.ts (excerpt)
export async function POST(request: Request) {
  await ensureInitialized();
  const compressionRequestHeader = readCompressionRequestHeader(request);
  // …injection guard logic…
  
  const parsed = await validatedJsonBody(request, completionSchema);
  if (!parsed.success) return parsed.response;
  
  // …handleChat business logic…
}

```

## Reusing Validation in Playground Endpoints

The pattern scales to development utilities like the playground endpoint in [`src/app/api/v1/playground/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/playground/route.ts):

```typescript
// src/app/api/v1/playground/route.ts
import { validatedJsonBody } from "@/shared/validation/helpers";
import { playgroundPromptSchema } from "@/shared/validation/schemas/playground";

export async function POST(request: Request) {
  const result = await validatedJsonBody(request, playgroundPromptSchema);
  if (!result.success) return result.response;

  const answer = await runPromptEngine(result.data);
  return new Response(JSON.stringify({ answer }), { status: 200 });
}

```

## Summary

- **Centralized validation**: OmniRoute uses the `validatedJsonBody` helper in [`src/shared/validation/helpers.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/validation/helpers.ts) to parse JSON and validate against Zod schemas in a single operation.
- **Discriminated unions**: The helper returns either `{success: true, data}` with inferred types or `{success: false, response}` with a pre-built 400 error.
- **Schema location**: All Zod schemas reside in `src/shared/validation/schemas/` and are imported as named exports into route handlers.
- **Consistent errors**: Failed validations automatically return a standardized error envelope with field-level details, ensuring uniform API contracts.
- **Pipeline positioning**: Validation executes after CORS, compression, and security guards but before business logic, as shown in [`src/app/api/v1/completions/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/completions/route.ts).

## Frequently Asked Questions

### What validation library does OmniRoute use?

OmniRoute uses **Zod** for runtime type validation. Every request body is validated against Zod schemas defined in `src/shared/validation/schemas/`, providing both type safety at build time and runtime data integrity.

### Where are validation schemas stored in OmniRoute?

All validation schemas are stored in the `src/shared/validation/schemas/` directory. Each domain entity (combos, settings, completions, playground) has its own file exporting named schema constants like `createComboSchema` or `settingsUpdateSchema`.

### How does `validatedJsonBody` handle malformed JSON?

If the request body contains malformed JSON or fails Zod validation, `validatedJsonBody` returns `{ success: false, response }` where `response` is a `NextResponse` with status 400 and a standard error envelope containing specific field-level error messages.

### Can I use `validatedJsonBody` with Next.js App Router?

Yes. The `validatedJsonBody` function is designed specifically for Next.js App Router API routes. It accepts the standard Web API `Request` object provided by Next.js route handlers and returns responses compatible with the `NextResponse` type used throughout the App Router.