Skip to content

defineHandler

The defineHandler function is the core building block for creating API endpoints in Toapi. It ensures end-to-end type safety by connecting your request schema validation with your implementation logic. A route module typically exports one handler per HTTP method (GET, POST, PUT, PATCH, DELETE).

import { defineHandler, TResponse } from "@toapi/server";
import { z } from "zod";
export const GET = defineHandler(
{
authorize: () => true,
query: {
limit: z.coerce.number().min(1).default(10),
},
},
async (req) => {
const { limit } = req.query();
return TResponse.json({ limit });
},
);
function defineHandler<Response, AuthData, Params, Query, Body>(
schema: Schema<Response, AuthData, Params, Query, Body>,
handler: (
req: TRequest<AuthData, Params, Query, Body>,
) => Promise<TResponse<Response>>,
): Handler;

An object defining the input validation, authorization logic, and response shape.

Property Type Description
authorize (req: TRequest) => AuthData | Promise<AuthData> Required. Determines whether the request is allowed. Return a falsy value (or throw) to deny access — the request is rejected with 401 Unauthorized. The return value is accessible via req.auth().
params Record<string, ZodType> Optional. A map of Zod schemas validating path parameters (e.g. /users/:id).
query Record<string, ZodType> Optional. A map of Zod schemas validating query-string parameters.
body ZodType Optional. Zod schema validating the JSON request body, parsed and validated lazily by req.data().
response ZodType Optional. Zod schema describing the success response. Validated at runtime after the handler returns, and used to describe the 200 response in the generated OpenAPI document.

The implementation function that receives the validated request and returns a response.

  • Argument: req — a TRequest object with typed, validated accessors.
  • Returns: a promise resolving to a TResponse.
export const GET = defineHandler(
{ authorize: () => true },
async () => {
return TResponse.json({ message: "Hello World" });
},
);

For a route defined as /users/:id:

export const GET = defineHandler(
{
authorize: () => true,
params: {
id: z.string().uuid(),
},
},
async (req) => {
const { id } = req.params();
return TResponse.json({ userId: id });
},
);
export const POST = defineHandler(
{
authorize: (req) => {
if (!req.headers.get("Authorization")) throw new HttpError(401, "Unauthorized");
return { userId: "current-user" };
},
body: z.object({
title: z.string().min(3),
content: z.string(),
}),
},
async (req) => {
const user = req.auth();
const { title, content } = await req.data();
// Perform database operation...
return TResponse.json({ success: true, author: user.userId });
},
);
export const POST = defineHandler(
{
authorize: (req) => {
if (!req.headers.get("Authorization")) throw new HttpError(401, "Unauthorized");
return { userId: "current-user" };
},
},
async (req) => {
const user = req.auth();
// For multipart/form-data, read the body with `req.formData()` inside the
// handler. The `body` schema is for JSON payloads (parsed from `req.json()`).
const formData = await req.formData();
const parsed = z
.object({
title: z.string().min(3),
content: z.string(),
})
.parse({
title: formData.get("title"),
content: formData.get("content"),
});
return TResponse.json({ success: true, title: parsed.title, author: user.userId });
},
);
  • TRequest — the request object passed to your handler.
  • TResponse — the response helper you return.
  • HttpError — throw typed error responses from authorize or the handler.
  • defineApi — register the handler under a route path.