# Validation

Zod schemas plug directly into routes via the `request` option. The framework parses and validates the request body, query string, and path parameters before the handler runs.

## Defining schemas

```ts
const CreateBookSchema = z.object({
  title: z.string().trim().min(1).max(200),
  authorId: z.uuid(),
  genre: z.enum(["fiction", "science-fiction", "fantasy", "non-fiction"]),
  description: z.string().max(5000).optional(),
});

const BookQuerySchema = z.object({
  genre: z.enum(["fiction", "science-fiction", "fantasy", "non-fiction"]).optional(),
  sort: z.enum(["title", "createdAt", "rating"]).default("title"),
  page: z.coerce.number().int().min(1).default(1),
  limit: z.coerce.number().int().min(1).max(100).default(20),
});
```

## Attaching schemas to routes

```ts
route.post("/books", {
  request: {
    body: CreateBookSchema,
  },
  resolve: (c) => {
    if (!c.input.ok) {
      return Response.json({ error: c.input.issues }, { status: 400 });
    }
    // c.input.body is typed as { title: string, authorId: string, genre: string, description?: string }
  },
});

route.get("/books", {
  request: {
    query: BookQuerySchema,
  },
  resolve: (c) => {
    if (!c.input.ok) {
      return Response.json({ error: c.input.issues }, { status: 400 });
    }
    // c.input.query is typed as { genre?: string, sort: string, page: number, limit: number }
  },
});
```

## Body validation

The `body` schema validates the parsed JSON body of POST, PUT, and PATCH requests. The framework calls `request.json()` and passes the result through the Zod schema.

```ts
request: {
  body: z.object({
    title: z.string().min(1),
    tags: z.array(z.string()).max(10).optional(),
  }),
},
```

## Query validation

The `query` schema validates URL query parameters. Parameters are extracted from the URL and passed through the schema.

Use `z.coerce.number()` for numeric query parameters — query strings are always strings, so coercion converts `"1"` to `1`.

```ts
request: {
  query: z.object({
    page: z.coerce.number().int().min(1).default(1),
    limit: z.coerce.number().int().min(1).max(100).default(20),
    search: z.string().optional(),
  }),
},
```

## Params validation

The `params` schema validates path parameters. You need a params schema to access path parameters on `c.input.params`. Without a schema, `c.input` is not available.

```ts
route.get("/books/:id", {
  request: {
    params: z.object({ id: z.uuid() }),
  },
  resolve: (c) => {
    if (!c.input.ok) {
      return Response.json({ error: "Invalid book ID format" }, { status: 400 });
    }
    const { id } = c.input.params; // validated UUID string
  },
});
```

## Checking validation results

Validation is explicit. The framework does NOT automatically reject invalid requests — you check `c.input.ok` and decide what to do.

```ts
resolve: (c) => {
  // Always check before using validated data
  if (!c.input.ok) {
    return Response.json({
      error: {
        code: "VALIDATION_ERROR",
        message: "Invalid input",
        issues: c.input.issues,
      },
    }, { status: 400 });
  }

  // Safe to use c.input.body, c.input.query, c.input.params
  const { title, genre } = c.input.body;
},
```

## Inferring TypeScript types

Use `z.infer<>` to extract TypeScript types from schemas:

```ts
const CreateBookSchema = z.object({
  title: z.string(),
  genre: z.enum(["fiction", "non-fiction"]),
});

type CreateBook = z.infer<typeof CreateBookSchema>;
// { title: string; genre: "fiction" | "non-fiction" }
```

## Common patterns

### Shared schemas for create and update

```ts
const BookBase = z.object({
  title: z.string().min(1).max(200),
  genre: z.enum(["fiction", "science-fiction", "fantasy", "non-fiction"]),
  description: z.string().max(5000).optional(),
});

const CreateBook = BookBase.extend({ authorId: z.uuid() });
const UpdateBook = BookBase.partial(); // All fields optional
```

### Pagination schema

```ts
const PaginationQuery = z.object({
  page: z.coerce.number().int().min(1).default(1),
  limit: z.coerce.number().int().min(1).max(100).default(20),
});
```
