09/02/2026⏱️ 13 min read
TypeScript Complete Guide: From JavaScript to Type-Safe Code
TypeScriptJavaScriptType SafetyProgrammingWeb DevelopmentFrontend

TypeScript Complete Guide: From JavaScript to Type-Safe Code

Introduction

TypeScript has become one of the most popular programming languages in web development, offering type safety, better tooling, and improved developer experience over JavaScript. Created by Microsoft, TypeScript is a superset of JavaScript that compiles to plain JavaScript, making it compatible with any JavaScript runtime.
This comprehensive guide will take you from TypeScript basics to advanced features, showing you how to write type-safe, maintainable code. Whether you're migrating from JavaScript or starting fresh, you'll learn how to leverage TypeScript's powerful type system to build robust applications.

What is TypeScript?

TypeScript is a statically typed programming language that builds on JavaScript by adding type definitions. It provides:
Key Features:
  • Static Type Checking: Catch errors at compile time
  • Type Inference: Automatic type detection
  • Modern JavaScript Features: ES6+ support with transpilation
  • Better IDE Support: Enhanced autocomplete and refactoring
  • Gradual Adoption: Can be adopted incrementally
  • Interoperability: Works with existing JavaScript code
Benefits:
  • Early Error Detection: Find bugs before runtime
  • Better Documentation: Types serve as inline documentation
  • Improved Refactoring: Safe code changes with confidence
  • Enhanced Developer Experience: Better autocomplete and IntelliSense
  • Scalability: Easier to maintain large codebases
TypeScript vs JavaScript:
  • TypeScript adds types but compiles to JavaScript
  • All JavaScript is valid TypeScript
  • TypeScript provides compile-time type checking
  • JavaScript is dynamically typed, TypeScript is statically typed

Getting Started with TypeScript

Setting up TypeScript is straightforward:
Installation:
bash
# Install TypeScript globally npm install -g typescript # Or install locally in your project npm install --save-dev typescript # Install TypeScript compiler npm install --save-dev @types/node
Initialize TypeScript Project:
bash
# Create tsconfig.json npx tsc --init # Compile TypeScript files tsc # Watch mode for development tsc --watch
Basic TypeScript Configuration (tsconfig.json):
json
{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "lib": ["ES2020"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "moduleResolution": "node", "declaration": true, "declarationMap": true, "sourceMap": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }
Your First TypeScript File:
typescript
// src/index.ts function greet(name: string): string { return `Hello, ${name}!`; } const message: string = greet('TypeScript'); console.log(message);
Compile and Run:
bash
tsc node dist/index.js

TypeScript Basics: Types and Annotations

TypeScript's type system is its core feature:
Basic Types:
typescript
// Primitive types let name: string = 'John'; let age: number = 30; let isActive: boolean = true; let data: null = null; let value: undefined = undefined; // Arrays let numbers: number[] = [1, 2, 3, 4, 5]; let names: Array<string> = ['John', 'Jane', 'Bob']; // Tuples let person: [string, number] = ['John', 30]; // Enums enum Color { Red = 'RED', Green = 'GREEN', Blue = 'BLUE' } let favoriteColor: Color = Color.Red; // Any (avoid when possible) let anything: any = 'can be anything'; // Unknown (safer than any) let userInput: unknown; if (typeof userInput === 'string') { let str: string = userInput; } // Void function logMessage(message: string): void { console.log(message); } // Never function throwError(message: string): never { throw new Error(message); }
Type Inference:
typescript
// TypeScript infers types automatically let name = 'John'; // inferred as string let age = 30; // inferred as number let isActive = true; // inferred as boolean // Explicit type annotations let userName: string = 'John'; let userAge: number = 30;
Union Types:
typescript
// Union types allow multiple types let id: string | number; id = '123'; id = 123; // Type guards function processId(id: string | number) { if (typeof id === 'string') { console.log(id.toUpperCase()); } else { console.log(id.toFixed(2)); } }
Intersection Types:
typescript
// Intersection types combine multiple types interface Person { name: string; age: number; } interface Employee { employeeId: string; department: string; } type EmployeePerson = Person & Employee; const employee: EmployeePerson = { name: 'John', age: 30, employeeId: 'E123', department: 'Engineering' };

Interfaces and Type Aliases

Interfaces and type aliases define object shapes:
Interfaces:
typescript
// Basic interface interface User { id: number; name: string; email: string; age?: number; // Optional property readonly createdAt: Date; // Read-only property } // Using interface const user: User = { id: 1, name: 'John Doe', email: 'john@example.com', createdAt: new Date() }; // Extending interfaces interface Admin extends User { role: 'admin' | 'super-admin'; permissions: string[]; } const admin: Admin = { id: 1, name: 'Admin User', email: 'admin@example.com', createdAt: new Date(), role: 'admin', permissions: ['read', 'write', 'delete'] }; // Interface with methods interface Calculator { add(a: number, b: number): number; subtract(a: number, b: number): number; multiply(a: number, b: number): number; divide(a: number, b: number): number; } // Implementing interface class BasicCalculator implements Calculator { add(a: number, b: number): number { return a + b; } subtract(a: number, b: number): number { return a - b; } multiply(a: number, b: number): number { return a * b; } divide(a: number, b: number): number { if (b === 0) throw new Error('Division by zero'); return a / b; } }
Type Aliases:
typescript
// Type aliases for unions type Status = 'pending' | 'approved' | 'rejected'; // Type aliases for objects type Point = { x: number; y: number; }; // Type aliases for functions type MathOperation = (a: number, b: number) => number; // Type aliases for complex types type UserWithStatus = User & { status: Status; }; // Differences between interface and type // Interfaces can be extended and merged // Types can represent unions, intersections, and primitives

Classes and Object-Oriented Programming

TypeScript enhances JavaScript classes with type safety:
Basic Classes:
typescript
class Person { // Public properties (default) name: string; age: number; // Private properties private email: string; // Protected properties protected id: number; // Readonly properties readonly createdAt: Date; // Constructor constructor(name: string, age: number, email: string) { this.name = name; this.age = age; this.email = email; this.id = Math.random(); this.createdAt = new Date(); } // Public method greet(): string { return `Hello, I'm ${this.name}`; } // Private method private validateEmail(): boolean { return this.email.includes('@'); } // Getter get userInfo(): string { return `${this.name} (${this.email})`; } // Setter set userAge(newAge: number) { if (newAge < 0) { throw new Error('Age cannot be negative'); } this.age = newAge; } } // Using the class const person = new Person('John', 30, 'john@example.com'); console.log(person.greet()); console.log(person.userInfo);
Inheritance:
typescript
// Base class class Animal { protected name: string; constructor(name: string) { this.name = name; } move(distance: number = 0): void { console.log(`${this.name} moved ${distance}m`); } } // Derived class class Dog extends Animal { private breed: string; constructor(name: string, breed: string) { super(name); // Call parent constructor this.breed = breed; } bark(): void { console.log(`${this.name} barks!`); } // Override method move(distance: number = 5): void { console.log(`${this.name} runs ${distance}m`); super.move(distance); } } const dog = new Dog('Buddy', 'Golden Retriever'); dog.bark(); dog.move(10);
Abstract Classes:
typescript
// Abstract class abstract class Shape { protected color: string; constructor(color: string) { this.color = color; } // Abstract method (must be implemented by derived classes) abstract calculateArea(): number; // Concrete method getColor(): string { return this.color; } } // Concrete implementation class Circle extends Shape { private radius: number; constructor(color: string, radius: number) { super(color); this.radius = radius; } calculateArea(): number { return Math.PI * this.radius * this.radius; } } class Rectangle extends Shape { private width: number; private height: number; constructor(color: string, width: number, height: number) { super(color); this.width = width; this.height = height; } calculateArea(): number { return this.width * this.height; } }

Generics

Generics provide type-safe, reusable code:
Basic Generics:
typescript
// Generic function function identity<T>(arg: T): T { return arg; } // Usage let output1 = identity<string>('hello'); let output2 = identity<number>(42); let output3 = identity('hello'); // Type inference // Generic interface interface Box<T> { value: T; } const stringBox: Box<string> = { value: 'hello' }; const numberBox: Box<number> = { value: 42 }; // Generic class class Container<T> { private items: T[] = []; add(item: T): void { this.items.push(item); } get(index: number): T | undefined { return this.items[index]; } getAll(): T[] { return [...this.items]; } } const stringContainer = new Container<string>(); stringContainer.add('hello'); stringContainer.add('world'); const numberContainer = new Container<number>(); numberContainer.add(1); numberContainer.add(2);
Generic Constraints:
typescript
// Constraint with interface interface Lengthwise { length: number; } function logLength<T extends Lengthwise>(arg: T): T { console.log(arg.length); return arg; } logLength('hello'); // OK logLength([1, 2, 3]); // OK // logLength(42); // Error: number doesn't have length // Multiple constraints function combine<T extends object, U extends object>(obj1: T, obj2: U): T & U { return { ...obj1, ...obj2 }; } // Keyof constraint function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const person = { name: 'John', age: 30 }; getProperty(person, 'name'); // OK getProperty(person, 'age'); // OK // getProperty(person, 'email'); // Error: 'email' doesn't exist
Utility Types:
typescript
// Partial - makes all properties optional interface User { name: string; age: number; email: string; } type PartialUser = Partial<User>; // { name?: string; age?: number; email?: string; } // Required - makes all properties required type RequiredUser = Required<PartialUser>; // Readonly - makes all properties readonly type ReadonlyUser = Readonly<User>; // Pick - select specific properties type UserName = Pick<User, 'name'>; // Omit - exclude specific properties type UserWithoutEmail = Omit<User, 'email'>; // Record - create object type type UserMap = Record<string, User>; // Exclude - exclude types from union type NonNullable<T> = Exclude<T, null | undefined>; // Extract - extract types from union type StringKeys = Extract<keyof User, string>;

Advanced TypeScript Features

TypeScript offers advanced features for complex scenarios:
Decorators (Experimental):
typescript
// Enable decorators in tsconfig.json // "experimentalDecorators": true // Class decorator function sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); } @sealed class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } } // Method decorator function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Calling ${propertyKey} with args:`, args); const result = originalMethod.apply(this, args); console.log(`Result:`, result); return result; }; return descriptor; } class Calculator { @log add(a: number, b: number): number { return a + b; } }
Conditional Types:
typescript
// Conditional type type NonNullable<T> = T extends null | undefined ? never : T; // Infer keyword type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any; type FunctionReturnType = ReturnType<() => string>; // string // Template literal types type EventName<T extends string> = `on${Capitalize<T>}`; type ClickEvent = EventName<'click'>; // 'onClick' // Mapped types type Optional<T> = { [K in keyof T]?: T[K]; };
Module Augmentation:
typescript
// Augment existing modules declare module 'express' { interface Request { user?: User; } } // Now Request has user property import { Request } from 'express'; const req: Request = {}; req.user; // OK
TypeScript integrates seamlessly with popular frameworks:
TypeScript with React:
typescript
// Functional components import React from 'react'; interface Props { name: string; age?: number; } const Greeting: React.FC<Props> = ({ name, age }) => { return ( <div> <h1>Hello, {name}!</h1> {age && <p>You are {age} years old</p>} </div> ); }; // Class components interface State { count: number; } class Counter extends React.Component<{}, State> { state: State = { count: 0 }; increment = (): void => { this.setState({ count: this.state.count + 1 }); }; render() { return ( <div> <p>Count: {this.state.count}</p> <button onClick={this.increment}>Increment</button> </div> ); } } // Hooks with TypeScript import { useState, useEffect } from 'react'; function useCounter(initialValue: number = 0) { const [count, setCount] = useState<number>(initialValue); const increment = () => setCount(count + 1); const decrement = () => setCount(count - 1); return { count, increment, decrement }; }
TypeScript with Node.js:
typescript
// Express with TypeScript import express, { Request, Response, NextFunction } from 'express'; interface User { id: number; name: string; email: string; } const app = express(); app.get('/users/:id', (req: Request, res: Response, next: NextFunction) => { const userId: number = parseInt(req.params.id); const user: User = { id: userId, name: 'John Doe', email: 'john@example.com' }; res.json(user); }); app.listen(3000, () => { console.log('Server running on port 3000'); });

Migrating from JavaScript to TypeScript

Migrating to TypeScript can be done gradually:
Step 1: Add TypeScript to Project:
bash
npm install --save-dev typescript @types/node npx tsc --init
Step 2: Rename Files Gradually:
bash
# Rename .js files to .ts mv src/index.js src/index.ts
Step 3: Add Type Annotations:
typescript
// Before (JavaScript) function add(a, b) { return a + b; } // After (TypeScript) function add(a: number, b: number): number { return a + b; }
Step 4: Configure tsconfig.json:
json
{ "compilerOptions": { "allowJs": true, // Allow JavaScript files "checkJs": false, // Don't check JavaScript files initially "noImplicitAny": false, // Allow implicit any initially "strict": false // Start with loose settings } }
Step 5: Enable Strict Mode Gradually:
json
{ "compilerOptions": { "strict": true, // Enable all strict checks "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true } }
Best Practices:
  • Start with allowJs: true to mix JS and TS
  • Add types incrementally
  • Use any sparingly, prefer unknown
  • Enable strict mode gradually
  • Use type inference when possible
  • Leverage existing JavaScript libraries with @types packages

TypeScript Best Practices

Follow these best practices for better TypeScript code:
1. Use Type Inference:
typescript
// Good - let TypeScript infer types const name = 'John'; const age = 30; // Avoid unnecessary type annotations const name: string = 'John'; // Redundant
2. Prefer Interfaces for Objects:
typescript
// Good - use interfaces for object shapes interface User { name: string; age: number; } // Use types for unions, intersections, primitives type Status = 'active' | 'inactive';
3. Use Strict Mode:
json
{ "compilerOptions": { "strict": true } }
4. Avoid any, Use unknown:
typescript
// Bad function processData(data: any) { // No type safety } // Good function processData(data: unknown) { if (typeof data === 'string') { // Type-safe handling } }
5. Use Utility Types:
typescript
// Use built-in utility types type PartialUser = Partial<User>; type RequiredUser = Required<User>; type ReadonlyUser = Readonly<User>;
6. Document Complex Types:
typescript
/** * Represents a user in the system * @property id - Unique identifier * @property name - User's full name * @property email - User's email address */ interface User { id: number; name: string; email: string; }

Conclusion

TypeScript brings type safety, better tooling, and improved developer experience to JavaScript development. By understanding TypeScript's type system, generics, and advanced features, you can write more maintainable, scalable code.
Start with the basics—types, interfaces, and classes—then gradually explore advanced features like generics, decorators, and conditional types. Remember that TypeScript is designed to be adopted incrementally, so you can migrate your JavaScript codebase gradually.
With TypeScript, you can catch errors early, improve code documentation, and build more robust applications. Whether you're building frontend applications with React or backend services with Node.js, TypeScript provides the tools you need to write better code.
Embrace TypeScript's type system, leverage its powerful features, and enjoy the benefits of type-safe JavaScript development.

Share this article

Comments