Lesson 01-TypeScript Basics

TypeScript Basics

Type Declarations

Variable Type Declarations:

// Explicit type declarations
let name: string = 'Alice'
let age: number = 30
let isActive: boolean = true

// Array types
let numbers: number[] = [1, 2, 3]
let names: Array<string> = ['Alice', 'Bob'] // Generic syntax

// Tuple type
let user: [string, number] = ['Alice', 30]

// Enum type
enum Color {
  Red = 'RED',
  Green = 'GREEN',
  Blue = 'BLUE'
}
let favoriteColor: Color = Color.Red

Function Type Declarations:

// Parameter and return type
function greet(name: string): string {
  return `Hello, ${name}`
}

// Optional parameter
function greet(name: string, title?: string): string {
  return title ? `Hello, ${title} ${name}` : `Hello, ${name}`
}

// Default parameter
function greet(name: string, title: string = 'Mr.'): string {
  return `Hello, ${title} ${name}`
}

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

Type Guards

Type Guards:

// typeof type guard
function printValue(value: string | number) {
  if (typeof value === 'string') {
    console.log(value.toUpperCase()) // value is string here
  } else {
    console.log(value.toFixed(2)) // value is number here
  }
}

// instanceof type guard
class Animal {}
class Dog extends Animal {}

function handleAnimal(animal: Animal) {
  if (animal instanceof Dog) {
    console.log('It\'s a dog!')
  } else {
    console.log('It\'s some other animal')
  }
}

// Custom type guard
interface Bird {
  fly(): void
}

interface Fish {
  swim(): void
}

function isBird(pet: Bird | Fish): pet is Bird {
  return 'fly' in pet
}

function movePet(pet: Bird | Fish) {
  if (isBird(pet)) {
    pet.fly()
  } else {
    pet.swim()
  }
}

TS Compilation

tsconfig.json Configuration:

{
  "compilerOptions": {
    "target": "ES2020",          // Compilation target version
    "module": "CommonJS",        // Module system
    "outDir": "./dist",          // Output directory
    "rootDir": "./src",          // Source code directory
    "strict": true,              // Enable all strict type checking
    "esModuleInterop": true,     // Compatibility with CommonJS/ES modules
    "skipLibCheck": true,        // Skip type checking for library files
    "forceConsistentCasingInFileNames": true // Enforce consistent file name casing
  },
  "include": ["src/**/*"],       // Files to include
  "exclude": ["node_modules"]    // Files to exclude
}

Compilation Commands:

# Compile entire project
tsc

# Watch mode (auto-recompile)
tsc --watch

# Compile specific file
tsc src/index.ts

# Generate declaration files (.d.ts)
tsc --declaration

Values and Types

Value Types

Basic Types:

// Primitive types
let isDone: boolean = false
let decimal: number = 6
let hex: number = 0xf00d
let binary: number = 0b1010
let octal: number = 0o744
let color: string = "blue"
let sentence: string = `Hello, ${color}`

// Special types
let u: undefined = undefined
let n: null = null
let big: bigint = 100n
let sym: symbol = Symbol('key')

Object Types:

// Object type
let user: { name: string; age: number } = {
  name: 'Alice',
  age: 30
}

// Interface defining object type
interface Person {
  name: string
  age: number
  readonly id: number // Read-only property
}

let person: Person = {
  name: 'Bob',
  age: 25,
  id: 12345
}

// person.id = 67890 // Error: Cannot assign to 'id' because it is a read-only property

Union Types

Using Union Types:

// String or number
let id: string | number
id = 'abc123'
id = 12345

// Function parameter union type
function printId(id: number | string) {
  if (typeof id === 'string') {
    console.log(id.toUpperCase())
  } else {
    console.log(id.toFixed(2))
  }
}

// Union type array
let arr: (number | string)[]
arr = [1, 'two', 3, 'four']

// Class type union
class Dog {
  bark() {
    console.log('Woof!')
  }
}

class Cat {
  meow() {
    console.log('Meow!')
  }
}

let pet: Dog | Cat
pet = new Dog()
pet.bark() // Works
// pet.meow() // Error: Property 'meow' does not exist on type 'Dog'

if ('meow' in pet) {
  (pet as Cat).meow() // Type assertion
}

Intersection Types

Merging Intersection Types:

// Base interfaces
interface Person {
  name: string
  age: number
}

interface Employee {
  employeeId: number
  department: string
}

// Intersection type
type EmployeePerson = Person & Employee

let employee: EmployeePerson = {
  name: 'Alice',
  age: 30,
  employeeId: 12345,
  department: 'Engineering'
}

// Function returning intersection type
function createEmployee(name: string, age: number, employeeId: number, department: string): Person & Employee {
  return {
    name,
    age,
    employeeId,
    department
  }
}

Type System

Primitive Types and Wrapper Objects

Primitive Types vs Wrapper Objects:

// Primitive types
let num: number = 123
let str: string = 'hello'
let bool: boolean = true

// Wrapper objects (not recommended in TS)
let numObj: Number = new Number(123) // Not recommended
let strObj: String = new String('hello') // Not recommended
let boolObj: Boolean = new Boolean(true) // Not recommended

// Type conversion
let numFromString: number = Number('123') // Recommended
let strFromNum: string = String(123) // Recommended
let boolFromStr: boolean = Boolean('true') // Recommended

Object Type vs object Type

Object vs object:

// Object type (parent type of all objects)
let obj1: Object = { name: 'Alice' }
obj1 = new Date() // Can assign any object
obj1 = [1, 2, 3] // Can assign arrays

// object type (non-primitive types)
let obj2: object = { name: 'Bob' }
// obj2 = 123 // Error: Type 'number' is not assignable to type 'object'
// obj2 = 'hello' // Error: Type 'string' is not assignable to type 'object'
obj2 = new Date() // Can assign any object

// Best practice: Use specific types instead of Object/object
interface User {
  name: string
  age: number
}

let user: User = { name: 'Charlie', age: 30 }

Value Types vs Reference Types

Differences Between Value and Reference Types:

// Value types (primitive types)
let a: number = 10
let b: number = a
b = 20
console.log(a) // Still 10

// Reference types (objects)
let objA: { value: number } = { value: 10 }
let objB: { value: number } = objA
objB.value = 20
console.log(objA.value) // Also 20

// Solving reference type sharing issues
let objC: { value: number } = { value: 10 }
let objD: { value: number } = { ...objC } // Shallow copy
objD.value = 20
console.log(objC.value) // Still 10

Union Types and Intersection Types

Advanced Union Type Usage:

// Function overloading (implemented with union types)
function makeDate(timestamp: number): Date
function makeDate(year: number, month: number, day: number): Date
function makeDate(overload1: number, overload2?: number, overload3?: number): Date {
  if (overload2 !== undefined && overload3 !== undefined) {
    return new Date(overload1, overload2, overload3)
  } else {
    return new Date(overload1)
  }
}

// Type alias union
type ID = number | string
let userId: ID = 123
userId = 'abc123'

// Conditional types (based on union types)
type NonNullable<T> = T extends null | undefined ? never : T
type T1 = NonNullable<string | null | undefined> // string

Advanced Intersection Type Usage:

// Mixin types
type Serializable = { serialize(): string }
type Deserializable = { deserialize(data: string): void }

type SerializableAndDeserializable = Serializable & Deserializable

class User implements SerializableAndDeserializable {
  constructor(public name: string, public age: number) {}
  
  serialize(): string {
    return JSON.stringify(this)
  }
  
  deserialize(data: string): void {
    const parsed = JSON.parse(data)
    this.name = parsed.name
    this.age = parsed.age
  }
}

// Utility type combination
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
interface User {
  id: number
  name: string
  age: number
}

type PartialUser = PartialBy<User, 'id' | 'age'>
// Equivalent to { id?: number; name: string; age?: number }

typeof Operator

typeof Type Queries:

// JavaScript typeof
let num = 123
console.log(typeof num) // "number"

// TypeScript typeof type query
let person = { name: 'Alice', age: 30 }
type PersonType = typeof person // { name: string; age: number }

function greet(p: typeof person) {
  console.log(`Hello, ${p.name}`)
}

// Combining with generics
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key]
}

let user = { name: 'Bob', age: 25 }
let userName = getProperty(user, 'name') // Type-safe
// let userAge = getProperty(user, 'gender') // Error: 'gender' is not a property of 'user'

type Command and Type Aliases

Using Type Aliases (type):

// Basic type aliases
type ID = number | string
type Point = {
  x: number
  y: number
}

// Function type alias
type GreetFunction = (name: string) => string

// Complex type alias
type Tree<T> = {
  value: T
  left?: Tree<T>
  right?: Tree<T>
}

// Using type aliases
let userId: ID = 123
userId = 'abc123'

let point: Point = { x: 10, y: 20 }

let greet: GreetFunction = (name) => `Hello, ${name}`

let tree: Tree<number> = {
  value: 1,
  left: {
    value: 2
  },
  right: {
    value: 3
  }
}

Block-Level Type Declarations

Block-Level Scope Types:

{
  // Block-level type declaration
  type LocalType = string
  let localVar: LocalType = 'hello'
  
  // This type is only valid in the current block
  interface LocalInterface {
    name: string
  }
  
  let localObj: LocalInterface = { name: 'Alice' }
}

// console.log(typeof LocalType) // Error: Cannot find name 'LocalType'
// let x: LocalType = 'world' // Error: Cannot find name 'LocalType'

Type Compatibility

Type Compatibility Rules:

// Structural type system (duck typing)
interface Person {
  name: string
  age: number
}

class Employee {
  constructor(public name: string, public age: number) {}
}

let p: Person = { name: 'Alice', age: 30 }
p = new Employee('Bob', 25) // Can assign because structure matches

// Function compatibility
type Handler = (a: number, b: number) => void

let handler1: Handler = (a: number, b: number) => {}
let handler2: Handler = (a: number) => {} // Works, fewer parameters
// let handler3: Handler = (a: number, b: number, c: number) => {} // Error: Too many parameters

// Optional and rest parameters
let handler4: Handler = (a: number, b?: number) => {} // Works, b is optional
let handler5: Handler = (...args: number[]) => {} // Works, rest parameters

// Enum compatibility
enum Status { Ready, Waiting }
enum Color { Red, Blue, Green }

let s: Status = Status.Ready
// s = Color.Red // Error: Type 'Color' is not assignable to type 'Status'

// Class compatibility (only instance members are compared)
class Animal {
  feet: number
  constructor(name: string, numFeet: number) {}
}

class Size {
  feet: number
  constructor(numFeet: number) {}
}

let a: Animal = new Size(4) // Works, instance members match
// Note: Different constructor parameters do not affect compatibility

Advanced Type Compatibility:

// Generic compatibility
interface Empty<T> {}
let x: Empty<number>
let y: Empty<string>
x = y // Works, because type parameter T is not used

interface NotEmpty<T> {
  data: T
}
let x1: NotEmpty<number>
let y1: NotEmpty<string>
// x1 = y1 // Error: Type 'NotEmpty<string>' is not assignable to type 'NotEmpty<number>'

// Function parameter bivariance
type EventHandler = (event: string | number) => void
let handler: EventHandler = (event: string) => {} // Works
let handler2: EventHandler = (event: number) => {} // Works

// Return type contravariance
type Creator<T> = () => T
let creator: Creator<string | number> = () => 'hello' // Works
let creator2: Creator<string | number> = () => 123 // Works

Basic Types and Type Annotations

Primitive Types

Primitive Type Declarations and Usage:

// String type
let name: string = 'Alice'
name = 'Bob' // Correct
// name = 123 // Error: Type 'number' is not assignable to type 'string'

// Number type
let age: number = 30
age = 30.5 // Correct (supports floating-point numbers)
// age = '30' // Error: Type 'string' is not assignable to type 'number'

// Boolean type
let isActive: boolean = true
isActive = false // Correct
// isActive = 'true' // Error: Type 'string' is not assignable to type 'boolean'

// Special primitive types
let nothing: null = null
let notDefined: undefined = undefined
let bigIntValue: bigint = 100n
let symbolValue: symbol = Symbol('unique')

// Usage example
function greet(name: string, age: number): string {
  return `Hello ${name}, you are ${age} years old`
}

Object Types

Object Type Declaration Methods:

// Object type (parent type of all objects)
let obj1: Object = { name: 'Alice' }
obj1 = new Date() // Can assign any object
// obj1.name // Error: Property 'name' does not exist on type 'Object'

// {} type (empty object type)
let obj2: {} = {} // Can only assign empty object
// obj2 = { name: 'Bob' } // Error: Type '{ name: string; }' is not assignable to type '{}'

// Interface defining object type (recommended)
interface Person {
  name: string
  age: number
  readonly id: number // Read-only property
}

let person: Person = {
  name: 'Alice',
  age: 30,
  id: 12345
}
// person.id = 67890 // Error: Cannot assign to 'id' because it is a read-only property

// Class implementing interface
class Employee implements Person {
  constructor(
    public name: string,
    public age: number,
    public id: number
  ) {}
}

let employee: Person = new Employee('Bob', 25, 54321)

Arrays and Tuples

Array Type Declarations:

// Array types (two syntaxes)
let numbers: number[] = [1, 2, 3]
let strings: Array<string> = ['a', 'b', 'c'] // Generic syntax

// Mixed type array
let mixed: (number | string)[] = [1, 'two', 3, 'four']

// Tuple type (fixed-length and typed array)
let user: [string, number] = ['Alice', 30]
// user = [30, 'Alice'] // Error: Type mismatch
// user.push('admin') // Can add elements (but loses type safety)

// Tuple with optional elements
let userWithOptional: [string, number?, boolean?] = ['Alice']
userWithOptional = ['Bob', 25]
userWithOptional = ['Charlie', 30, true]

// Accessing tuple elements
const [userName, userAge] = user // Destructuring assignment
console.log(userName, userAge)

Enum Types

Enum Type Definition and Usage:

// Numeric enum (starts from 0 by default)
enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}

let dir: Direction = Direction.Up
console.log(dir) // 0

// String enum
enum LogLevel {
  ERROR = 'ERROR',
  WARN = 'WARN',
  INFO = 'INFO',
  DEBUG = 'DEBUG'
}

let logLevel: LogLevel = LogLevel.INFO
console.log(logLevel) // 'INFO'

// Heterogeneous enum (mixing numbers and strings)
enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = 'YES'
}

// Const enum (inlined at compile time)
const enum Size {
  Small = 1,
  Medium = 2,
  Large = 3
}

let size: Size = Size.Medium
// Compiled to: let size = 2;

// Enum practical tips
enum Status {
  Pending = 'PENDING',
  Approved = 'APPROVED',
  Rejected = 'REJECTED'
}

function handleStatus(status: Status) {
  switch (status) {
    case Status.Pending:
      console.log('Pending')
      break
    case Status.Approved:
      console.log('Approved')
      break
    case Status.Rejected:
      console.log('Rejected')
      break
  }
}

Type Inference and Type Compatibility

Type Inference Rules

Implicit Type Inference Examples:

// Variable initialization inference
let x = 10 // Inferred as number
x = 'hello' // Error: Type 'string' is not assignable to type 'number'

// Function return type inference
function add(a: number, b: number) {
  return a + b // Inferred to return number
}

// Best common type inference
let arr = [0, 1, null] // Inferred as (number | null)[]

// Contextual type inference
window.onmousedown = function(mouseEvent) {
  console.log(mouseEvent.button) // Correctly inferred as MouseEvent
}

Type Compatibility

Structural Type System Examples:

interface Named {
  name: string
}

class Person {
  constructor(public name: string) {}
}

let p: Named
p = new Person('Alice') // Can assign because structure matches

// Function compatibility
type Handler = (a: number, b: number) => void

let handler1: Handler = (a: number, b: number) => {}
let handler2: Handler = (a: number) => {} // Works, fewer parameters
// let handler3: Handler = (a: number, b: number, c: number) => {} // Error: Too many parameters

// Optional and rest parameters
let handler4: Handler = (a: number, b?: number) => {} // Works, b is optional
let handler5: Handler = (...args: number[]) => {} // Works, rest parameters

// Enum compatibility
enum Status { Ready, Waiting }
enum Color { Red, Blue, Green }

let s: Status = Status.Ready
// s = Color.Red // Error: Type 'Color' is not assignable to type 'Status'

Type Assertions

Type Assertion Syntax:

// Angle-bracket syntax (not recommended in .tsx files)
let someValue: any = 'this is a string'
let strLength: number = (<string>someValue).length

// as syntax (recommended)
let someValue: any = 'this is a string'
let strLength: number = (someValue as string).length

// Non-null assertion operator (!)
function liveDangerously(x?: number | null) {
  console.log(x!.toFixed(2)) // Tells compiler x is not null/undefined
}

// Best practices for type assertions
interface Cat {
  meow(): void
}

interface Dog {
  bark(): void
}

function isCat(pet: Cat | Dog): pet is Cat {
  return 'meow' in pet
}

function handlePet(pet: Cat | Dog) {
  if (isCat(pet)) {
    pet.meow() // Type-safe
  } else {
    pet.bark() // Type-safe
  }
  
  // Alternative: Type assertion (not recommended unless type is certain)
  // (pet as Cat).meow() // Dangerous: Runtime error if pet is actually Dog
}

Type Guards

Custom Type Guard Implementation:

// typeof type guard
function printValue(value: string | number) {
  if (typeof value === 'string') {
    console.log(value.toUpperCase()) // value is string here
  } else {
    console.log(value.toFixed(2)) // value is number here
  }
}

// instanceof type guard
class Animal {}
class Dog extends Animal {}

function handleAnimal(animal: Animal) {
  if (animal instanceof Dog) {
    console.log('It\'s a dog!')
  } else {
    console.log('It\'s some other animal')
  }
}

// Custom type guard
interface Bird {
  fly(): void
}

interface Fish {
  swim(): void
}

function isBird(pet: Bird | Fish): pet is Bird {
  return 'fly' in pet
}

function movePet(pet: Bird | Fish) {
  if (isBird(pet)) {
    pet.fly()
  } else {
    pet.swim()
  }
}

// in operator type guard
type Square = { kind: 'square'; size: number }
type Rectangle = { kind: 'rectangle'; width: number; height: number }

function area(shape: Square | Rectangle): number {
  if ('size' in shape) {
    return shape.size * shape.size
  } else {
    return shape.width * shape.height
  }
}

Index Types and Mapped Types Basics

Index Type Operations:

// Indexed access types
interface Person {
  name: string
  age: number
}

type NameType = Person['name'] // string

// Index signature
interface StringDictionary {
  [key: string]: string
}

let dict: StringDictionary = {
  name: 'Alice',
  email: 'alice@example.com'
}
// dict.age = 30 // Error: Type 'number' is not assignable to type 'string'

// Mapped type basics
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

type Partial<T> = {
  [P in keyof T]?: T[P]
}

interface User {
  id: number
  name: string
}

type ReadonlyUser = Readonly<User>
type PartialUser = Partial<User>

Functions and Classes

Function Type Annotations

Function Type Declarations:

// Basic function type
function greet(name: string): string {
  return `Hello, ${name}`
}

// Function type variable
let greetFunc: (name: string) => string = greet

// Optional parameter
function greet(name: string, title?: string): string {
  return title ? `Hello, ${title} ${name}` : `Hello, ${name}`
}

// Default parameter
function greet(name: string, title: string = 'Mr.'): string {
  return `Hello, ${title} ${name}`
}

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

// Function type alias
type MathOperation = (a: number, b: number) => number

const add: MathOperation = (x, y) => x + y
const subtract: MathOperation = (x, y) => x - y

Optional Parameters and Default Parameters

Parameter Handling Techniques:

// Combining optional and default parameters
function createUser(
  name: string,
  age?: number,
  isAdmin: boolean = false
): { name: string; age?: number; isAdmin: boolean } {
  return { name, age, isAdmin }
}

// Parameter destructuring with defaults
function drawChart({
  size = 'big',
  coords = { x: 0, y: 0 },
  radius = 25
} = {}) {
  console.log(size, coords, radius)
}

drawChart() // Uses all defaults
drawChart({ size: 'small' }) // Overrides size

// Rest parameters with defaults
function connect(options: {
  timeout?: number
  retries?: number
  [key: string]: any
} = {}) {
  // ...
}

// Function overloading (handling different parameter types)
function makeDate(timestamp: number): Date
function makeDate(year: number, month: number, day: number): Date
function makeDate(overload1: number, overload2?: number, overload3?: number): Date {
  if (overload2 !== undefined && overload3 !== undefined) {
    return new Date(overload1, overload2, overload3)
  } else {
    return new Date(overload1)
  }
}

Rest Parameters and Spread Operator

Parameter Spread Operations:

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

// Spread array as parameters
let numbers = [1, 2, 3]
console.log(sum(...numbers)) // Equivalent to sum(1, 2, 3)

// Object spread
let defaults = { food: 'spicy', price: '$$', ambiance: 'noisy' }
let search = { ...defaults, food: 'rich' } // Overrides food property

// Function parameter spread
function logValues(a: number, b: number, c: number) {
  console.log(a, b, c)
}

let nums = [1, 2, 3]
logValues(...nums) // Equivalent to logValues(1, 2, 3)

// Generic function with spread
function mergeObjects<T extends object, U extends object>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 }
}

let merged = mergeObjects({ name: 'Alice' }, { age: 30 })

Class Basics

Class Definition and Instantiation:

// Basic class definition
class Person {
  // Constructor
  constructor(public name: string, public age: number) {}
  
  // Method
  greet() {
    return `Hello, my name is ${this.name}`
  }
}

let person = new Person('Alice', 30)
console.log(person.greet())

// Inheritance
class Employee extends Person {
  constructor(
    name: string,
    age: number,
    public jobTitle: string
  ) {
    super(name, age) // Call parent constructor
  }
  
  work() {
    return `${this.name} is working as a ${this.jobTitle}`
  }
}

let employee = new Employee('Bob', 25, 'Developer')
console.log(employee.work())
console.log(employee.greet()) // Inherited method

// Static members
class MathHelper {
  static PI = 3.14159
  
  static calculateCircleArea(radius: number): number {
    return this.PI * radius * radius
  }
}

console.log(MathHelper.calculateCircleArea(2))

Access Modifiers

Access Control Implementation:

// public (default)
class PublicExample {
  public name: string // Explicit declaration
  constructor(name: string) {
    this.name = name // Accessible everywhere
  }
}

// private (accessible only within class)
class PrivateExample {
  private secret: string
  
  constructor(secret: string) {
    this.secret = secret
  }
  
  revealSecret() {
    return this.secret // Accessible within class
  }
}

let privateEx = new PrivateExample('my secret')
// privateEx.secret // Error: Property 'secret' is private

// protected (accessible within class and subclasses)
class ProtectedExample {
  protected value: number
  
  constructor(value: number) {
    this.value = value
  }
}

class ChildClass extends ProtectedExample {
  showValue() {
    return this.value // Can access protected member
  }
}

let child = new ChildClass(42)
// child.value // Error: Property 'value' is protected

// readonly modifier
class ReadonlyExample {
  readonly id: number
  
  constructor(id: number) {
    this.id = id
  }
}

let example = new ReadonlyExample(123)
// example.id = 456 // Error: Cannot assign to 'id' because it is a read-only property

// Combining access modifiers
class BankAccount {
  public accountNumber: string
  private balance: number
  protected owner: string
  readonly accountType: string
  
  constructor(
    accountNumber: string,
    initialBalance: number,
    owner: string,
    accountType: string
  ) {
    this.accountNumber = accountNumber
    this.balance = initialBalance
    this.owner = owner
    this.accountType = accountType
  }
  
  deposit(amount: number) {
    if (amount > 0) {
      this.balance += amount
    }
  }
  
  getBalance() {
    return this.balance
  }
}

class SavingsAccount extends BankAccount {
  calculateInterest() {
    // Can access protected owner property
    console.log(`Calculating interest for ${this.owner}`)
    // Cannot access private balance property (must use getBalance)
    return this.getBalance() * 0.05
  }
}

Utilities and Compiler

ts-node Module

ts-node Usage Guide:

# Install ts-node
npm install -D ts-node

# Run TypeScript file directly
ts-node src/index.ts

# Use nodemon for auto-restart
npx nodemon --exec ts-node src/index.ts

# Compile and run (without generating files)
ts-node --transpile-only src/index.ts

# Use custom configuration file
ts-node -P tsconfig.dev.json src/index.ts

# Debugging support
node --inspect-brk -r ts-node/register src/index.ts

tsconfig.json Configuration Example:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "types": ["node"], // Specify global types
    "baseUrl": ".", // Base directory for resolving non-relative module names
    "paths": {      // Module name to path mapping based on baseUrl
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.spec.ts"]
}

Compiler Options Explained

Common Compiler Options:

{
  "compilerOptions": {
    // Target version
    "target": "ES2020", // ES3, ES5, ES6/ES2015, ES2016, ES2017, ES2018, ES2019, ES2020, ESNext
    
    // Module system
    "module": "CommonJS", // CommonJS, UMD, AMD, System, ES6/ES2015, ES2020, ESNext, None
    
    // Output directory
    "outDir": "./dist", // Redirect output directory
    
    // Source code directory
    "rootDir": "./src", // Used to control output directory structure
    
    // Strict type checking options
    "strict": true, // Enable all strict type checking options
    
    // Module resolution options
    "moduleResolution": "node", // node, classic
    
    // Type declaration files
    "declaration": true, // Generate corresponding .d.ts files
    
    // Source maps
    "sourceMap": true, // Generate corresponding .map files
    
    // Inline source maps
    "inlineSourceMap": false, // Generate single sourcemaps file instead of separate ones
    
    // Inline sources
    "inlineSources": false, // Include source code with sourcemaps in a single file
    
    // Experimental decorators
    "experimentalDecorators": true, // Enable experimental ES decorators
    
    // Emit decorator metadata
    "emitDecoratorMetadata": true, // Add design type metadata to decorator declarations
    
    // Other important options
    "esModuleInterop": true, // Allow default imports from modules without default exports
    "allowSyntheticDefaultImports": true, // Allow default imports from modules without default exports
    "skipLibCheck": true, // Skip type checking of all declaration files (.d.ts)
    "forceConsistentCasingInFileNames": true // Disallow inconsistent file name references
  }
}

Best Practices

Project Structure Recommendations

Recommended Project Structure:

my-project/
├── src/                  # Source code directory
│   ├── components/       # Reusable components
│   ├── services/         # Service layer
│   ├── utils/            # Utility functions
│   ├── models/           # Data models
│   ├── App.ts            # Main application file
│   └── index.ts          # Entry file
├── tests/                # Test files
├── dist/                 # Compiled output directory (auto-generated)
├── node_modules/         # Dependencies (auto-generated)
├── package.json          # Project configuration
├── tsconfig.json         # TypeScript configuration
└── README.md             # Project documentation

Type Safety Practices

Techniques for Enhancing Type Safety:

// 1. Use precise types instead of any
// Avoid:
function processData(data: any) { /* ... */ }

// Recommended:
interface Data {
  id: number
  name: string
  value?: number
}

function processData(data: Data) { /* ... */ }

// 2. Use read-only types to prevent accidental modifications
interface Config {
  apiUrl: string
  timeout: number
}

const config: Readonly<Config> = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
}

// config.apiUrl = 'new-url' // Error: Cannot assign to 'apiUrl' because it is a read-only property

// 3. Use type guards to narrow type ranges
function isString(value: unknown): value is string {
  return typeof value === 'string'
}

function printLength(value: string | number) {
  if (isString(value)) {
    console.log(value.length) // value is string here
  } else {
    console.log(value.toFixed(2)) // value is number here
  }
}

// 4. Use generics to increase code reusability
function identity<T>(arg: T): T {
  return arg
}

let output1 = identity<string>('hello')
let output2 = identity<number>(123)

// 5. Use utility types to simplify complex type operations
interface User {
  id: number
  name: string
  age: number
  email: string
}

// Pick specific properties from User
type UserBasicInfo = Pick<User, 'id' | 'name'>

// Omit specific properties from User
type UserWithoutEmail = Omit<User, 'email'>

// Make some properties optional
type PartialUser = Partial<User>

// Make some properties required
type RequiredUser = Required<Pick<User, 'id' | 'name'>>

Performance Optimization Tips

TypeScript Performance Optimization:

  1. Avoid Overusing Types: Add type annotations only where necessary
  2. Use Interfaces Over Type Aliases: Interfaces are more flexible for merging
  3. Use Generics Judiciously: Avoid overly complex generic nesting
  4. Utilize Project References: Split large projects into sub-projects
  5. Enable Incremental Compilation: "incremental": true option

Project References Example:

// tsconfig.json (root project)
{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true
  },
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/utils" }
  ]
}

// packages/core/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "outDir": "../../dist/core"
  },
  "include": ["src"]
}

// packages/utils/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "outDir": "../../dist/utils"
  },
  "include": ["src"]
}

Share your love