wispwisp
Primitives

Api & Route

HTTP routes on API Gateway, one Lambda function per route, with Zod validation built in.

Declaring an API

import { Api } from "@vorynza/wisp";

export const api = new Api("main", {
  cors: { origins: ["*"] },
});
OptionTypeDescription
cors{ origins: string[] }Optional. Enables CORS on the API with the given allowed origins.
customDomainstringOptional. Reserved for a future milestone — not implemented yet.

Declaring a route

Call get, post, put, patch, or delete on an Api instance. Each call must be assigned directly to an exported const — that's how the compiler finds it:

import { z } from "zod";
import { api } from "./app.ts";
import { orders } from "./stores/orders.ts";

export const createOrder = api.post(
  "/orders",
  {
    body: z.object({ sku: z.string(), qty: z.number().int().positive() }),
  },
  async ({ body }) => {
    const order = await orders.put({ id: crypto.randomUUID(), ...body, status: "pending" });
    return { status: 201, body: order };
  },
);

Route options

OptionTypeDescription
bodyz.ZodTypeOptional Zod schema. Requests that fail validation are rejected before your handler runs.
queryz.ZodTypeOptional schema for the query string.
paramsz.ZodTypeOptional schema for path parameters (/orders/:id).
memorynumberLambda memory in MB. Defaults to 1024.
timeoutnumberLambda timeout in seconds. Defaults to 10.

The handler

Your handler receives one argument with the request already parsed and validated:

interface HandlerContext<TBody, TQuery, TParams> {
  body: TBody;
  query: TQuery;
  params: TParams;
  headers: Record<string, string>;
}

...and returns a response — synchronously or as a Promise:

interface RouteResponse {
  status: number;
  body?: unknown;
  headers?: Record<string, string>;
}

Path parameters

export const getOrder = api.get("/orders/:id", { params: z.object({ id: z.string() }) }, async ({ params }) => {
  const order = await orders.get({ id: params.id });
  if (!order) return { status: 404, body: { error: "not_found" } };
  return { status: 200, body: order };
});

What this deploys to

Each route compiles to its own AWS::Lambda::Function, wired to a shared AWS::ApiGatewayV2::Api via a dedicated AWS::ApiGatewayV2::Route and Integration. There is no runtime router — API Gateway invokes the exact function for the matched route directly. Each route's Lambda gets its own AWS::IAM::Role, scoped to only the resources that specific handler references (see IAM derivation).

On this page