Skip to content

API Handlers

defineApiHandler creates Lambda handlers for API Gateway endpoints with built-in auth, body validation, and response schema enforcement. Import from @mantleframework/validation.

Pattern

typescript
import { buildValidatedResponse } from '@mantleframework/core'
import { defineApiHandler, z } from '@mantleframework/validation'

const ResponseSchema = z.object({ items: z.array(z.string()) })

const api = defineApiHandler({ auth: 'bearer', operationName: 'ListItems' })
export const handler = api(async ({ context }) => {
  return buildValidatedResponse(context, 200, { items: [] }, ResponseSchema)
})

Options

OptionTypeDefaultDescription
schemaz.ZodSchema<TBody>--Zod schema for request body validation
auth'bearer' | 'none''none'Auth mode
responseSchemaz.ZodSchema--Response schema (optional, deprecated — pass to buildValidatedResponse instead)
operationNamestringfunction nameName for metrics and tracing
openapi{ summary?, tags?, description? }--OpenAPI metadata for generated spec

Handler Params

When no schema is provided (ApiHandlerParams):

typescript
interface ApiHandlerParams {
  event: APIGatewayProxyEvent
  context: Context
  metadata: WrapperMetadata       // { traceId, correlationId }
}

When a schema is provided (ValidatedApiParams<TBody>):

typescript
interface ValidatedApiParams<TBody> {
  event: APIGatewayProxyEvent
  context: Context
  metadata: WrapperMetadata
  body: TBody                     // Validated and typed request body
}

Auth

Set auth: 'bearer' to require a static bearer token. The token is validated from the Authorization header before your handler runs. Unauthenticated requests receive a 401 response automatically.

For health checks, webhooks, or public endpoints omit auth (defaults to 'none'):

typescript
const api = defineApiHandler({ operationName: 'HealthCheck' })
export const handler = api(async ({ context }) => {
  return buildResponse(context, 200, { status: 'ok' })
})

Body Validation

Pass a Zod schema to schema to validate the request body. The validated value is available as body in the handler params, fully typed.

typescript
const SyncPayloadSchema = z.object({
  items: z.array(z.object({ id: z.uuid(), value: z.number() })),
  deviceId: z.uuid(),
})

const api = defineApiHandler({ schema: SyncPayloadSchema, auth: 'bearer', operationName: 'SyncData' })
export const handler = api(async ({ context, body }) => {
  // body is typed as { items: { id: string; value: number }[]; deviceId: string }
  const count = await DataQueries.upsertBatch(body.items, body.deviceId)
  return buildValidatedResponse(context, 200, { synced: count }, ResponseSchema)
})

When validation fails, the handler returns a 400 automatically:

json
{
  "message": "Bad Request",
  "errors": {
    "items": ["Required"],
    "deviceId": ["Invalid uuid"]
  }
}

OpenAPI Metadata

Add openapi to include the handler in the generated OpenAPI spec:

typescript
const api = defineApiHandler({
  auth: 'bearer',
  openapi: {
    summary: 'Sync data items',
    tags: ['data'],
  },
})

Response Helpers

buildValidatedResponse

Validates the response body against a Zod schema before returning. Recommended for all API responses.

typescript
import { buildValidatedResponse } from '@mantleframework/core'
return buildValidatedResponse(context, 200, { items }, ResponseSchema)

buildResponse

Returns an API Gateway response without schema validation. Use for simple responses or 204 No Content.

typescript
import { buildResponse } from '@mantleframework/core'
return buildResponse(context, 204)
return buildResponse(context, 200, { message: 'ok' })

Both helpers add correlation headers to the response automatically.

File-System Routing

API handler filenames follow <name>.<method>.ts under src/lambdas/api/:

FileRouteMethodLambda Name
api/sync.post.ts/syncPOSTSync
api/users/index.get.ts/usersGETUsersGet
api/users/profile.get.ts/users/profileGETUsersProfile

See Also