# How LunaTV Handles Runtime Validation with Zod in Next.js API Routes

> Learn how LunaTV uses Zod for robust runtime validation in Next.js API routes. Discover how safeParse ensures data integrity and returns clear error responses for invalid payloads.

- Repository: [MoonTechLab/LunaTV](https://github.com/MoonTechLab/LunaTV)
- Tags: how-to-guide
- Published: 2026-09-08

---

**LunaTV validates all incoming HTTP requests at runtime using Zod schemas in its Next.js API routes, calling `safeParse()` to verify JSON payloads and returning structured 400 Bad Request responses when validation fails.**

MoonTechLab/LunaTV leverages the **Zod** library for comprehensive runtime validation across its Next.js application. Every API route handler verifies incoming data against strict schemas before executing business logic. This defensive pattern ensures that only well-formed, type-safe data reaches authentication and database layers, preventing runtime crashes and providing clear error feedback to API consumers.

## Defining Zod Schemas for Request Validation

Validation schemas in LunaTV are defined using Zod’s fluent API, either inline within route handlers or in dedicated modules for reuse. The codebase stores reusable schemas in `src/lib/schemas/` while keeping route-specific validation close to the handler logic.

In [`src/lib/schemas/user.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/schemas/user.ts), the repository defines a registration schema with strict constraints:

```typescript
import { z } from 'zod';

export const RegisterSchema = z.object({
  username: z.string().min(3),
  password: z.string().min(8),
  email: z.string().email(),
});

```

For the login endpoint in [`src/app/api/login/route.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/api/login/route.ts), the schema enforces minimum length requirements and custom error messages:

```typescript
import { z } from 'zod';

const loginSchema = z.object({
  username: z.string().min(1, 'Username required'),
  password: z.string().min(6, 'Password too short'),
});

```

This approach keeps validation logic declarative and composable, allowing complex nested objects to be built from reusable schema fragments.

## Runtime Validation in API Route Handlers

When a request reaches a LunaTV API endpoint, the handler immediately validates the raw request body against the predefined Zod schema. The codebase uses `safeParse()` rather than `parse()` to handle validation failures gracefully without throwing exceptions.

### Using safeParse() for Defensive Validation

The `safeParse()` method returns an object containing either the validated data or a formatted error, enabling the handler to branch logic based on validation success:

```typescript
export async function POST(req: Request) {
  const parsed = RegisterSchema.safeParse(await req.json());

  if (!parsed.success) {
    return NextResponse.json(
      { error: parsed.error.format() },
      { status: 400 }
    );
  }

  const { username, password, email } = parsed.data;
  // Continue with registration logic using type-safe values
}

```

This pattern appears consistently across LunaTV’s route handlers, including the login implementation in [`src/app/api/login/route.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/api/login/route.ts):

```typescript
const result = loginSchema.safeParse(await req.json());
if (!result.success) {
  return NextResponse.json({ error: result.error.format() }, { status: 400 });
}
const { username, password } = result.data;

```

### Alternative: Using parse() and Try-Catch

While `safeParse()` is preferred for explicit error handling, the codebase also demonstrates catching `ZodError` exceptions thrown by `parse()` in broader try-catch blocks that handle unexpected server errors. This dual-layer approach separates validation failures (400 responses) from server exceptions (500 responses).

## Error Handling and Response Patterns

LunaTV implements a strict error handling hierarchy that distinguishes between client validation errors and server failures.

For validation failures, the handler extracts structured error messages using `error.format()` and returns a **400 Bad Request** status:

```typescript
return NextResponse.json(
  { error: parsed.error.format() },
  { status: 400 }
);

```

For unexpected runtime errors or database failures, the catch block logs the error (e.g., `console.error('登录接口异常', error)`) and returns a **500 Internal Server Error** with a generic message to prevent information leakage:

```typescript
} catch (error) {
  console.error('登录接口异常', error);
  return NextResponse.json({ error: '服务器错误' }, { status: 500 });
}

```

This separation ensures that malformed payloads receive detailed corrective feedback while server issues remain opaque to potential attackers.

## Type Safety and Developer Experience

Zod provides LunaTV with **static type inference**, eliminating the need to maintain separate TypeScript interfaces. After calling `safeParse()`, the `data` property carries the inferred TypeScript type from the schema, giving the IDE and compiler full visibility into field types without additional type declarations.

The dependency is declared in [`package.json`](https://github.com/MoonTechLab/LunaTV/blob/main/package.json) as `"zod": "^3.24.1"`, locking the project to a stable version of the library while allowing patch updates.

## Summary

- **Schema Definition**: LunaTV declares Zod schemas in `src/lib/schemas/` or inline within `src/app/api/*` route files using `z.object()` and chainable validators like `.min()` and `.email()`.
- **Safe Parsing**: Route handlers use `schema.safeParse()` to validate `await req.json()` without throwing exceptions, checking the `success` property before accessing `data`.
- **Error Responses**: Validation failures trigger immediate **400 Bad Request** responses containing `error.format()` details, while unexpected errors trigger **500** responses after logging.
- **Type Inference**: Validated data objects receive automatic TypeScript typing from Zod schemas, ensuring end-to-end type safety from HTTP request to database query.

## Frequently Asked Questions

### What version of Zod does LunaTV use?

According to [`package.json`](https://github.com/MoonTechLab/LunaTV/blob/main/package.json), LunaTV depends on Zod version `^3.24.1`. This version provides the `safeParse()` and `format()` methods used throughout the API route handlers.

### Why does LunaTV use safeParse() instead of parse()?

LunaTV uses `safeParse()` because it returns a result object rather than throwing a `ZodError` on failure. This pattern allows the code to handle validation errors as control flow (returning 400 responses) without requiring try-catch blocks specifically for validation failures, while reserving exception handling for genuine server errors.

### How does LunaTV structure its Zod schemas?

The codebase centralizes reusable schemas in `src/lib/schemas/` (such as [`user.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/user.ts) containing `RegisterSchema`), while route-specific schemas may be defined directly in the route handler file. This hybrid approach keeps common validation logic DRY while allowing endpoint-specific constraints to remain colocated with the handlers that consume them.

### What happens when validation fails in LunaTV’s API routes?

When `safeParse()` returns `success: false`, the handler immediately returns a **400 Bad Request** response containing the formatted Zod errors via `result.error.format()`. This provides API consumers with clear, field-level error messages indicating exactly which constraints failed.