Getting Started with TypeScript: A Practical Guide

TypeScript code on a dark editor background

TypeScript has become the standard for building large-scale JavaScript applications. In this comprehensive guide, we'll explore everything you need to know to get started with TypeScript and write better, more maintainable code.

Why TypeScript?#

TypeScript adds static type checking to JavaScript, catching errors before they reach production. But it's more than just types—it's a better developer experience.

TypeScript is JavaScript that scales. It's a strict syntactical superset of JavaScript that adds optional static typing.

— Anders Hejlsberg, TypeScript Creator

Setting Up Your Environment#

First, let's install TypeScript globally:

Bash
npm install -g typescript

Create a new project and initialize it:

Bash
mkdir my-ts-project
cd my-ts-project
npm init -y
npm install typescript --save-dev
npx tsc --init

This generates a tsconfig.json file with sensible defaults.

Basic Types#

TypeScript provides several built-in types. Let's explore the most common ones:

TypeScript
// Primitive types
const name: string = "Alice";
const age: number = 30;
const isActive: boolean = true;

// Arrays
const numbers: number[] = [1, 2, 3, 4, 5];
const names: Array<string> = ["Alice", "Bob", "Charlie"];

// Tuples - fixed length arrays with specific types
const coordinate: [number, number] = [10, 20];
const record: [string, number, boolean] = ["Alice", 30, true];

Interfaces and Type Aliases#

Interfaces define the shape of objects:

TypeScript
interface User {
    id: number;
    name: string;
    email: string;
    age?: number; // Optional property
    readonly createdAt: Date; // Cannot be modified after creation
}

// Using the interface
const user: User = {
    id: 1,
    name: "Alice",
    email: "alice@example.com",
    createdAt: new Date()
};

Type aliases offer similar functionality with some differences:

TypeScript
type Point = {
    x: number;
    y: number;
};

// Union types - only possible with type aliases
type Status = "pending" | "approved" | "rejected";

// Intersection types
type Employee = User & {
    department: string;
    salary: number;
};

Functions with Types#

TypeScript brings type safety to functions:

TypeScript
// Function with typed parameters and return type
function greet(name: string, greeting: string = "Hello"): string {
    return `${greeting}, ${name}!`;
}

// Arrow function with types
const add = (a: number, b: number): number => a + b;

// Function with optional parameters
function createUser(name: string, age?: number): User {
    return {
        id: Math.random(),
        name,
        email: `${name.toLowerCase()}@example.com`,
        age,
        createdAt: new Date()
    };
}

// Rest parameters
function sum(...numbers: number[]): number {
    return numbers.reduce((acc, curr) => acc + curr, 0);
}

Generics#

Generics allow you to write reusable, type-safe code:

TypeScript
// Generic function
function identity<T>(value: T): T {
    return value;
}

const str = identity<string>("hello"); // type: string
const num = identity(42); // type: number (inferred)

// Generic interface
interface ApiResponse<T> {
    data: T;
    status: number;
    message: string;
}

// Using generic interface
interface Product {
    id: number;
    name: string;
    price: number;
}

const response: ApiResponse<Product[]> = {
    data: [{ id: 1, name: "Widget", price: 9.99 }],
    status: 200,
    message: "Success"
};
TypeScript
// Generic with constraint
interface HasLength {
    length: number;
}

function logLength<T extends HasLength>(item: T): void {
    console.log(`Length: ${item.length}`);
}

logLength("hello"); // Works - strings have length
logLength([1, 2, 3]); // Works - arrays have length
// logLength(42);    // Error - numbers don't have length

Working with Classes#

TypeScript enhances JavaScript classes with access modifiers and more:

TypeScript
class BankAccount {
    // Properties with access modifiers
    private balance: number;
    public readonly accountNumber: string;
    protected owner: string;

    constructor(owner: string, initialBalance: number = 0) {
        this.accountNumber = this.generateAccountNumber();
        this.owner = owner;
        this.balance = initialBalance;
    }

    private generateAccountNumber(): string {
        return Math.random().toString(36).substring(2, 15);
    }

    public deposit(amount: number): void {
        if (amount <= 0) {
            throw new Error("Deposit amount must be positive");
        }
        this.balance += amount;
    }

    public withdraw(amount: number): boolean {
        if (amount > this.balance) {
            return false;
        }
        this.balance -= amount;
        return true;
    }

    public getBalance(): number {
        return this.balance;
    }
}

Utility Types#

TypeScript provides built-in utility types for common transformations:

TypeScript
interface Todo {
    title: string;
    description: string;
    completed: boolean;
    createdAt: Date;
}

// Partial - makes all properties optional
type PartialTodo = Partial<Todo>;

// Required - makes all properties required
type RequiredTodo = Required<Todo>;

// Pick - select specific properties
type TodoPreview = Pick<Todo, "title" | "completed">;

// Omit - exclude specific properties
type TodoWithoutDates = Omit<Todo, "createdAt">;

// Readonly - makes all properties readonly
type ReadonlyTodo = Readonly<Todo>;

// Record - create an object type with specific keys
type TodoRecord = Record<string, Todo>;

Error Handling with Types#

Type-safe error handling patterns:

TypeScript
// Result type pattern
type Result<T, E = Error> =
    | { success: true; data: T }
    | { success: false; error: E };

async function fetchUser(id: number): Promise<Result<User>> {
    try {
        const response = await fetch(`/api/users/${id}`);
        if (!response.ok) {
            return {
                success: false,
                error: new Error(`HTTP ${response.status}`)
            };
        }
        const data = await response.json();
        return { success: true, data };
    } catch (error) {
        return {
            success: false,
            error: error instanceof Error ? error : new Error(String(error))
        };
    }
}

// Usage with type narrowing
const result = await fetchUser(1);
if (result.success) {
    console.log(result.data.name); // TypeScript knows data is User
} else {
    console.error(result.error.message); // TypeScript knows error is Error
}

Next Steps#

Now that you understand the basics, here are some resources to continue your TypeScript journey:

TypeScript transforms how you write JavaScript. Start small, gradually add types to your existing projects, and watch your code quality improve dramatically.

Share:

Related Articles