Telaio
Introduction

Why Telaio

The philosophy behind the fixed stack — what you gain, what you give up, and who it is for.

Why Telaio

Telaio is a TypeScript-first Fastify 5 framework built around a single, opinionated premise: a fixed technology stack in exchange for zero wiring overhead and deep compile-time type integration.

Every service you build with Telaio shares the same foundation. PostgreSQL via Kysely. Redis. pg-boss. Pino. Zod for config, TypeBox for routes. AWS SDK v3. The choices have already been made. If that sounds like a limitation, you are not the target audience — and that is fine.

The Deal

When you reach for Telaio, you are accepting a fixed stack in exchange for three concrete things:

No wiring overhead. A one-liner createApp().withDatabase().withCache().build() gives you a fully wired Fastify app with a pg pool, Kysely instance, Redis cache, and all the scaffolding connected. You did not configure any of it. You just used it.

Zero per-service library decisions. Every service in your fleet uses the same database client, the same queue system, the same auth approach. Onboarding a new team member means learning the stack once.

Misconfigurations caught at compile time. This is the part that makes Telaio unusual. If you call app.pool on an app that was not built with .withDatabase(), TypeScript rejects it. Not at runtime. Not with a null pointer. At the type level, the property does not exist.

AI-Ready by Design

These same properties -- fixed stack, phantom types, builder pattern -- have a second-order effect that matters increasingly: they make your codebase dramatically easier for LLM coding agents to work with.

Reduced surface area. A Telaio app's entire infrastructure story is one builder chain in app.ts. Compare that to NestJS, where the same information is scattered across 15-25 module and config files, or Sails.js, which ships 40+ config files in a fresh project. Fewer files for an LLM to read means more context budget for your actual business logic.

Inflexibility as a guardrail. When the stack is fixed, an LLM agent cannot decide to swap Kysely for Prisma or Redis for Memcached. Its decision space is constrained to the code that matters -- your domain logic, your routes, your queries. Not infrastructure plumbing.

Compile-time feedback loops. Phantom types and Zod config validation create a tight feedback loop for AI agents. If an agent tries to use app.db without .withDatabase(), the compiler rejects it immediately. The agent self-corrects without a deploy-and-debug cycle. This is fundamentally different from frameworks where misconfiguration only surfaces at runtime.

For a deeper look at how Telaio's design choices interact with AI-assisted development, see AI-Ready by Design.

Phantom Types — The Structural Idea

Telaio's AppBuilder carries a phantom type parameter F extends Features. Every .with*() method transforms it:

createApp()
  // AppBuilder<DefaultFeatures, unknown, ...>
  .withDatabase()
  // AppBuilder<{ database: true; cache: false; ... }, unknown, ...>
  .withCache()
  // AppBuilder<{ database: true; cache: true; ... }, unknown, ...>
  .build()
  // Promise<TelaioApp<{ database: true; cache: true; ... }>>

TelaioApp<F> uses conditional types to expose only the properties that were enabled:

type TelaioApp<F> = {
  fastify: FastifyInstance;
  start: () => Promise<void>;
  stop: () => Promise<void>;
} & (F['database'] extends true
  ? { pool: pg.Pool; db: Kysely<DB> }
  : unknown) &
  (F['cache'] extends true ? { cache: Cache } : unknown);

A & unknown equals A in TypeScript — so disabled features simply do not contribute any properties to the type. You cannot accidentally use them.

Who This Is For

Teams that want to ship without debating library choices per service. Organizations where consistency matters more than flexibility. Developers who want TypeScript to tell them when their app is misconfigured, before the app runs.

Who This Is Not For

If you need to swap PostgreSQL for MySQL or MongoDB, Telaio is the wrong tool. The database layer is built around pg and Kysely specifically.

If you are already using Prisma and love it, Telaio does not support it — there is no ORM abstraction layer. Kysely is the query builder, and that choice is structural.

If you need a pluggable, swappable stack where every component can be replaced, Telaio is too opinionated. Raw Fastify with manual wiring gives you that flexibility.

For Fastify Developers

If you already know Fastify, Telaio adds a thin layer on top — not around it. Route syntax is unchanged. You still write fastify.get('/path', { schema }, handler). The app's underlying FastifyInstance is always accessible via app.fastify. Telaio handles the setup that otherwise lives in boilerplate files.

ESM-Only

Telaio is ESM-only. This is a feature, not a limitation. Top-level await in the module scope. Clean dynamic imports for optional peer dependencies. Consistent behavior with modern Node.js module resolution.

Node 20 is the minimum because that is where the platform became stable enough. Older versions are not supported and will not be.

On this page