Prerequisites
- Basic understanding of JavaScript 📝
- TypeScript installation ⚡
- VS Code or preferred IDE 💻
What you'll learn
- Understand the concept fundamentals 🎯
- Apply the concept in real projects 🏗️
- Debug common issues 🐛
- Write type-safe code ✨
🎯 Introduction
Welcome to this exciting tutorial on TSDoc! 🎉 In this guide, we’ll explore how to document your TypeScript code like a pro using TSDoc, the standard documentation format for TypeScript projects.
You’ll discover how TSDoc can transform your codebase into a well-documented, professional library that other developers will love to use. Whether you’re building internal tools 🛠️, open-source libraries 📚, or enterprise applications 🏢, understanding TSDoc is essential for creating maintainable, self-documenting code.
By the end of this tutorial, you’ll feel confident writing comprehensive documentation that makes your TypeScript code shine! Let’s dive in! 🏊♂️
📚 Understanding TSDoc
🤔 What is TSDoc?
TSDoc is like a smart notebook 📓 for your TypeScript code. Think of it as leaving helpful sticky notes 📌 on your code that not only humans can read, but also tools like VS Code can understand and use to provide better IntelliSense!
In TypeScript terms, TSDoc is a standardized way to write documentation comments that get parsed by tools and IDEs. This means you can:
- ✨ Generate beautiful API documentation automatically
- 🚀 Get better IntelliSense and autocomplete in VS Code
- 🛡️ Help other developers (including future you!) understand your code
💡 Why Use TSDoc?
Here’s why developers love TSDoc:
- IntelliSense Support 🔒: Your documentation appears in VS Code tooltips
- API Documentation 💻: Generate beautiful docs websites automatically
- Type Safety 📖: Documentation stays in sync with your code
- Team Collaboration 🔧: Everyone understands what code does
Real-world example: Imagine building a payment processing library 💳. With TSDoc, developers using your library can see exactly what each method does, what parameters it expects, and what errors it might throw - all without leaving their editor!
🔧 Basic Syntax and Usage
📝 Simple Example
Let’s start with a friendly example:
// 👋 Basic TSDoc comment structure
/**
* Calculates the total price including tax
* @param price - The base price of the item
* @param taxRate - The tax rate as a decimal (e.g., 0.08 for 8%)
* @returns The total price including tax
*/
const calculateTotal = (price: number, taxRate: number): number => {
return price * (1 + taxRate);
};
// 🎨 Documenting a class
/**
* Represents a shopping cart for an e-commerce application
* @remarks
* This class handles all cart operations including adding items,
* calculating totals, and applying discounts
*/
class ShoppingCart {
private items: CartItem[] = [];
/**
* Adds an item to the shopping cart
* @param item - The item to add to the cart
* @example
* ```typescript
* cart.addItem({ id: "123", name: "TypeScript Book", price: 29.99 });
* ```
*/
addItem(item: CartItem): void {
this.items.push(item);
}
}
💡 Explanation: Notice how TSDoc comments start with /**
and use tags like @param
, @returns
, and @example
to structure the documentation!
🎯 Common TSDoc Tags
Here are the most useful TSDoc tags:
// 🏗️ Common TSDoc tags in action
interface User {
id: string;
name: string;
email: string;
}
/**
* Creates a new user account
*
* @param userData - The user information
* @returns A promise that resolves to the created user
*
* @throws {@link ValidationError}
* Thrown if the email is invalid
*
* @example
* ```typescript
* const user = await createUser({
* name: "Sarah",
* email: "[email protected]"
* });
* ```
*
* @see {@link updateUser} for updating existing users
* @since v1.0.0
* @deprecated Use {@link createUserV2} instead
*/
async function createUser(userData: Omit<User, "id">): Promise<User> {
// 🚀 Implementation here
return { id: "123", ...userData };
}
💡 Practical Examples
🛒 Example 1: E-Commerce Product API
Let’s document a real product management system:
// 🛍️ Product management with comprehensive TSDoc
/**
* Represents a product in the e-commerce system
* @public
*/
interface Product {
/** Unique product identifier */
id: string;
/** Product display name */
name: string;
/** Price in USD */
price: number;
/** Product emoji for fun display! */
emoji: string;
/** Stock quantity available */
stock: number;
}
/**
* Manages product inventory and operations
* @remarks
* This service handles all product-related operations including
* inventory management, pricing updates, and stock tracking
*
* @example
* ```typescript
* const productService = new ProductService();
* const laptop = await productService.createProduct({
* name: "Gaming Laptop",
* price: 999.99,
* emoji: "💻",
* stock: 50
* });
* ```
*/
class ProductService {
private products: Map<string, Product> = new Map();
/**
* Creates a new product in the inventory
*
* @param productData - The product information without ID
* @returns The created product with generated ID
*
* @throws {@link Error}
* Thrown if a product with the same name already exists
*
* @example
* ```typescript
* const coffee = await productService.createProduct({
* name: "Premium Coffee",
* price: 12.99,
* emoji: "☕",
* stock: 100
* });
* console.log(`Created: ${coffee.emoji} ${coffee.name}`);
* ```
*/
createProduct(productData: Omit<Product, "id">): Product {
const id = Date.now().toString();
const product: Product = { ...productData, id };
this.products.set(id, product);
console.log(`✅ Created product: ${product.emoji} ${product.name}`);
return product;
}
/**
* Updates product stock after a purchase
*
* @param productId - The product ID
* @param quantity - The quantity to deduct from stock
* @returns The updated product
*
* @throws {@link Error}
* Thrown if product not found or insufficient stock
*
* @remarks
* This method is transactional and will rollback on error
*/
purchaseProduct(productId: string, quantity: number): Product {
const product = this.products.get(productId);
if (!product) {
throw new Error(`❌ Product ${productId} not found`);
}
if (product.stock < quantity) {
throw new Error(`❌ Insufficient stock for ${product.emoji} ${product.name}`);
}
product.stock -= quantity;
console.log(`📦 Purchased ${quantity}x ${product.emoji} ${product.name}`);
return product;
}
}
🎯 Try it yourself: Add a method to search products by name with proper TSDoc!
🎮 Example 2: Game State Manager
Let’s document a game state management system:
// 🏆 Game state documentation example
/**
* Represents the current state of a player in the game
* @public
*/
interface PlayerState {
/** Player's unique identifier */
id: string;
/** Player's display name */
name: string;
/** Current score */
score: number;
/** Current level (1-100) */
level: number;
/** Achievements earned */
achievements: Achievement[];
}
/**
* Represents an achievement in the game
* @public
*/
interface Achievement {
/** Achievement ID */
id: string;
/** Display name */
name: string;
/** Achievement emoji */
emoji: string;
/** When the achievement was earned */
earnedAt: Date;
}
/**
* Manages game state and player progression
*
* @remarks
* This class handles all game state operations including
* score tracking, level progression, and achievement unlocking
*
* @example
* ```typescript
* const game = new GameStateManager();
* const player = game.createPlayer("Alice");
* game.addScore(player.id, 100);
* ```
*
* @public
*/
class GameStateManager {
private players: Map<string, PlayerState> = new Map();
/**
* Creates a new player
*
* @param name - The player's display name
* @returns The created player state
*
* @example
* ```typescript
* const player = game.createPlayer("Bob");
* console.log(`Welcome ${player.name}! 🎮`);
* ```
*/
createPlayer(name: string): PlayerState {
const player: PlayerState = {
id: Date.now().toString(),
name,
score: 0,
level: 1,
achievements: [{
id: "first-steps",
name: "First Steps",
emoji: "🌟",
earnedAt: new Date()
}]
};
this.players.set(player.id, player);
console.log(`🎮 ${name} joined the game!`);
return player;
}
/**
* Adds score to a player and handles level progression
*
* @param playerId - The player's ID
* @param points - Points to add
* @returns The updated player state
*
* @throws {@link Error}
* Thrown if player not found
*
* @fires levelUp - When player reaches a new level
*
* @example
* ```typescript
* // Add 50 points to player
* const updated = game.addScore(player.id, 50);
* if (updated.level > player.level) {
* console.log("Level up! 🎉");
* }
* ```
*/
addScore(playerId: string, points: number): PlayerState {
const player = this.players.get(playerId);
if (!player) {
throw new Error(`❌ Player ${playerId} not found`);
}
player.score += points;
const newLevel = Math.floor(player.score / 100) + 1;
if (newLevel > player.level) {
player.level = newLevel;
player.achievements.push({
id: `level-${newLevel}`,
name: `Level ${newLevel} Master`,
emoji: "🏆",
earnedAt: new Date()
});
console.log(`🎉 ${player.name} reached level ${newLevel}!`);
}
return player;
}
}
🚀 Advanced Concepts
🧙♂️ Advanced Topic 1: Generic Type Documentation
When you’re ready to level up, document your generic types:
// 🎯 Advanced generic documentation
/**
* A generic result type for operations that might fail
*
* @typeParam T - The type of the success value
* @typeParam E - The type of the error value
*
* @example
* ```typescript
* type UserResult = Result<User, ValidationError>;
*
* function getUser(id: string): UserResult {
* if (!id) {
* return { success: false, error: new ValidationError("Invalid ID") };
* }
* return { success: true, data: user };
* }
* ```
*/
type Result<T, E = Error> =
| { success: true; data: T; sparkles: "✨" }
| { success: false; error: E; sparkles: "💫" };
/**
* Creates a type-safe event emitter
*
* @typeParam Events - Record of event names to payload types
*
* @example
* ```typescript
* interface GameEvents {
* levelUp: { player: string; newLevel: number };
* achievement: { player: string; achievement: Achievement };
* }
*
* const emitter = new TypedEventEmitter<GameEvents>();
* emitter.on("levelUp", ({ player, newLevel }) => {
* console.log(`${player} reached level ${newLevel}! 🎉`);
* });
* ```
*/
class TypedEventEmitter<Events extends Record<string, any>> {
/**
* Subscribes to an event
* @param event - The event name
* @param handler - The event handler function
*/
on<K extends keyof Events>(event: K, handler: (payload: Events[K]) => void): void {
// 🚀 Implementation
}
}
🏗️ Advanced Topic 2: Module-Level Documentation
Document entire modules and namespaces:
// 🚀 Module-level documentation
/**
* @packageDocumentation
*
* ## Payment Processing Module 💳
*
* This module provides a complete payment processing solution
* with support for multiple payment providers.
*
* ### Features:
* - ✨ Multiple payment gateway support
* - 🛡️ PCI-compliant card handling
* - 🌍 Multi-currency support
* - 📊 Transaction reporting
*
* ### Quick Start:
* ```typescript
* import { PaymentProcessor } from '@myapp/payments';
*
* const processor = new PaymentProcessor({
* provider: 'stripe',
* apiKey: process.env.STRIPE_KEY
* });
*
* const result = await processor.charge({
* amount: 2999,
* currency: 'USD',
* card: customerCard
* });
* ```
*
* @module payments
*/
/**
* Main payment processor class
* @public
*/
export class PaymentProcessor {
// 💳 Implementation
}
⚠️ Common Pitfalls and Solutions
😱 Pitfall 1: Outdated Documentation
// ❌ Wrong way - documentation doesn't match code!
/**
* Calculates the sum of two numbers
* @param a - First number
* @param b - Second number
* @returns The sum
*/
const multiply = (a: number, b: number): number => {
return a * b; // 💥 Documentation says sum, but we're multiplying!
};
// ✅ Correct way - keep docs in sync!
/**
* Multiplies two numbers together
* @param a - First number
* @param b - Second number
* @returns The product of a and b
*/
const multiply = (a: number, b: number): number => {
return a * b; // ✅ Documentation matches implementation!
};
🤯 Pitfall 2: Missing Important Details
// ❌ Insufficient documentation
/**
* Processes payment
*/
async function processPayment(amount: number): Promise<boolean> {
// 💥 What currency? What errors? What does true/false mean?
return true;
}
// ✅ Comprehensive documentation
/**
* Processes a payment transaction
*
* @param amount - The payment amount in cents (USD)
* @returns `true` if payment successful, `false` if declined
*
* @throws {@link NetworkError}
* Thrown if payment gateway is unreachable
*
* @throws {@link ValidationError}
* Thrown if amount is negative or exceeds limits
*
* @example
* ```typescript
* try {
* const success = await processPayment(2999); // $29.99
* if (success) {
* console.log("✅ Payment processed!");
* }
* } catch (error) {
* console.error("❌ Payment failed:", error);
* }
* ```
*/
async function processPayment(amount: number): Promise<boolean> {
// ✅ Clear implementation
return true;
}
🛠️ Best Practices
- 🎯 Document Public APIs: Focus on public methods and interfaces
- 📝 Use Examples: Show real usage with
@example
tags - 🛡️ Document Errors: Use
@throws
for all possible exceptions - 🎨 Stay Consistent: Use the same style throughout your project
- ✨ Keep It Fresh: Update docs when code changes
🧪 Hands-On Exercise
🎯 Challenge: Document a Task Management System
Create comprehensive TSDoc documentation for this task management system:
📋 Requirements:
- ✅ Document all interfaces and classes
- 🏷️ Include examples for each public method
- 👤 Document type parameters
- 📅 Include error documentation
- 🎨 Add module-level documentation
🚀 Bonus Points:
- Use
@see
tags to link related methods - Add
@since
tags for versioning - Include
@remarks
for additional context
💡 Solution
🔍 Click to see solution
/**
* @packageDocumentation
*
* ## Task Management System 📋
*
* A comprehensive task management solution with support for
* projects, assignments, and deadlines.
*
* @module tasks
*/
/**
* Priority levels for tasks
* @public
*/
export type Priority = "low" | "medium" | "high" | "urgent";
/**
* Task status values
* @public
*/
export type TaskStatus = "pending" | "in-progress" | "completed" | "cancelled";
/**
* Represents a task in the system
*
* @remarks
* Tasks are the fundamental unit of work in the system.
* Each task belongs to a project and can be assigned to users.
*
* @public
*/
export interface Task {
/** Unique task identifier */
id: string;
/** Task title */
title: string;
/** Detailed task description */
description: string;
/** Task priority level */
priority: Priority;
/** Current task status */
status: TaskStatus;
/** Task emoji for visual identification */
emoji: string;
/** Optional due date */
dueDate?: Date;
/** User ID of assignee */
assignee?: string;
/** Project ID this task belongs to */
projectId: string;
/** Task creation timestamp */
createdAt: Date;
/** Last update timestamp */
updatedAt: Date;
}
/**
* Service for managing tasks
*
* @remarks
* This service provides complete CRUD operations for tasks
* along with advanced features like filtering and batch operations.
*
* @example
* ```typescript
* const taskService = new TaskService();
*
* // Create a new task
* const task = await taskService.createTask({
* title: "Implement TSDoc",
* description: "Add documentation to all public APIs",
* priority: "high",
* emoji: "📝",
* projectId: "proj-123"
* });
*
* // Assign to user
* await taskService.assignTask(task.id, "user-456");
* ```
*
* @public
*/
export class TaskService {
private tasks: Map<string, Task> = new Map();
/**
* Creates a new task
*
* @param taskData - The task information
* @returns The created task with generated ID and timestamps
*
* @throws {@link ValidationError}
* Thrown if required fields are missing or invalid
*
* @example
* ```typescript
* const task = await taskService.createTask({
* title: "Review PR",
* description: "Review the TypeScript refactor PR",
* priority: "medium",
* emoji: "👀",
* projectId: "proj-123",
* dueDate: new Date("2024-12-31")
* });
* console.log(`Created task: ${task.emoji} ${task.title}`);
* ```
*
* @since v1.0.0
*/
async createTask(taskData: Omit<Task, "id" | "status" | "createdAt" | "updatedAt">): Promise<Task> {
const task: Task = {
...taskData,
id: `task-${Date.now()}`,
status: "pending",
createdAt: new Date(),
updatedAt: new Date()
};
this.tasks.set(task.id, task);
console.log(`✅ Created task: ${task.emoji} ${task.title}`);
return task;
}
/**
* Assigns a task to a user
*
* @param taskId - The task ID
* @param userId - The user ID to assign to
* @returns The updated task
*
* @throws {@link NotFoundError}
* Thrown if task doesn't exist
*
* @throws {@link ValidationError}
* Thrown if task is already completed
*
* @fires taskAssigned - Event emitted when task is assigned
*
* @example
* ```typescript
* try {
* const updated = await taskService.assignTask("task-123", "user-456");
* console.log(`📋 Task assigned to user ${updated.assignee}`);
* } catch (error) {
* console.error("❌ Failed to assign task:", error);
* }
* ```
*
* @see {@link unassignTask} to remove assignment
* @since v1.0.0
*/
async assignTask(taskId: string, userId: string): Promise<Task> {
const task = this.tasks.get(taskId);
if (!task) {
throw new Error(`❌ Task ${taskId} not found`);
}
if (task.status === "completed") {
throw new Error(`❌ Cannot assign completed task`);
}
task.assignee = userId;
task.updatedAt = new Date();
console.log(`👤 Task assigned to ${userId}`);
return task;
}
/**
* Filters tasks by various criteria
*
* @typeParam K - The key to filter by
*
* @param key - The task property to filter by
* @param value - The value to match
* @returns Array of matching tasks
*
* @example
* ```typescript
* // Get all high priority tasks
* const urgentTasks = taskService.filterTasks("priority", "high");
*
* // Get tasks for a specific project
* const projectTasks = taskService.filterTasks("projectId", "proj-123");
*
* // Get assigned tasks
* const myTasks = taskService.filterTasks("assignee", "user-456");
* ```
*
* @since v1.1.0
*/
filterTasks<K extends keyof Task>(key: K, value: Task[K]): Task[] {
return Array.from(this.tasks.values()).filter(task => task[key] === value);
}
}
🎓 Key Takeaways
You’ve learned so much! Here’s what you can now do:
- ✅ Write TSDoc comments with confidence 💪
- ✅ Document complex types and generics properly 🛡️
- ✅ Create examples that help other developers 🎯
- ✅ Generate documentation from your code 🐛
- ✅ Build professional TypeScript libraries! 🚀
Remember: Good documentation is a gift to your future self and your teammates! 🤝
🤝 Next Steps
Congratulations! 🎉 You’ve mastered TSDoc documentation!
Here’s what to do next:
- 💻 Document your current TypeScript project
- 🏗️ Set up automated documentation generation
- 📚 Move on to our next tutorial: TypeScript ESLint Rules
- 🌟 Share your well-documented code with the world!
Remember: Every well-documented codebase started with a single TSDoc comment. Keep documenting, keep learning, and most importantly, have fun! 🚀
Happy coding! 🎉🚀✨