Spec Authoring

Your Typeweaver spec is the source of truth for your API contract. It defines the resources, operations, request schemas, and response schemas that generated clients, handlers, types, and validators all rely on.

That means the spec should describe the contract clearly:

  • resources
  • operations
  • request schemas
  • response schemas

Runtime behavior, such as fetching from a database or calling another service, belongs in your handlers, not in the spec.

The mental model

  • defineSpec(...) groups your API by resource.
  • Each resource contains operations.
  • Each operation defines one HTTP action.
  • Each operation lists the responses it can return.

If you are unsure where something belongs, ask: “Is this part of the contract, or part of the implementation?” If it is contract data, it belongs in the spec.

A small resource-based spec

import {
  defineOperation,
  defineResponse,
  defineSpec,
  HttpMethod,
  HttpStatusCode,
} from "@rexeus/typeweaver-core";
import { z } from "zod";

const todoSchema = z.object({
  id: z.string(),
  title: z.string(),
  completed: z.boolean(),
});

const GetTodoDefinition = defineOperation({
  operationId: "GetTodo",
  method: HttpMethod.GET,
  summary: "Get todo",
  path: "/todos/:todoId",
  request: {
    param: z.object({
      todoId: z.string(),
    }),
  },
  responses: [
    defineResponse({
      name: "GetTodoSuccess",
      statusCode: HttpStatusCode.OK,
      description: "Todo retrieved successfully",
      body: todoSchema,
    }),
  ],
});

const CreateTodoDefinition = defineOperation({
  operationId: "CreateTodo",
  method: HttpMethod.POST,
  summary: "Create todo",
  path: "/todos",
  request: {
    body: z.object({
      title: z.string().min(1),
    }),
  },
  responses: [
    defineResponse({
      name: "CreateTodoSuccess",
      statusCode: HttpStatusCode.CREATED,
      description: "Todo created successfully",
      body: todoSchema,
    }),
  ],
});

export const spec = defineSpec({
  resources: {
    todo: {
      operations: [GetTodoDefinition, CreateTodoDefinition],
    },
  },
});

Folder structure is up to you

You can keep everything in one file or split operations, schemas, and shared responses into separate files. Typeweaver cares about the spec entrypoint you pass to the CLI, not about a required folder layout.

In other words, this is valid:

  • api/spec/index.ts
  • api/spec/todo/GetTodoDefinition.ts
  • api/spec/todo/CreateTodoDefinition.ts
  • api/spec/shared/sharedResponses.ts

Or a completely different structure, as long as the entrypoint exports your defineSpec(...) result.

Naming basics

  • Prefer singular camelCase resource names such as todo.
  • Use one operationId style consistently across your spec. PascalCase names such as GetTodo and camelCase names such as getTodo are both supported.
  • These docs use PascalCase operationId values so the generated class names line up with what you see in examples.
  • snake_case and kebab-case are not supported for resource names or operation IDs.

This page is about what belongs in the spec at all. For deeper guidance on how to split operations into resources, see Resources and Operations.

Was this page helpful?