Aura Router

Zod Schemas

Schema validations are an optional configuration in createEndpoint and createEndpointConfig. They enable Zod-based validations 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 createEndpoint can be found in the API Reference.
  • Zod is not included in @aura-stack/router. To use validations, it must be installed separately.
  • Zod schemas are optional but highly recommended for adding runtime validation to:
    • params via schemas.params
    • searchParams via schemas.searchParams
    • body via schemas.body

Type Inference

When Zod 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 zod.

npm install @aura-stack/router zod

Schemas 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(),
      }),
    },
  }
)

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"

const config = createEndpointConfig("/users/:userId", {
  schemas: {
    params: z.object({
      userId: z.coerce.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"

const dynamicsConfig = createEndpointConfig("/users/:userId/books/:bookId", {
  schemas: {
    params: z.object({
      userId: z.regex(/^[0-9]+$/),
      bookId: z.uuid(),
    }),
  },
})

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

Search Params Validation

Basic Validation

import { z } from "zod"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"

const searchConfig = createEndpointConfig({
  schemas: {
    searchParams: z.object({
      q: z.string().min(1),
      page: z.coerce.number().int().positive().default(1),
    }),
  },
})

const searchUsers = createEndpoint(
  "GET",
  "/users/search",
  async (ctx) => {
    const { q, page } = ctx.searchParams
    return Response.json({ query: q, page, results: [] })
  },
  searchConfig
)

Advanced Validation

import { z } from "zod"
import { createEndpointConfig } from "@aura-stack/router"

const advancedSearchConfig = 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"),
    }),
  },
})

Request Body Validation

Basic Validation

import { z } from "zod"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"

const createUserConfig = createEndpointConfig({
  schemas: {
    body: z.object({
      name: z.string().min(1),
      email: z.string().email(),
    }),
  },
})

const createUser = createEndpoint(
  "POST",
  "/users",
  async (ctx) => {
    const { name, email } = ctx.body

    return Response.json({ id: "new-id", name, email }, { status: 201 })
  },
  createUserConfig
)

Advanced Validation

import { z } from "zod"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"

const updateProfileConfig = 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),
    }),
  },
})

const updateProfile = createEndpoint(
  "PATCH",
  "/profile",
  async (ctx) => {
    const { profile, settings, tags } = ctx.body
    return Response.json({ updated: true })
  },
  updateProfileConfig
)

Combined Validation

Both searchParams and body can be validated in the same endpoint.

import { z } from "zod"
import { createEndpointConfig, createEndpoint } from "@aura-stack/router"

const createPostConfig = createEndpointConfig({
  schemas: {
    body: z.object({
      title: z.string().min(1).max(200),
      content: z.string().min(1),
      tags: z.array(z.string()).optional(),
    }),
    searchParams: z.object({
      publish: z.enum(["true", "false"]).default("false"),
      notify: z.enum(["true", "false"]).default("true"),
    }),
  },
})

const createPost = createEndpoint(
  "POST",
  "/posts",
  async (ctx) => {
    const { title, content, tags } = ctx.body
    const { publish, notify } = ctx.searchParams

    return Response.json(
      {
        id: "new-post",
        title,
        content,
        tags,
        published: publish === "true",
        notified: notify === "true",
      },
      { status: 201 }
    )
  },
  createPostConfig
)

On this page