Aura Router

Params

Learn how to validate request params in Aura Stack Router using schema validation.

Dynamic URL segments can be validated with the schemas.params option, which checks the incoming slugs against the defined schema.

Defining schemas.params lets the endpoint infer the type of the params, accessible via ctx.params. If no schema is defined, each param is instead inferred as a plain string, extracted directly from the route pattern.

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.string().regex(/^[0-9]+$/),
      bookId: z.uuid(),
    }),
  },
})

export const getBookById = createEndpoint(
  "GET",
  "/users/:userId/books/:bookId",
  async (ctx) => {
    const { userId, bookId } = ctx.params
    return ctx.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 ctx.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 ctx.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 ctx.json({ bookId })
  },
  config
)

Before the params are validated, they can be transformed or normalized with the onParams hook — called after params are extracted from the path, but before they're checked against the schema.

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.string().regex(/^[0-9]+$/),
      bookId: z.uuid(),
    }),
  },
  hooks: {
    onParams: ({ params }) => {
      params.userId = params.userId.trim()
      params.bookId = params.bookId.toLowerCase()
    },
  },
})

export const getBookById = createEndpoint(
  "GET",
  "/users/:userId/books/:bookId",
  async (ctx) => {
    const { userId, bookId } = ctx.params
    return ctx.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"),
    }),
  },
  hooks: {
    onParams: ({ params }) => {
      params.userId = params.userId.trim()
      params.bookId = params.bookId.toLowerCase()
    },
  },
})

export const getBookById = createEndpoint(
  "GET",
  "/users/:userId/books/:bookId",
  async (ctx) => {
    const { userId, bookId } = ctx.params
    return ctx.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",
    }),
  },
  hooks: {
    onParams: ({ params }) => {
      params.userId = params.userId.trim()
      params.bookId = params.bookId.toLowerCase()
    },
  },
})

export const getBookById = createEndpoint(
  "GET",
  "/users/:userId/books/:bookId",
  async (ctx) => {
    const { userId, bookId } = ctx.params
    return ctx.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" }),
    }),
  },
  hooks: {
    onParams: ({ params }) => {
      params.userId = params.userId.trim()
      params.bookId = params.bookId.toLowerCase()
    },
  },
})

export const getBookById = createEndpoint(
  "GET",
  "/users/:userId/books/:bookId",
  async (ctx) => {
    const { userId, bookId } = ctx.params
    return ctx.json({ bookId })
  },
  config
)

Type inference for the params comes from the schema definition, so you get type-safe access to ctx.params. That inference describes the shape TypeScript expects, not a runtime guarantee — invalid requests are still rejected by the validator itself before the handler runs.