# TypeSpec TypeSpec is a language for defining cloud service APIs and shapes, developed by Microsoft. It provides a highly extensible way to describe API shapes common among REST, OpenAPI, gRPC, and other protocols. TypeSpec enables API-first development by allowing you to define your APIs in a concise, human-readable format and generate multiple artifacts from a single source of truth, including OpenAPI specifications, client libraries (JavaScript, Python, Java, C#), server code scaffolding, and documentation. The core philosophy of TypeSpec is to establish reusable patterns and "guardrails" for API designers, making it easier to follow best practices. The compiler (version 1.11.0) includes a rich linter framework for flagging anti-patterns and an emitter framework for controlling output. TypeSpec is production-ready with stable components including the compiler, HTTP/OpenAPI libraries, VS Code extension, and emitters for OpenAPI 3.0 and JSON Schema. ## Installation and Project Setup Install TypeSpec CLI globally and create a new project. ```bash # Install TypeSpec CLI npm install -g @typespec/compiler # Verify installation tsp --version # Create a new project (select "Generic REST API" template) tsp init # Install dependencies tsp install # Compile the project tsp compile . # Watch mode for development tsp compile . --watch ``` ## Defining Models Models define the structure of data with properties, optional fields, defaults, and validation constraints. ```typespec import "@typespec/http"; using Http; model Pet { id: int32; @minLength(1) @maxLength(100) name: string; @minValue(0) @maxValue(100) age: int32; kind: petType; // Optional property with default status?: "available" | "pending" | "sold" = "available"; } enum petType { dog: "dog", cat: "cat", fish: "fish", bird: "bird", reptile: "reptile", } // Model with inheritance model Dog extends Pet { breed: string; } // Model composition using spread model PetWithOwner { ...Pet; ownerName: string; } // Generic model template model Page { size: int32; items: Item[]; nextLink?: string; } // Using the template model PetPage is Page; ``` ## Defining REST Services and Operations Define REST services with namespaces, routes, and CRUD operations with proper HTTP methods. ```typespec import "@typespec/http"; using Http; @service(#{ title: "Pet Store" }) @server("https://api.petstore.example.com", "Production server") @server("https://api-dev.petstore.example.com", "Development server") namespace PetStore; model Pet { id: int32; @minLength(1) name: string; @minValue(0) @maxValue(100) age: int32; kind: "dog" | "cat" | "fish"; } @route("/pets") namespace Pets { // GET /pets - List all pets @get op listPets(@query limit?: int32, @query offset?: int32): { @statusCode statusCode: 200; @body pets: Pet[]; }; // GET /pets/{petId} - Get a specific pet @get op getPet(@path petId: int32): { @statusCode statusCode: 200; @body pet: Pet; } | { @statusCode statusCode: 404; @body error: NotFoundError; }; // POST /pets - Create a new pet @post op createPet(@body pet: Pet): { @statusCode statusCode: 201; @body newPet: Pet; } | { @statusCode statusCode: 400; @body error: ValidationError; }; // PUT /pets/{petId} - Update a pet @put op updatePet(@path petId: int32, @body pet: Pet): { @statusCode statusCode: 200; @body updatedPet: Pet; } | { @statusCode statusCode: 404; @body error: NotFoundError; }; // DELETE /pets/{petId} - Delete a pet @delete op deletePet(@path petId: int32): { @statusCode statusCode: 204; }; } @error model NotFoundError { code: "NOT_FOUND"; message: string; } @error model ValidationError { code: "VALIDATION_ERROR"; message: string; details: string[]; } ``` ## Using Decorators Decorators attach metadata to types for documentation, validation, HTTP mapping, and custom behaviors. ```typespec import "@typespec/http"; using Http; @service(#{ title: "Widget Service" }) @server("https://api.example.com", "Main endpoint") namespace WidgetService; // Documentation decorators @doc("Represents a widget in the system") @summary("Widget resource") model Widget { @doc("Unique identifier for the widget") id: string; @doc("Display name of the widget") @minLength(1) @maxLength(256) name: string; @doc("Widget creation timestamp") createdAt: utcDateTime; @doc("Secret API key - not exposed in responses") @secret apiKey?: string; } // HTTP decorators for parameters @route("/widgets") interface Widgets { @get @doc("Retrieves a widget by ID") @summary("Get Widget") getWidget( @path @doc("The widget identifier") widgetId: string, @header @doc("Request correlation ID") requestId: string, @query @doc("Include deleted widgets") includeDeleted?: boolean, ): Widget | NotFoundError; @post @doc("Creates a new widget") createWidget( @body widget: Widget, @header contentType: "application/json", ): { @statusCode statusCode: 201; @header location: string; @body newWidget: Widget; }; } // Augment decorators (apply from different location) @@doc(Widget.id, "System-generated unique identifier (UUID format)"); ``` ## Interfaces and Operation Templates Group and reuse operations with interfaces and templates for consistent API patterns. ```typespec import "@typespec/http"; using Http; @service(#{ title: "Resource API" }) namespace ResourceAPI; // Base error model @error model ApiError { code: string; message: string; } // Generic CRUD interface template interface ResourceOperations { @get list(): TResource[]; @get read(@path id: string): TResource | ApiError; @post create(@body resource: TCreate): TResource | ApiError; @put update(@path id: string, @body resource: TUpdate): TResource | ApiError; @delete delete(@path id: string): void | ApiError; } // Models for User resource model User { id: string; name: string; email: string; createdAt: utcDateTime; } model CreateUser { name: string; email: string; } model UpdateUser { name?: string; email?: string; } // Apply the template to create User operations @route("/users") interface Users extends ResourceOperations { // Additional user-specific operations @route("{id}/activate") @post activate(@path id: string): User | ApiError; } // Operation templates for reuse op ReadResource(id: string): T | ApiError; op ListResources(@query limit?: int32): T[]; // Use operation templates @route("/products") namespace Products { model Product { id: string; name: string; price: float64; } @get op getProduct is ReadResource; @get op listProducts is ListResources; } ``` ## Authentication and Security Define authentication schemes using the `@useAuth` decorator with various auth models. ```typespec import "@typespec/http"; using Http; @service(#{ title: "Secure API" }) @server("https://api.secure.example.com", "Secure endpoint") namespace SecureAPI; // OAuth2 authentication model model OAuth2Flow is OAuth2Auth<[ { type: OAuth2FlowType.authorizationCode; authorizationUrl: "https://auth.example.com/oauth2/authorize"; tokenUrl: "https://auth.example.com/oauth2/token"; scopes: ["read", "write", "admin"]; } ]>; // API Key authentication model ApiKeyAuth is ApiKeyAuth; // Models model SecretData { id: string; @secret value: string; } @error model UnauthorizedError { code: "UNAUTHORIZED"; message: string; } @route("/data") namespace Data { // Public endpoint - no auth required @get op getPublicData(): { message: string }; // Protected endpoint - requires Bearer token @get @useAuth(BearerAuth) op getProtectedData(): SecretData | UnauthorizedError; // Admin endpoint - requires OAuth2 with admin scope @post @useAuth(OAuth2Flow) op createSecretData(@body data: SecretData): { @statusCode statusCode: 201; @body created: SecretData; } | UnauthorizedError; // Endpoint with API Key auth @delete @useAuth(ApiKeyAuth) op deleteData(@path id: string): { @statusCode statusCode: 204; } | UnauthorizedError; } ``` ## API Versioning Use the versioning library to manage multiple API versions with version-specific models and operations. ```typespec import "@typespec/http"; import "@typespec/versioning"; using Http; using Versioning; @service(#{ title: "Versioned API" }) @server("https://api.example.com", "API endpoint") @versioned(Versions) namespace VersionedAPI; enum Versions { v1: "1.0", v2: "2.0", v3: "3.0", } // Base model available in all versions model Pet { id: int32; name: string; // Property added in v2 @added(Versions.v2) tags?: string[]; // Property added in v3 @added(Versions.v3) metadata?: Record; } // Model added in v2 @added(Versions.v2) model PetHealth { petId: int32; status: "healthy" | "sick" | "unknown"; lastCheckup?: utcDateTime; } @route("/pets") namespace Pets { // Available in all versions @get op listPets(): Pet[]; @get op getPet(@path petId: int32): Pet; @post op createPet(@body pet: Pet): Pet; // Operation added in v2 @added(Versions.v2) @route("{petId}/health") @get op getPetHealth(@path petId: int32): PetHealth; // Operation added in v3 @added(Versions.v3) @route("{petId}/history") @get op getPetHistory(@path petId: int32): { @body history: PetHealth[]; }; // Operation removed in v3 @removed(Versions.v3) @route("{petId}/legacy") @get op getLegacyData(@path petId: int32): { data: string }; } ``` ## OpenAPI 3 Emitter Configuration Configure the OpenAPI 3 emitter to generate OpenAPI specifications from TypeSpec definitions. ```yaml # tspconfig.yaml emit: - "@typespec/openapi3" options: "@typespec/openapi3": emitter-output-dir: "{project-root}/openapi" # Output file naming with service and version interpolation output-file: "{service-name}.{version}.yaml" # Generate separate files for each service file-type: "yaml" # Include x-typespec-name extension include-x-typespec-name: "inline-only" ``` ```bash # Generate OpenAPI specification tsp compile main.tsp --emit @typespec/openapi3 # Output location: ./tsp-output/@typespec/openapi3/openapi.yaml # With custom output directory tsp compile main.tsp --emit @typespec/openapi3 --option "@typespec/openapi3.emitter-output-dir=./specs" ``` ## Client SDK Generation Generate client SDKs for multiple programming languages from TypeSpec definitions. ```yaml # tspconfig.yaml - Configure multiple client emitters emit: - "@typespec/openapi3" - "@typespec/http-client-js" - "@typespec/http-client-python" - "@typespec/http-client-java" - "@typespec/http-client-csharp" options: "@typespec/openapi3": emitter-output-dir: "{project-root}/specs" "@typespec/http-client-js": emitter-output-dir: "{project-root}/clients/javascript" packageDetails: name: "@myorg/petstore-client" version: "1.0.0" "@typespec/http-client-python": emitter-output-dir: "{project-root}/clients/python" "@typespec/http-client-java": emitter-output-dir: "{project-root}/clients/java" "@typespec/http-client-csharp": emitter-output-dir: "{project-root}/clients/dotnet" ``` ```json // package.json dependencies for client generation { "dependencies": { "@typespec/compiler": "latest", "@typespec/http": "latest", "@typespec/openapi3": "latest", "@typespec/http-client-js": "latest", "@typespec/http-client-python": "latest", "@typespec/http-client-java": "latest", "@typespec/http-client-csharp": "latest" } } ``` ```bash # Install all dependencies tsp install # Generate all clients tsp compile main.tsp # Generate specific emitter only tsp compile main.tsp --emit @typespec/http-client-python ``` ## Creating Custom Decorators Implement custom decorators in JavaScript/TypeScript to extend TypeSpec functionality. ```typespec // decorators.tsp - Declare decorator signatures import "./decorators.js"; using TypeSpec.Reflection; // Decorator that logs type information extern dec logType(target: unknown, name: string); // Decorator to mark audit fields extern dec auditable(target: Model); // Decorator with optional parameters extern dec customName(target: Model | ModelProperty, name?: valueof string); // Usage @logType("Pet model") @auditable model Pet { id: string; @customName("pet_name") name: string; } ``` ```typescript // decorators.ts - Implement decorator logic import type { DecoratorContext, Model, Type } from "@typespec/compiler"; // Simple logging decorator export function $logType(context: DecoratorContext, target: Type, name: string) { console.log(`${name}: ${target.kind}`); } // Decorator that stores metadata using state const auditableKey = Symbol("auditable"); export function $auditable(context: DecoratorContext, target: Model) { // Store that this model is auditable context.program.stateSet(auditableKey).add(target); } // Helper to check if a model is auditable export function isAuditable(program: Program, target: Model): boolean { return program.stateSet(auditableKey).has(target); } // Decorator with validation export function $customName( context: DecoratorContext, target: Type, name?: string ) { if (name && name.length > 50) { context.program.reportDiagnostic({ code: "custom-name-too-long", message: "Custom name must be 50 characters or less", severity: "error", target: context.getArgumentTarget(0)!, }); return; } // Store the custom name const customNameKey = Symbol("customName"); context.program.stateMap(customNameKey).set(target, name ?? target.name); } ``` ## Linter Configuration Configure linting rules to enforce API design best practices. ```yaml # tspconfig.yaml - Linter configuration linter: # Extend recommended rulesets extends: - "@typespec/best-practices/recommended" # Enable specific rules enable: "@typespec/http/no-duplicate-routes": true "@typespec/openapi/no-inline-schemas": true # Disable rules with justification disable: "@typespec/best-practices/no-enum-value": "Using string enums for OpenAPI compatibility" # Treat warnings as errors in CI warn-as-error: true ``` ```bash # Run compiler with linting tsp compile main.tsp # Run with specific trace for debugging tsp compile main.tsp --trace import-resolution # Ignore deprecated warnings tsp compile main.tsp --ignore-deprecated ``` ## Common Parameters and Headers Define reusable parameter models for consistent API design across operations. ```typespec import "@typespec/http"; using Http; @service(#{ title: "API with Common Parameters" }) namespace CommonParamsAPI; // Common request parameters model CommonParameters { @header @doc("Unique request identifier for tracing") requestId: string; @header @doc("Client application version") clientVersion?: string; @query @doc("Response locale") locale?: string; } // Pagination parameters model PaginationParams { @query @doc("Maximum number of results") @minValue(1) @maxValue(100) limit?: int32 = 20; @query @doc("Offset for pagination") @minValue(0) offset?: int32 = 0; } // Paginated response wrapper model PaginatedResponse { items: T[]; total: int32; limit: int32; offset: int32; hasMore: boolean; } model Widget { id: string; name: string; } @route("/widgets") namespace Widgets { // Using spread to include common parameters @get op listWidgets( ...CommonParameters, ...PaginationParams, @query search?: string, ): PaginatedResponse; @get op getWidget( @path widgetId: string, ...CommonParameters, ): Widget; @post op createWidget( @body widget: Widget, ...CommonParameters, ): { @statusCode statusCode: 201; @header location: string; @body created: Widget; }; } ``` ## TypeSpec provides a powerful, type-safe approach to API-first development that scales from simple REST APIs to complex enterprise systems. The primary use cases include: defining OpenAPI specifications with better maintainability than raw YAML/JSON, generating consistent client SDKs across multiple programming languages, enforcing organizational API standards through custom decorators and linter rules, and managing API versioning with compile-time validation. Integration patterns typically involve using TypeSpec as the source of truth in a CI/CD pipeline, where changes to `.tsp` files trigger regeneration of OpenAPI specs and client libraries. The emitter framework allows customization of output for specific organizational needs, while the decorator system enables encoding domain-specific constraints that get validated at compile time. For teams adopting TypeSpec, the recommended approach is to start with the OpenAPI 3 emitter to validate the generated specs match expectations, then gradually add client emitters and custom decorators as the API design matures.