Skip to content

Key Concepts

Before diving in, it helps to have a mental model of the pieces involved.

Decorators

TRAPI reads the decorators already present in your source (@Controller, @Get, @Body, …). It never executes them — they stay as compile-time markers that the TypeScript compiler API can introspect.

Because decorator names differ from framework to framework, TRAPI normalises them via handlers declared in a preset (see below).

Handlers

A handler is a function that matches against a decorator name (or JSDoc tag) and contributes to a draft. The orchestrator walks each TS node, dispatches its decorators to all matching handlers in the registry, and finalises the draft into a Controller / Method / Parameter.

typescript
import { ParamKind, parameter } from '@trapi/core';

const bodyHandler = parameter({
    match: { name: 'Body', on: 'parameter' },
    apply: (_ctx, draft) => { draft.in = ParamKind.Body; },
});

See Decorators & Presets for the full handler shape.

Presets

A preset is a published package whose default export is a Preset — a name plus arrays of handlers per kind. Instead of describing handlers inline, you reference the package by name and TRAPI loads it:

typescript
await generateMetadata({
    entryPoint: ['src/controllers/**/*.ts'],
    preset: '@trapi/preset-decorators-express',
});

The monorepo ships two:

PresetFor
@trapi/preset-decorators-express@decorators/express — self-contained (routing + TRAPI markers + JSDoc handlers)
@trapi/preset-typescript-resttypescript-rest — self-contained (routing + TRAPI markers + JSDoc handlers)

You can also publish your own. See Custom Presets.

Metadata

The output of generateMetadata() is a plain Metadata object:

typescript
type Metadata = {
    controllers: Controller[];
    referenceTypes: Record<string, ReferenceType>;
};

It is a faithful representation of your decorated source — no OpenAPI-specific simplifications are baked in. Consumers that need the full TypeScript type information (tuples with named elements, branded types, …) get it.

Type Resolver

The resolver walks TypeScript's type system to turn type annotations into a normalised representation:

  • primitives (string, number, …)
  • classes and interfaces, with inherited properties
  • generics with type parameter substitution
  • unions, intersections, tuples
  • utility types (Partial, Pick, Omit, Record, NonNullable, Required, Readonly)
  • string and numeric enums

For the full support matrix, see Supported TypeScript Types.

OpenAPI Emission

@trapi/swagger consumes the metadata and emits an OpenAPI document. Version-specific differences (nullable vs x-nullable, requestBody vs in: body, allOf vs flattened intersections) are handled by dedicated emitters — the metadata layer never makes OpenAPI-driven compromises.

The Short Version

  1. You write decorators. TRAPI does not care which ones.
  2. A preset (built-in or your own) defines handlers that match those decorator names and mutate drafts.
  3. generateMetadata() produces a normalised, framework-agnostic representation.
  4. generateSwagger() turns that into an OpenAPI spec.
  5. Anything else that wants to consume the metadata — validators, SDK generators, CLI tools — can do so directly.