Endpoint Definitions
Create endpoint definitions in Aura Router
An endpoint definition is the source of truth for a request's TypeScript types. It's a declarative object that describes the request and response shape, along with any parameters, search params, headers, and body that may be required.
An endpoint defines the action or resource a request targets. It must define the HTTP method, the URL path, and the handler function that runs when the request is matched. The handler is responsible for processing the request and returning a response.
import { createEndpoint } from "@aura-stack/router"
const getSession = createEndpoint("GET", "/auth/session", async (ctx) => {
return ctx.json({
session: {
user: {
userId: "uuid-123",
username: "john_doe",
},
},
})
})Use ctx.json instead of Response.json when possible. It preserves the typed response payload, automatically sets the
Content-Type header, and gives you full inference on the client via createClient. See
RequestContext for details.
Request Context
The handler function takes a RequestContext object as its argument. It contains information about the request body, headers, params, search params and plus helpers for building the response.
import { createEndpoint } from "@aura-stack/router"
const getSession = createEndpoint("GET", "/auth/session", async (ctx) => {
if (!ctx.headers.has("Authorization")) {
return ctx.json({ error: "Unauthorized" }, 401)
}
return ctx.json({
session: {
user: {
userId: "uuid-123",
username: "john_doe",
},
},
})
})A one-off check like this is fine inline, but if the same guard needs to run across several endpoints, define it once as an
onRequest hook or a use middleware instead of repeating it in every handler. See Lifecycle -
onRequest.
Configuring Endpoints
The endpoint definition can also include an optional configuration object to customize its behavior. The configuration object can include:
schemas: Validation schemas for validating and typingparams,searchParams,body, andheaders. See Schema Validation.use: middlewares that run once, right before the handler, with access to the fully parsed and validated context. Best suited for cross-cutting logic like auth or logging that several endpoints share.hooks: lifecycle hooks tied to a specific phase of the request/response cycle (onRequest,onParams,onBody,onResponse, and more). Best suited for transforms scoped to a single field, right as it's parsed. See Lifecycle.
The configuration can be defined in two ways: passed directly to createEndpoint, or built separately with createEndpointConfig.
import { z } from "zod"
import { createEndpoint } from "@aura-stack/router"
import { hashPassword } from "@/lib/helpers"
const createUser = createEndpoint(
"POST",
"/users",
async (ctx) => {
const body = ctx.body
return ctx.json({ user: body })
},
{
schemas: {
body: z.object({
username: z.string().min(1),
email: z.string().email(),
password: z.string().min(8),
}),
},
hooks: {
onBody: async ({ body }) => {
body.password = await hashPassword(body.password)
},
},
}
)import { z } from "zod"
import { createEndpoint, createEndpointConfig } from "@aura-stack/router"
const config = createEndpointConfig({
schemas: {
body: z.object({
username: z.string().min(1),
email: z.string().email(),
password: z.string().min(8),
}),
},
hooks: {
onBody: async ({ body }) => {
body.password = await hashPassword(body.password)
},
},
})
const createUser = createEndpoint(
"POST",
"/users",
async (ctx) => {
const body = ctx.body
return ctx.json({ user: body })
},
config
)Lifecycle Execution Order: The onBody hook receives the parsed but unvalidated payload and executes before schema
validation. This means your schema will validate the mutated (hashed) password. If you need to validate the original plaintext
password (e.g., enforcing a minimum length of 8 characters), move your hashing logic to the onHandler hook or directly inside
the endpoint handler, which executes after schema validation succeeds.