Validate
The Validate middleware provides type-safe request validation using a factory function pattern. It supports multiple validation sources and works with any Standard Schema compliant library (Zod, Valibot, ArkType, etc.).
Quick Start
import { TabiApp } from "@tabirun/app";
import { validator } from "@tabirun/app/validate";
import { z } from "zod";
const app = new TabiApp();
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
const { validate, valid } = validator({ json: loginSchema });
app.post("/login", validate, (c) => {
// valid(c).json is automatically typed as { email: string; password: string }
const { json } = valid(c);
return c.json({ email: json.email });
});
Deno.serve(app.handler);How It Works
The validator function creates a middleware and a type-safe accessor. The
middleware validates request data against your schemas, and the accessor
provides typed access to validated data. Validation errors return 400 Bad
Request by default.
API
Factory Pattern
const { validate, valid } = validator(config, options?);
app.post(path, validate, (c) => {
const validated = valid(c);
});Validation Config
Specify which request sources to validate:
| Source | Description | Example Data |
|---|---|---|
json |
Request body as JSON | { "name": "Alice" } |
form |
Form data | firstName=John&lastName=Doe |
search |
URL query parameters | ?year=2021&month=01 |
params |
Route parameters | /users/:id -> { id: "123" } |
Validation Options
| Option | Type | Default | Description |
|---|---|---|---|
reportErrors |
boolean |
false |
Include validation errors in response |
onError |
(errors: ValidationError[]) => Response |
- | Custom error handler |
Examples
Multiple Source Validation
Validate multiple request sources at once:
import { TabiApp } from "@tabirun/app";
import { validator } from "@tabirun/app/validate";
import { z } from "zod";
const app = new TabiApp();
const idSchema = z.object({ id: z.string().uuid() });
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
});
const { validate, valid } = validator({
params: idSchema,
json: userSchema,
});
app.put("/users/:id", validate, (c) => {
// Both params and json are automatically typed
const { params, json } = valid(c);
return c.json({ id: params.id, name: json.name, email: json.email });
});With Other Middleware
Combine with authentication and logging:
import { TabiApp } from "@tabirun/app";
import { validator } from "@tabirun/app/validate";
import { z } from "zod";
const app = new TabiApp();
const { validate, valid } = validator({ json: loginSchema });
app.post("/protected", auth, logging, validate, (c) => {
const { json } = valid(c);
return c.json(json);
});Error Reporting
Include validation errors in responses:
import { TabiApp } from "@tabirun/app";
import { validator } from "@tabirun/app/validate";
import { z } from "zod";
const app = new TabiApp();
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
});
const { validate, valid } = validator(
{ json: userSchema },
{ reportErrors: true },
);
app.post("/users", validate, (c) => {
const { json } = valid(c);
return c.json(json);
});Custom Error Handler
Provide custom error handling:
import { TabiApp } from "@tabirun/app";
import { validator } from "@tabirun/app/validate";
import { z } from "zod";
const app = new TabiApp();
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
});
const { validate, valid } = validator(
{ json: userSchema },
{
onError: (errors) => {
return new Response(
JSON.stringify({ message: "Validation failed", errors }),
{ status: 422 },
);
},
},
);
app.post("/users", validate, (c) => {
const { json } = valid(c);
return c.json(json);
});Query Parameter Validation
Validate URL search parameters:
import { TabiApp } from "@tabirun/app";
import { validator } from "@tabirun/app/validate";
import { z } from "zod";
const app = new TabiApp();
const searchSchema = z.object({
page: z.coerce.number().min(1).default(1),
limit: z.coerce.number().min(1).max(100).default(20),
sort: z.enum(["asc", "desc"]).default("desc"),
});
const { validate, valid } = validator({ search: searchSchema });
app.get("/users", validate, (c) => {
const { search } = valid(c);
// search is typed as { page: number; limit: number; sort: "asc" | "desc" }
return c.json({ page: search.page, limit: search.limit });
});Form Data Validation
Validate form submissions:
import { TabiApp } from "@tabirun/app";
import { validator } from "@tabirun/app/validate";
import { z } from "zod";
const app = new TabiApp();
const contactSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
message: z.string().min(10),
});
const { validate, valid } = validator({ form: contactSchema });
app.post("/contact", validate, (c) => {
const { form } = valid(c);
// Process contact form...
return c.json({ received: true });
});Notes
- Uses Standard Schema V1 specification (works with Zod, Valibot, ArkType, etc.)
- Type inference is automatic - no manual type annotations needed
- Validated data is readonly to prevent accidental modification
- Validates all sources before returning errors (error accumulation, not fail-fast)
- Returns 400 Bad Request by default on validation failure
- Uses unique string-based context storage to avoid key collisions
- Empty config
validator({})is a no-op but still providesvalid(c) - Standard middleware pattern - works with all other Tabi middleware
Related
- Middleware System - How middleware works
- Request/Response - Accessing request data
- Error Handling - Handling validation errors