Telaio
Server

Schemas & OpenAPI

TypeBox schema registration, built-in schemas, schema helpers, Swagger spec generation, and the Scalar API explorer.

Schemas & OpenAPI

Telaio uses TypeBox for runtime and compile-time schema validation. Schemas are registered with Fastify so they can be referenced by $id throughout the app and appear as named components in the generated OpenAPI spec.

Auto-registration from src/schemas/

Place your TypeBox schemas in src/schemas/. Telaio scans the directory at startup and registers every export whose name ends in Schema. The $id is used as the component name in the OpenAPI spec.

// src/schemas/user.ts
import { Type } from '@sinclair/typebox';

export const UserSchema = Type.Object(
  {
    id: Type.String({ format: 'uuid' }),
    email: Type.String({ format: 'email' }),
    name: Type.String(),
    createdAt: Timestamp,
  },
  { $id: 'User' },
);

export const CreateUserSchema = Type.Object(
  {
    email: Type.String({ format: 'email' }),
    name: Type.String(),
  },
  { $id: 'CreateUser' },
);

Files named index.ts and utils.ts are skipped during schema auto-discovery. If a schema export does not have a $id, Telaio auto-generates one from the export name (e.g. UserSchema becomes User).

You can also pass a custom directory via .withSchemas(dir):

.withSchemas('src/api/schemas')

Built-in schemas

These schemas are registered automatically with every Telaio app. They are available by $ref in any route schema and appear in your OpenAPI spec.

Schema$idDescription
SortPaginationParamsSchemaSortPaginationParamsQuery params: sort, limit (1-100, default 10), skip (default 0)
PaginationMetaSchemaPaginationMetaResponse meta: total, skip, limit
GenericErrorResponseSchemaGenericErrorResponseGeneric 500 error body
BadRequestErrorResponseSchemaBadRequestErrorResponse400 error body
UnauthorizedResponseSchemaUnauthorizedResponse401 error body
ForbiddenResponseSchemaForbiddenResponse403 error body
NotFoundResponseSchemaNotFoundResponse404 error body
import {
  SortPaginationParamsSchema,
  NotFoundResponseSchema,
} from 'telaio/schema';

fastify.get('/users/:id', {
  schema: {
    response: {
      200: AutoRef(UserSchema),
      404: AutoRef(NotFoundResponseSchema),
    },
  },
}, handler);

Schema helpers

Import helpers from telaio/schema.

AutoRef

Emits a JSON Schema $ref pointer using the schema's $id. Use this in schema.response, schema.querystring, and similar fields when you want the OpenAPI spec to reference a named component rather than inlining the full schema.

import { AutoRef } from 'telaio/schema';

schema: {
  querystring: AutoRef(SortPaginationParamsSchema),
  response: {
    200: AutoRef(UserSchema),
  },
}

Nullable

Produces Schema | null -- useful for optional fields that can be explicitly set to null.

import { Nullable } from 'telaio/schema';

const UserSchema = Type.Object({
  id: Type.String(),
  deletedAt: Nullable(Timestamp),  // Date | null
});

PlainEnum

Creates a TypeBox TEnum from a TypeScript enum-like object. Avoids the verbose TypeBox enum syntax.

import { PlainEnum } from 'telaio/schema';

enum UserRole {
  Admin = 'admin',
  Member = 'member',
  Guest = 'guest',
}

const RoleSchema = PlainEnum(UserRole);
// Equivalent to Type.Enum(UserRole)

TypeName

Adds a string literal field with a default value, useful for discriminated unions and typed payloads.

import { TypeName } from 'telaio/schema';

const CreateUserEventSchema = Type.Object({
  type: TypeName('user.created'),
  userId: Type.String(),
});

Paginated

Wraps a data schema in a standard paginated response envelope.

import { Paginated } from 'telaio/schema';

schema: {
  response: {
    200: Paginated(UserSchema),
    // Produces: { data: User[], meta: { total, skip, limit } }
  },
}

Paginated accepts an optional second argument for additional options on the wrapper object.

Timestamp

A Type.Unsafe<Date> schema with format: 'date-time'. Use it wherever you have a date/time column.

import { Timestamp } from 'telaio/schema';

const EventSchema = Type.Object({
  id: Type.String(),
  occurredAt: Timestamp,
});

Swagger / OpenAPI spec

Call .withSwagger(options) to register OpenAPI 3.1 spec generation. Swagger must be configured before routes are loaded -- Telaio enforces this automatically.

const app = await createApp({ config })
  .withSwagger({
    info: {
      title: 'My API',
      version: '1.0.0',
      description: 'REST API for the My App platform',
    },
  })
  .build();

SwaggerOptions

OptionTypeDescription
info.titlestringRequired. API title
info.versionstringAPI version string
info.descriptionstringAPI description (supports Markdown)

Additional standard OpenAPI fields can be passed alongside info. They are forwarded to the underlying @fastify/swagger plugin.

Schema $id values become $ref component names in the generated spec. A schema registered with $id: 'User' appears as #/components/schemas/User in every route that references it via AutoRef.

Scalar API explorer

Call .withApiDocs(options?) to mount the Scalar interactive API reference.

const app = await createApp({ config })
  .withSwagger({ info: { title: 'My API' } })
  .withApiDocs()
  .build();
URLContents
/docsScalar interactive API explorer UI
/docs/jsonRaw OpenAPI JSON spec

ScalarOptions are passed through to @scalar/fastify-api-reference. Any valid Scalar configuration option can be provided.

.withApiDocs() requires .withSwagger() to be called first. If you call .withApiDocs() without .withSwagger(), the Scalar UI will have no spec to display.

On this page