Lesson 20 of 20

Final Project: Build a Typed App

Project Overview

In this final project, you'll build a fully typed Task Manager application that demonstrates all the TypeScript concepts you've learned.

  • Type-safe data models with interfaces and enums
  • Generic utility functions
  • Discriminated unions for task states
  • Class-based service layer
  • Strict null checking and error handling

Type Definitions

Start by defining the type system for the application.

Example
// types.ts
export enum Priority { Low = 'low', Medium = 'medium', High = 'high' }
export enum Status { Todo = 'todo', InProgress = 'in-progress', Done = 'done' }

export interface Task {
  readonly id: string;
  title: string;
  description?: string;
  priority: Priority;
  status: Status;
  createdAt: Date;
  completedAt?: Date;
}

export type CreateTaskInput = Omit<Task, 'id' | 'createdAt' | 'completedAt' | 'status'>;
export type UpdateTaskInput = Partial<Pick<Task, 'title' | 'description' | 'priority'>>;

export interface TaskFilter {
  status?: Status;
  priority?: Priority;
  search?: string;
}

Task Service

Create a generic, type-safe service class for managing tasks.

Example
// taskService.ts
import { Task, CreateTaskInput, UpdateTaskInput, TaskFilter, Status } from './types';

class TaskService {
  private tasks: Map<string, Task> = new Map();

  create(input: CreateTaskInput): Task {
    const task: Task = {
      id: crypto.randomUUID(),
      ...input,
      status: Status.Todo,
      createdAt: new Date()
    };
    this.tasks.set(task.id, task);
    return task;
  }

  update(id: string, input: UpdateTaskInput): Task | null {
    const task = this.tasks.get(id);
    if (!task) return null;
    const updated = { ...task, ...input };
    this.tasks.set(id, updated);
    return updated;
  }

  filter(criteria: TaskFilter): Task[] {
    return Array.from(this.tasks.values()).filter(task => {
      if (criteria.status && task.status !== criteria.status) return false;
      if (criteria.priority && task.priority !== criteria.priority) return false;
      if (criteria.search && !task.title.includes(criteria.search)) return false;
      return true;
    });
  }
}

export default new TaskService();