Documentation

Everything you need to set up TypeOwl in your project.

Installation

Install TypeOwl in both your backend and frontend projects:

terminal
npm install typeowl
Pure TypeScript — no schemas required! TypeOwl uses the TypeScript Compiler API (TypeChecker) for robust type extraction. Define types with regular interfaces and type aliases.

Quick Start

Get TypeOwl running in 3 steps:

1. Backend: Create config file

typeowl.server.config.ts
import { defineServerConfig } from 'typeowl/server'; export default defineServerConfig({ version: '1.0.0', typeSources: './src/types/', // Type files to extract routes: './src/server.ts', // Route files to scan // onConflict: 'rename', // Auto-rename duplicates: User -> User1 mode: 'dynamic', });

2. Backend: Initialize and mount

server.ts
import { initTypeOwl } from 'typeowl/server'; const typeowl = await initTypeOwl(); await typeowl.mount(app);

3. Frontend: Sync types using CLI

terminal
# One-time sync from backend npx typeowl sync # Watch mode (polls every 5 seconds by default) npx typeowl watch # Watch with custom interval (30 seconds) npx typeowl watch -i 30

Project Structure

backend/
  • typeowl.server.config.ts Server config
  • src/
    • server.ts Main server file
    • types/
      • User.ts Type definitions
      • Blog.ts
frontend/
  • typeowl.config.ts Client config
  • .typeowl/ Generated types (commit for independent deploys!)
    • index.d.ts ApiEndpoints + re-exports
    • content.d.ts All extracted types
  • .typeowl-cache/ Local cache (gitignore)
  • src/
    • api/
      • client.ts Type-safe API client

Server Configuration

The typeowl.server.config.ts file controls how types are extracted and served:

Option Type Description
version string Version string for cache invalidation
basePath string Base URL path (default: /__typeowl)
typeSources string | string[] Source files for type extraction (TypeChecker scans these)
routes string | string[] Route files to scan for route.get().returns<T>() patterns
onConflict 'error' | 'rename' How to handle duplicate type names (default: 'error')
mode 'dynamic' | 'static' How types are served
guard object Security settings (API key, environment)

Type Extraction

TypeOwl can extract types directly from your TypeScript files:

typeowl.server.config.ts
extract: { // Domain name → file configuration content: { from: './src/types/', types: '*', // All exported types }, models: { from: './src/models/', types: ['User', 'Order'], // Specific types only }, }

route.get() — Pure TypeScript

The recommended way to define API types. Works with Fastify, Express, Hono, Next.js, and Koa:

server.ts
import { route } from 'typeowl/server'; // Define types with regular TypeScript interface User { id: string; name: string; email: string; } interface IdParams { id: string; } interface CreateUserInput { name: string; email: string; } // Define routes with type parameters const getUsers = route.get('/api/users').returns<User[]>(); const getUserById = route.get('/api/users/:id').params<IdParams>().returns<User | null>(); const createUser = route.post('/api/users').body<CreateUserInput>().returns<User>(); // Wire up with your framework app.get(getUsers.path, async () => users); app.get(getUserById.path, async (req) => findUser(req.params.id)); app.post(createUser.path, async (req) => createNewUser(req.body));
Why route.get() is recommended:
  • Pure TypeScript — no schemas, just interfaces and types
  • TypeChecker extraction — robust type resolution
  • Multi-framework — works with Fastify, Express, Hono, Next.js, Koa
  • Conflict resolution — handles duplicate types with onConflict config

Conflict Resolution

When multiple files define types with the same name, TypeOwl uses structural comparison:

typeowl.server.config.ts
export default defineServerConfig({ // How to handle duplicate type names with different structures onConflict: 'error', // Default: throw error with suggestion // onConflict: 'rename', // Auto-rename: User -> User1 -> User2 });
Structural Comparison TypeOwl uses the TypeScript Compiler API to compare types by structure, not by name or source location. Two interfaces with identical properties are considered the same type.

Validation with Typia (Optional)

TypeOwl doesn't include validation. Use Typia for runtime validation — it uses the same types from your route definitions:

server.ts
import { route } from 'typeowl/server'; import typia from 'typia'; // Define route with body type const createUser = route.post('/api/users') .body<CreateUserInput>() .returns<User>(); // Use route's body type for validation! app.post(createUser.path, async (req) => { const input = typia.assert<createUser.bodyType>(req.body); return createNewUser(input); });
Why Typia?
  • Access types directly: route.bodyType, route.paramsType, route.responseType
  • No runtime schema overhead — validators generated at compile time
  • 10-1000x faster than Zod/Yup

Static Extraction Only (No Routes)

Don't need API endpoint mapping? You can use TypeOwl purely for type sharing — just extract types from your backend files and sync them to the frontend. No copy/paste needed.

When to use static-only:
  • You just want to stop copy/pasting types between repos
  • You don't need API endpoint mapping
  • You want the simplest possible setup

Setup: Create a dedicated folder (e.g., src/types/) for your shared types:

typeowl.server.config.ts
export default defineServerConfig({ version: '1.0.0', typeSources: './src/types/', // TypeChecker extracts all exported types mode: 'dynamic', });
frontend/api.ts
// Types auto-imported — no copy/paste! import type { User, Product } from 'typeowl/types'; // You handle the API calls yourself const users = await fetch('/api/users').then(r => r.json()) as User[];
No API contract: Static extraction shares types but doesn't guarantee your API actually returns those types. For full type safety with validation, use route.get() or typeowl.endpoint().

Client Configuration

Configure where to fetch types from. You can point to local dev, staging, or production!

typeowl.config.ts
import { defineConfig } from 'typeowl'; export default defineConfig({ resolvers: [ { name: 'api', // Use environment variable or default to local source: process.env.TYPEOWL_API_URL || 'http://localhost:3001/__typeowl', // Or point directly to production: // source: 'https://api.yourcompany.com/__typeowl', }, ], output: './.typeowl', cache: './.typeowl-cache', });
No monorepo required! Frontend developers can point to production and get real types without needing backend code access.

Syncing Types

Use the TypeOwl CLI to sync types from your backend:

terminal
# One-time sync (run before build) npx typeowl sync # Watch mode for development npx typeowl watch # Watch with custom interval (e.g., 30 seconds) npx typeowl watch --interval 30

Add CLI commands to your package.json:

package.json
{ "scripts": { "dev": "npx typeowl sync && vite", "dev:watch": "concurrently \"npx typeowl watch\" \"vite\"", "build": "npx typeowl sync && tsc && vite build" } }
CLI Commands
  • typeowl sync — [CLIENT] One-time sync from remote backend
  • typeowl watch — [CLIENT] Continuously poll for type changes
  • typeowl generate — [SERVER] Pre-generate static types

Deployment Strategies

TypeOwl gives you flexibility in how you manage types. Choose the strategy that fits your workflow:

Strategy 1: Commit Types (Recommended)

.gitignore
# Only cache is gitignored, NOT the types! .typeowl-cache/
What you get:
  • Guaranteed compatibility — Types match the API when code was written
  • CI/CD works offline — No backend access needed during builds
  • Independent deploys — Frontend doesn't break if backend changes
  • Pure frontend devs — Clone and run, types are already there

Strategy 2: Gitignore Types

.gitignore
# Gitignore both - sync fresh every time .typeowl/ .typeowl-cache/
Trade-offs:
  • Requires backend access during CI/CD builds
  • Breaking changes propagate immediately
  • Best for monorepos where FE/BE deploy together

Strategy 3: Point to Production

Frontend developers can sync from production without backend code access:

typeowl.config.ts
resolvers: [ { name: 'api', source: 'https://api.yourcompany.com/__typeowl', }, ]
Perfect for: Pure frontend developers who don't have access to backend code. Just point to production and sync!

Using Types

Import synced types in your frontend. First, configure your tsconfig.json:

tsconfig.json
{ "compilerOptions": { "paths": { "typeowl/types": ["./.typeowl/index.d.ts"] } }, "include": ["src", ".typeowl"] }

Then import your types:

src/App.tsx
import type { User, Blog, ApiEndpoints } from 'typeowl/types'; // Full autocomplete! ✨ const users: User[] = await fetch('/api/users').then(r => r.json());
Why typeowl/types? TypeOwl ships with a placeholder at typeowl/types that provides empty types before you run sync. After configuring your tsconfig paths, the placeholder gets overridden with your actual generated types.

TypeScript Setup

Configure your tsconfig.json to enable type imports:

tsconfig.json
{ "compilerOptions": { // ... your existing options "paths": { "typeowl/types": ["./.typeowl/index.d.ts"] } }, // Include the generated types directory "include": ["src", ".typeowl"] }

Now you can import your types:

src/App.tsx
import type { User, ApiEndpoints } from 'typeowl/types'; // Full autocomplete and type safety! ✨

Building an API Client

Create a fully typed API client using the generated ApiEndpoints:

src/api/client.ts
import type { ApiEndpoints } from 'typeowl/types'; type GetPath = ExtractPaths<'GET'>; export const api = { get: <P extends GetPath>(path: P) => fetch(path).then(r => r.json()) as Promise<ApiEndpoints[`GET ${P}`]['response']>, }; // Usage - fully typed! const users = await api.get('/api/users'); // User[]

Multiple Backends

For microservices, configure multiple resolvers:

typeowl.config.ts
resolvers: [ { name: 'api', source: 'http://localhost:3001/__typeowl' }, { name: 'auth', source: 'http://localhost:3002/__typeowl' }, { name: 'payments', source: 'http://localhost:3003/__typeowl' }, ]

Security

Protect TypeOwl endpoints in production:

typeowl.server.config.ts
guard: { enabled: 'development', // Only in dev, or true/false apiKey: process.env.TYPEOWL_API_KEY, }

Watch Mode

Auto-sync types during development using the CLI:

terminal
# Default: polls every 5 seconds npx typeowl watch # Custom interval (10 seconds) npx typeowl watch -i 10 # Longer interval (1 minute) npx typeowl watch --interval 60
Pro Tip Use concurrently to run watch mode alongside your dev server: "dev:watch": "concurrently \"npx typeowl watch\" \"vite\""