Schema Validation
Schema validations are an optional configuration in createEndpoint and createEndpointConfig. They enable runtime validation for params, searchParams, and body, ensuring full type safety for the endpoint's request context.
import { z } from "zod"
import { createEndpoint } from "@aura-stack/router"
export const endpoint = createEndpoint(
"POST",
"/auth/signIn",
async (ctx) => {
const { username, password } = ctx.body
return Response.json({ message: "Successful Login" })
},
{
schemas: {
body: z.object({
username: z.string(),
password: z.string(),
}),
},
}
)Overview
Key Concepts
- Detailed information about schema validations within
createEndpointcan be found in the API Reference. - Schema validation is optional but highly recommended for adding runtime validation to:
paramsviaschemas.paramssearchParamsviaschemas.searchParamsbodyviaschemas.body
- Supported schema engines are Zod, Valibot, ArkType, and TypeBox.
Type Inference
When supported schemas are added, the endpoint automatically extracts and infers types from them. These inferred types apply to:
- The
Route Handler Request Context. - The
Middleware Context.
This ensures fully typed access to ctx.params, ctx.searchParams, and ctx.body.
Installation
To enable schema validation, install the schema package(s) you plan to use.
npm install @aura-stack/router zodnpm install @aura-stack/router valibotnpm install @aura-stack/router arktypenpm install @aura-stack/router typeboxSchemas Validation
Schema validations can be passed directly to an endpoint or defined using the createEndpointConfig function.
Directly
The schema can be passed as the fourth argument of createEndpoint.
import { z } from "zod"
import { createEndpoint } from "@aura-stack/router"
export const deleteUser = createEndpoint(
"DELETE",
"/users/:userId",
async (ctx) => {
const { userId } = ctx.params
return Response.json({ message: "User deleted successfully" })
},
{
schemas: {
params: z.object({
userId: z.coerce.number(),
}),
},
}
)import * as valibot from "valibot"
import { createEndpoint } from "@aura-stack/router"
export const deleteUser = createEndpoint(
"DELETE",
"/users/:userId",
async (ctx) => {
const { userId } = ctx.params
return Response.json({ message: "User deleted successfully" })
},
{
schemas: {
params: valibot.object({
userId: valibot.number(),
}),
},
}
)import { type } from "arktype"
import { createEndpoint } from "@aura-stack/router"
export const deleteUser = createEndpoint(
"DELETE",
"/users/:userId",
async (ctx) => {
const { userId } = ctx.params
return Response.json({ message: "User deleted successfully" })
},
{
schemas: {
params: type({
userId: "number",
}),
},
}
)import { Type } from "typebox"
import { createEndpoint } from "@aura-stack/router"
export const deleteUser = createEndpoint(
"DELETE",
"/users/:userId",
async (ctx) => {
const { userId } = ctx.params
return Response.json({ message: "User deleted successfully" })
},
{
schemas: {
params: Type.Object({
userId: Type.Number(),
}),
},
}
)Using createEndpointConfig
The createEndpointConfig function can also be used, passing the configuration object as the fourth argument to createEndpoint.
When the endpoint requires a dynamic parameter, the route path should be provided as the first argument.
import { z } from "zod"
import { createEndpoint, createEndpointConfig } from "@aura-stack/router"
export const config = createEndpointConfig("/users/:userId", {
schemas: {
params: z.object({
userId: z.number(),
}),
},
})
export const deleteUser = createEndpoint(
"DELETE",
"/users/:userId",
async (ctx) => {
const { userId } = ctx.params
return Response.json({ message: "User deleted successfully" })
},
config
)import * as valibot from "valibot"
import { createEndpoint, createEndpointConfig } from "@aura-stack/router"
export const config = createEndpointConfig("/users/:userId", {
schemas: {
params: valibot.object({
userId: valibot.number(),
}),
},
})
export const deleteUser = createEndpoint(
"DELETE",
"/users/:userId",
async (ctx) => {
const { userId } = ctx.params
return Response.json({ message: "User deleted successfully" })
},
config
)import { type } from "arktype"
import { createEndpoint, createEndpointConfig } from "@aura-stack/router"
export const config = createEndpointConfig("/users/:userId", {
schemas: {
params: type({
userId: "number",
}),
},
})
export const deleteUser = createEndpoint(
"DELETE",
"/users/:userId",
async (ctx) => {
const { userId } = ctx.params
return Response.json({ message: "User deleted successfully" })
},
config
)import { Type } from "typebox"
import { createEndpoint, createEndpointConfig } from "@aura-stack/router"
export const config = createEndpointConfig("/users/:userId", {
schemas: {
params: Type.Object({
userId: Type.Number(),
}),
},
})
export const deleteUser = createEndpoint(
"DELETE",
"/users/:userId",
async (ctx) => {
const { userId } = ctx.params
return Response.json({ message: "User deleted successfully" })
},
config
)Params
Basic Validation
import { z } from "zod"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig("/users/:userId/books/:bookId", {
schemas: {
params: z.object({
userId: z.regex(/^[0-9]+$/),
bookId: z.uuid(),
}),
},
})
export const getBookById = createEndpoint(
"GET",
"/users/:userId/books/:bookId",
async (ctx) => {
const { userId, bookId } = ctx.params
return Response.json({ bookId })
},
config
)import * as valibot from "valibot"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
const config = createEndpointConfig("/users/:userId/books/:bookId", {
schemas: {
params: valibot.object({
userId: valibot.regex(/^[0-9]+$/, "Invalid userId format"),
bookId: valibot.uuid("Invalid bookId format"),
}),
},
})
export const getBookById = createEndpoint(
"GET",
"/users/:userId/books/:bookId",
async (ctx) => {
const { userId, bookId } = ctx.params
return Response.json({ bookId })
},
config
)import { type } from "arktype"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig("/users/:userId/books/:bookId", {
schemas: {
params: type({
userId: "string",
bookId: "string.uuid",
}),
},
})
export const getBookById = createEndpoint(
"GET",
"/users/:userId/books/:bookId",
async (ctx) => {
const { userId, bookId } = ctx.params
return Response.json({ bookId })
},
config
)import { Type } from "typebox"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig("/users/:userId/books/:bookId", {
schemas: {
params: Type.Object({
userId: Type.String({ format: "regex", pattern: "^[0-9]+$" }),
bookId: Type.String({ format: "uuid" }),
}),
},
})
export const getBookById = createEndpoint(
"GET",
"/users/:userId/books/:bookId",
async (ctx) => {
const { userId, bookId } = ctx.params
return Response.json({ bookId })
},
config
)Search Params Validation
Basic Validation
import { z } from "zod"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
searchParams: z.object({
q: z.string().min(1),
page: z.coerce.number().int().positive().default(1),
}),
},
})
export const searchUsers = createEndpoint(
"GET",
"/users/search",
async (ctx) => {
const { q, page } = ctx.searchParams
return Response.json({ query: q, page, results: [] })
},
config
)import * as valibot from "valibot"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
searchParams: valibot.object({
q: valibot.pipe(valibot.string(), valibot.minLength(1)),
page: valibot.fallback(
valibot.pipe(
valibot.unknown(),
valibot.transform((input) => Number(input)),
valibot.number(),
valibot.integer(),
valibot.positive()
),
1
),
}),
},
})
export const searchUsers = createEndpoint(
"GET",
"/users/search",
async (ctx) => {
const { q, page } = ctx.searchParams
return Response.json({ query: q, page, results: [] })
},
config
)import { type } from "arktype"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
searchParams: type({
q: "string > 0",
page: "(number.integer > 0) = 1",
});
},
})
export const searchUsers = createEndpoint(
"GET",
"/users/search",
async (ctx) => {
const { q, page } = ctx.searchParams
return Response.json({ query: q, page, results: [] })
},
config
)import { Type } from "typebox"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
searchParams: Type.Object({
q: Type.String({ minLength: 1 }),
page: Type.Transform(
Type.Integer({
minimum: 1,
default: 1
})
)
.Decode(value => Number(value))
.Encode(value => value)
});
},
})
export const searchUsers = createEndpoint(
"GET",
"/users/search",
async (ctx) => {
const { q, page } = ctx.searchParams
return Response.json({ query: q, page, results: [] })
},
config
)Advanced Validation
import { z } from "zod";
import { createEndpointConfig } from "@aura-stack/router";
export const config = createEndpointConfig({
schemas: {
searchParams: z.object({
q: z.string().min(3).max(100),
category: z.enum(["tech", "science", "arts"]).optional(),
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
sortBy: z.enum(["date", "relevance", "popularity"]).default("relevance"),
sortOrder: z.enum(["asc", "desc"]).default("desc"),
}),
},
})import * as valibot from "valibot"
import { createEndpointConfig } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
searchParams: valibot.object({
q: valibot.pipe(valibot.string(), valibot.minLength(3), valibot.maxLength(100)),
category: valibot.optional(valibot.enum_(["tech", "science", "arts"])),
page: valibot.fallback(
valibot.pipe(valibot.unknown(), valibot.transform(Number), valibot.integer(), valibot.minValue(1)),
1
),
limit: valibot.fallback(
valibot.pipe(valibot.unknown(), valibot.transform(Number), valibot.integer(), valibot.minValue(1), valibot.maxValue(100)),
20
),
sortBy: valibot.fallback(valibot.enum_(["date", "relevance", "popularity"]), "relevance"),
sortOrder: valibot.fallback(valibot.enum_(["asc", "desc"]), "desc"),
}),
},
})import { type } from "arktype";
import { createEndpointConfig } from "@aura-stack/router";
export const config = createEndpointConfig({
schemas: {
searchParams: type({
q: "3<=string<=100",
"category?": "'tech'|'science'|'arts'",
page: "(number.integer >= 1) = 1",
limit: "(1 <= number.integer <= 100) = 20",
sortBy: "('date'|'relevance'|'popularity') = 'relevance'",
sortOrder: "('asc'|'desc') = 'desc'",
}),
},
});import { Type } from "typebox"
import { createEndpointConfig } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
searchParams: Type.Object({
q: Type.String({ minLength: 3, maxLength: 100 }),
category: Type.Optional(Type.Union([Type.Literal("tech"), Type.Literal("science"), Type.Literal("arts")])),
page: Type.Integer({ minimum: 1, default: 1 }),
limit: Type.Integer({ minimum: 1, maximum: 100, default: 20 }),
sortBy: Type.Union([Type.Literal("date"), Type.Literal("relevance"), Type.Literal("popularity")], { default: "relevance" }),
sortOrder: Type.Union([Type.Literal("asc"), Type.Literal("desc")], { default: "desc" }),
}),
},
})Request Body Validation
Basic Validation
import { z } from "zod"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
body: z.object({
name: z.string().min(1),
email: z.string().email(),
}),
},
})
export const createUser = createEndpoint(
"POST",
"/users",
async (ctx) => {
const { name, email } = ctx.body
return Response.json({ id: "new-id", name, email }, { status: 201 })
},
config
)import * as valibot from "valibot"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
body: valibot.object({
name: valibot.pipe(valibot.string(), valibot.minLength(1)),
email: valibot.pipe(valibot.string(), valibot.email()),
}),
},
})
export const createUser = createEndpoint(
"POST",
"/users",
async (ctx) => {
const { name, email } = ctx.body
return Response.json({ id: "new-id", name, email }, { status: 201 })
},
config
)import { type } from "arktype"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
body: type({
name: "string > 0",
email: "string.email",
}),
},
})
export const createUser = createEndpoint(
"POST",
"/users",
async (ctx) => {
const { name, email } = ctx.body
return Response.json({ id: "new-id", name, email }, { status: 201 })
},
config
)import { Type } from "typebox"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
body: Type.Object({
name: Type.String({ minLength: 1 }),
email: Type.String({ format: "email" }),
}),
},
})
export const createUser = createEndpoint(
"POST",
"/users",
async (ctx) => {
const { name, email } = ctx.body
return Response.json({ id: "new-id", name, email }, { status: 201 })
},
config
)Advanced Validation
import { z } from "zod"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
body: z.object({
profile: z.object({
firstName: z.string().min(1),
lastName: z.string().min(1),
bio: z.string().max(500).optional(),
}),
settings: z.object({
theme: z.enum(["light", "dark"]).default("light"),
notifications: z.boolean().default(true),
language: z.string().length(2).default("en"),
}),
tags: z.array(z.string()).min(1).max(10),
}),
},
})
export const updateProfile = createEndpoint(
"PATCH",
"/profile",
async (ctx) => {
const { profile, settings, tags } = ctx.body
return Response.json({ updated: true })
},
config
)import * as valibot from "valibot"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
body: valibot.object({
profile: valibot.object({
firstName: valibot.pipe(valibot.string(), valibot.minLength(1)),
lastName: valibot.pipe(valibot.string(), valibot.minLength(1)),
bio: valibot.optional(valibot.pipe(valibot.string(), valibot.maxLength(500))),
}),
settings: valibot.object({
theme: valibot.fallback(valibot.enum_(["light", "dark"]), "light"),
notifications: valibot.fallback(valibot.boolean(), true),
language: valibot.fallback(valibot.pipe(valibot.string(), valibot.length(2)), "en"),
}),
tags: valibot.pipe(valibot.array(valibot.string()), valibot.minLength(1), valibot.maxLength(10)),
}),
},
})
export const updateProfile = createEndpoint(
"PATCH",
"/profile",
async (ctx) => {
const { profile, settings, tags } = ctx.body
return Response.json({ updated: true })
},
config
)import { type } from "arktype"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
body: type({
profile: {
firstName: "string > 0",
lastName: "string > 0",
"bio?": "string <= 500",
},
settings: {
theme: "('light'|'dark') = 'light'",
notifications: "boolean = true",
language: "string == 2 = 'en'",
},
tags: "1 <= string[] <= 10",
}),
},
})
export const updateProfile = createEndpoint(
"PATCH",
"/profile",
async (ctx) => {
const { profile, settings, tags } = ctx.body
return Response.json({ updated: true })
},
config
)import { Type } from "typebox"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"
export const config = createEndpointConfig({
schemas: {
body: Type.Object({
profile: Type.Object({
firstName: Type.String({ minLength: 1 }),
lastName: Type.String({ minLength: 1 }),
bio: Type.Optional(Type.String({ maxLength: 500 })),
}),
settings: Type.Object({
theme: Type.Union([Type.Literal("light"), Type.Literal("dark")], { default: "light" }),
notifications: Type.Boolean({ default: true }),
language: Type.String({ minLength: 2, maxLength: 2, default: "en" }),
}),
tags: Type.Array(Type.String(), { minItems: 1, maxItems: 10 }),
}),
},
})
export const updateProfile = createEndpoint(
"PATCH",
"/profile",
async (ctx) => {
const { profile, settings, tags } = ctx.body
return Response.json({ updated: true })
},
config
)